blob: 63098f7f755da81d274d8e92cf2a61fea13b6355 [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 Thompson3c263812012-12-01 17:20:28 -08007var LOG = 0;
Jeff Thompson5b265a72012-11-12 01:13:08 -08008
Jeff Thompsone06b31e2012-09-30 17:19:19 -07009/**
Jeff Thompson5b265a72012-11-12 01:13:08 -080010 * settings is an associative array with the following defaults:
11 * {
Jeff Thompsonac0ed602013-02-18 21:52:42 -080012 * getTransport: function() { return new WebSocketTransport(); },
Jeff Thompsond3a80dc2012-12-16 17:52:43 -080013 * getHostAndPort: transport.defaultGetHostAndPort,
Jeff Thompsond771b122013-01-26 19:04:41 -080014 * host: null, // If null, use getHostAndPort when connecting.
Jeff Thompsond3a80dc2012-12-16 17:52:43 -080015 * port: 9696,
Jeff Thompsonac0ed602013-02-18 21:52:42 -080016 * onopen: function() { if (LOG > 3) console.log("NDN connection established."); },
17 * onclose: function() { if (LOG > 3) console.log("NDN connection closed."); },
18 * verify: true // If false, don't verify and call upcall with Closure.UPCALL_CONTENT_UNVERIFIED.
Jeff Thompson5b265a72012-11-12 01:13:08 -080019 * }
Jeff Thompsond3a80dc2012-12-16 17:52:43 -080020 *
21 * getHostAndPort is a function, on each call it returns a new { host: host, port: port } or
22 * null if there are no more hosts.
Jeff Thompsone2f7b6d2013-02-24 20:37:27 -080023 *
24 * This throws an exception if NDN.supported is false.
Jeff Thompsone06b31e2012-09-30 17:19:19 -070025 */
Jeff Thompson5b265a72012-11-12 01:13:08 -080026var NDN = function NDN(settings) {
Jeff Thompsone2f7b6d2013-02-24 20:37:27 -080027 if (!NDN.supported)
28 throw new Error("The necessary JavaScript support is not available on this platform.");
29
Jeff Thompson5b265a72012-11-12 01:13:08 -080030 settings = (settings || {});
Jeff Thompson5b265a72012-11-12 01:13:08 -080031 var getTransport = (settings.getTransport || function() { return new WebSocketTransport(); });
Wentao Shang0e291c82012-12-02 23:36:29 -080032 this.transport = getTransport();
Jeff Thompsond3a80dc2012-12-16 17:52:43 -080033 this.getHostAndPort = (settings.getHostAndPort || this.transport.defaultGetHostAndPort);
Jeff Thompsond771b122013-01-26 19:04:41 -080034 this.host = (settings.host !== undefined ? settings.host : null);
Jeff Thompsond3a80dc2012-12-16 17:52:43 -080035 this.port = (settings.port || 9696);
Wentao Shang0e291c82012-12-02 23:36:29 -080036 this.readyStatus = NDN.UNOPEN;
Wentao Shangd4607392013-01-24 23:08:49 -080037 this.verify = (settings.verify !== undefined ? settings.verify : true);
Wentao Shang0e291c82012-12-02 23:36:29 -080038 // Event handler
Wentao Shangc0311e52012-12-03 10:38:23 -080039 this.onopen = (settings.onopen || function() { if (LOG > 3) console.log("NDN connection established."); });
40 this.onclose = (settings.onclose || function() { if (LOG > 3) console.log("NDN connection closed."); });
Jeff Thompson75771cb2013-01-20 23:27:38 -080041 this.ccndid = null;
Meki Cherkaoui8f173612012-06-06 01:05:40 -070042};
43
Wentao Shang0e291c82012-12-02 23:36:29 -080044NDN.UNOPEN = 0; // created but not opened yet
45NDN.OPENED = 1; // connection to ccnd opened
46NDN.CLOSED = 2; // connection to ccnd closed
Jeff Thompson5b265a72012-11-12 01:13:08 -080047
Jeff Thompsone2f7b6d2013-02-24 20:37:27 -080048/*
49 * Return true if necessary JavaScript support is available, else log an error and return false.
50 */
51NDN.getSupported = function() {
52 try {
53 var dummy = new Uint8Array(1).subarray(0, 1);
54 } catch (ex) {
55 console.log("NDN not available: Uint8Array not supported. " + ex);
56 return false;
57 }
58
59 return true;
60};
61
62NDN.supported = NDN.getSupported();
63
Wentao Shangb42483a2013-01-03 15:32:32 -080064NDN.ccndIdFetcher = new Name('/%C1.M.S.localhost/%C1.M.SRV/ccnd/KEY');
Jeff Thompson5b265a72012-11-12 01:13:08 -080065
Wentao Shangb42483a2013-01-03 15:32:32 -080066NDN.prototype.createRoute = function(host, port) {
Meki Cherkaoui8f173612012-06-06 01:05:40 -070067 this.host=host;
68 this.port=port;
Wentao Shangb42483a2013-01-03 15:32:32 -080069};
Meki Cherkaoui8f173612012-06-06 01:05:40 -070070
Wentao Shang82854bd2012-12-27 14:14:41 -080071
72NDN.KeyStore = new Array();
73
Wentao Shangb5d0c3e2012-12-30 11:12:03 -080074var KeyStoreEntry = function KeyStoreEntry(name, rsa, time) {
Wentao Shang82854bd2012-12-27 14:14:41 -080075 this.keyName = name; // KeyName
Wentao Shang82854bd2012-12-27 14:14:41 -080076 this.rsaKey = rsa; // RSA key
Wentao Shangb5d0c3e2012-12-30 11:12:03 -080077 this.timeStamp = time; // Time Stamp
78};
79
80NDN.addKeyEntry = function(/* KeyStoreEntry */ keyEntry) {
81 var result = NDN.getKeyByName(keyEntry.keyName);
82 if (result == null)
83 NDN.KeyStore.push(keyEntry);
84 else
85 result = keyEntry;
Wentao Shang82854bd2012-12-27 14:14:41 -080086};
87
88NDN.getKeyByName = function(/* KeyName */ name) {
89 var result = null;
90
91 for (var i = 0; i < NDN.KeyStore.length; i++) {
Wentao Shangb42483a2013-01-03 15:32:32 -080092 if (NDN.KeyStore[i].keyName.contentName.match(name.contentName)) {
Wentao Shang82854bd2012-12-27 14:14:41 -080093 if (result == null ||
94 NDN.KeyStore[i].keyName.contentName.components.length > result.keyName.contentName.components.length)
95 result = NDN.KeyStore[i];
96 }
97 }
98
99 return result;
100};
101
Jeff Thompsonbe85be62012-12-13 22:32:01 -0800102// For fetching data
103NDN.PITTable = new Array();
104
105var PITEntry = function PITEntry(interest, closure) {
106 this.interest = interest; // Interest
107 this.closure = closure; // Closure
Wentao Shangfcb16262013-01-20 14:42:46 -0800108 this.timerID = -1; // Timer ID
Jeff Thompsonbe85be62012-12-13 22:32:01 -0800109};
110
Jeff Thompson202728a2013-02-10 22:20:08 -0800111/*
112 * Return the entry from NDN.PITTable where the name conforms to the interest selectors, and
113 * the interest name is the longest that matches name.
114 */
Jeff Thompsonbe85be62012-12-13 22:32:01 -0800115NDN.getEntryForExpressedInterest = function(/*Name*/ name) {
Jeff Thompsonbe85be62012-12-13 22:32:01 -0800116 var result = null;
117
118 for (var i = 0; i < NDN.PITTable.length; i++) {
119 if (NDN.PITTable[i].interest.matches_name(name)) {
120 if (result == null ||
121 NDN.PITTable[i].interest.name.components.length > result.interest.name.components.length)
122 result = NDN.PITTable[i];
123 }
124 }
125
126 return result;
127};
128
Jeff Thompson75771cb2013-01-20 23:27:38 -0800129// For publishing data
130NDN.CSTable = new Array();
131
132var CSEntry = function CSEntry(name, closure) {
133 this.name = name; // String
134 this.closure = closure; // Closure
135};
136
137function getEntryForRegisteredPrefix(name) {
138 for (var i = 0; i < NDN.CSTable.length; i++) {
139 if (NDN.CSTable[i].name.match(name) != null)
140 return NDN.CSTable[i];
141 }
142 return null;
143}
144
Jeff Thompsond3a80dc2012-12-16 17:52:43 -0800145/*
146 * Return a function that selects a host at random from hostList and returns { host: host, port: port }.
147 * If no more hosts remain, return null.
148 */
149NDN.makeShuffledGetHostAndPort = function(hostList, port) {
150 // Make a copy.
151 hostList = hostList.slice(0, hostList.length);
152 DataUtils.shuffle(hostList);
153
154 return function() {
155 if (hostList.length == 0)
156 return null;
157
158 return { host: hostList.splice(0, 1)[0], port: port };
159 };
160};
161
Jeff Thompson34419762012-10-15 22:24:12 -0700162/** Encode name as an Interest. If template is not null, use its attributes.
163 * Send the interest to host:port, read the entire response and call
164 * closure.upcall(Closure.UPCALL_CONTENT (or Closure.UPCALL_CONTENT_UNVERIFIED),
Jeff Thompson97f27432012-10-16 00:28:03 -0700165 * new UpcallInfo(this, interest, 0, contentObject)).
Jeff Thompson34419762012-10-15 22:24:12 -0700166 */
167NDN.prototype.expressInterest = function(
168 // Name
169 name,
170 // Closure
171 closure,
172 // Interest
173 template) {
Jeff Thompson5b265a72012-11-12 01:13:08 -0800174 var interest = new Interest(name);
Jeff Thompson34419762012-10-15 22:24:12 -0700175 if (template != null) {
Jeff Thompson4404ab52012-10-21 10:29:48 -0700176 interest.minSuffixComponents = template.minSuffixComponents;
177 interest.maxSuffixComponents = template.maxSuffixComponents;
178 interest.publisherPublicKeyDigest = template.publisherPublicKeyDigest;
179 interest.exclude = template.exclude;
180 interest.childSelector = template.childSelector;
181 interest.answerOriginKind = template.answerOriginKind;
182 interest.scope = template.scope;
183 interest.interestLifetime = template.interestLifetime;
Jeff Thompson34419762012-10-15 22:24:12 -0700184 }
185 else
Jeff Thompson42806a12012-12-29 18:19:39 -0800186 interest.interestLifetime = 4000; // default interest timeout value in milliseconds.
Jeff Thompson34419762012-10-15 22:24:12 -0700187
Jeff Thompsond3a80dc2012-12-16 17:52:43 -0800188 if (this.host == null || this.port == null) {
189 if (this.getHostAndPort == null)
190 console.log('ERROR: host OR port NOT SET');
Jeff Thompsona5668d52013-01-26 16:23:27 -0800191 else {
192 var thisNDN = this;
193 this.connectAndExecute
Jeff Thompsonf668acb2013-01-26 20:29:46 -0800194 (function() { thisNDN.reconnectAndExpressInterest(interest, closure); });
Jeff Thompsona5668d52013-01-26 16:23:27 -0800195 }
Jeff Thompsond3a80dc2012-12-16 17:52:43 -0800196 }
197 else
Jeff Thompsonf668acb2013-01-26 20:29:46 -0800198 this.reconnectAndExpressInterest(interest, closure);
Jeff Thompsond3a80dc2012-12-16 17:52:43 -0800199};
Jeff Thompson5b265a72012-11-12 01:13:08 -0800200
Jeff Thompson537ce142013-01-26 20:02:48 -0800201/*
Jeff Thompsonf668acb2013-01-26 20:29:46 -0800202 * If the host and port are different than the ones in this.transport, then call
203 * this.transport.connect to change the connection (or connect for the first time).
204 * Then call expressInterestHelper.
205 */
206NDN.prototype.reconnectAndExpressInterest = function(interest, closure) {
207 if (this.transport.connectedHost != this.host || this.transport.connectedPort != this.port) {
208 var thisNDN = this;
209 this.transport.connect(thisNDN, function() { thisNDN.expressInterestHelper(interest, closure); });
210 }
211 else
212 this.expressInterestHelper(interest, closure);
213};
214
215/*
216 * Do the work of reconnectAndExpressInterest once we know we are connected. Set the PITTable and call
Jeff Thompson537ce142013-01-26 20:02:48 -0800217 * this.transport.send to send the interest.
218 */
219NDN.prototype.expressInterestHelper = function(interest, closure) {
Jeff Thompsone8de5822013-02-17 21:18:49 -0800220 var binaryInterest = encodeToBinaryInterest(interest);
221 var thisNDN = this;
Jeff Thompson537ce142013-01-26 20:02:48 -0800222 //TODO: check local content store first
223 if (closure != null) {
224 var pitEntry = new PITEntry(interest, closure);
225 // TODO: This needs to be a single thread-safe transaction on a global object.
226 NDN.PITTable.push(pitEntry);
227 closure.pitEntry = pitEntry;
Jeff Thompson537ce142013-01-26 20:02:48 -0800228
Jeff Thompsone8de5822013-02-17 21:18:49 -0800229 // Set interest timer.
Jeff Thompsona53e65a2013-02-10 10:52:52 -0800230 var timeoutMilliseconds = (interest.interestLifetime || 4000);
Jeff Thompsone8de5822013-02-17 21:18:49 -0800231 var timeoutCallback = function() {
232 if (LOG > 3) console.log("Interest time out: " + interest.name.to_uri());
Jeff Thompson537ce142013-01-26 20:02:48 -0800233
Jeff Thompsone8de5822013-02-17 21:18:49 -0800234 // Remove PIT entry from NDN.PITTable, even if we add it again later to re-express
235 // the interest because we don't want to match it in the mean time.
Jeff Thompson537ce142013-01-26 20:02:48 -0800236 // TODO: Make this a thread-safe operation on the global PITTable.
237 var index = NDN.PITTable.indexOf(pitEntry);
Jeff Thompson537ce142013-01-26 20:02:48 -0800238 if (index >= 0)
239 NDN.PITTable.splice(index, 1);
Jeff Thompson537ce142013-01-26 20:02:48 -0800240
241 // Raise closure callback
Jeff Thompsone8de5822013-02-17 21:18:49 -0800242 if (closure.upcall(Closure.UPCALL_INTEREST_TIMED_OUT,
243 new UpcallInfo(thisNDN, interest, 0, null)) == Closure.RESULT_REEXPRESS) {
244 if (LOG > 3) console.log("Re-express interest: " + interest.name.to_uri());
245 pitEntry.timerID = setTimeout(timeoutCallback, timeoutMilliseconds);
246 NDN.PITTable.push(pitEntry);
247 thisNDN.transport.send(binaryInterest);
248 }
249 };
250 pitEntry.timerID = setTimeout(timeoutCallback, timeoutMilliseconds);
Jeff Thompson537ce142013-01-26 20:02:48 -0800251 }
252
Jeff Thompsone8de5822013-02-17 21:18:49 -0800253 this.transport.send(binaryInterest);
Jeff Thompson537ce142013-01-26 20:02:48 -0800254};
255
Jeff Thompson5b265a72012-11-12 01:13:08 -0800256NDN.prototype.registerPrefix = function(name, closure, flag) {
Jeff Thompsond771b122013-01-26 19:04:41 -0800257 var thisNDN = this;
258 var onConnected = function() {
259 if (thisNDN.ccndid == null) {
260 // Fetch ccndid first, then register.
261 var interest = new Interest(NDN.ccndIdFetcher);
262 interest.interestLifetime = 4000; // milliseconds
263 if (LOG>3) console.log('Expressing interest for ccndid from ccnd.');
Jeff Thompsonf668acb2013-01-26 20:29:46 -0800264 thisNDN.reconnectAndExpressInterest
265 (interest, new NDN.FetchCcndidClosure(thisNDN, name, closure, flag));
Jeff Thompsond771b122013-01-26 19:04:41 -0800266 }
267 else
268 thisNDN.registerPrefixHelper(name, closure, flag);
269 };
Jeff Thompson75771cb2013-01-20 23:27:38 -0800270
Jeff Thompsond771b122013-01-26 19:04:41 -0800271 if (this.host == null || this.port == null) {
272 if (this.getHostAndPort == null)
273 console.log('ERROR: host OR port NOT SET');
274 else
275 this.connectAndExecute(onConnected);
276 }
277 else
278 onConnected();
279};
280
281/*
282 * This is a closure to receive the ContentObject for NDN.ccndIdFetcher and call
283 * registerPrefixHelper(name, callerClosure, flag).
284 */
285NDN.FetchCcndidClosure = function FetchCcndidClosure(ndn, name, callerClosure, flag) {
286 // Inherit from Closure.
287 Closure.call(this);
288
289 this.ndn = ndn;
290 this.name = name;
291 this.callerClosure = callerClosure;
292 this.flag = flag;
293};
294
295NDN.FetchCcndidClosure.prototype.upcall = function(kind, upcallInfo) {
296 if (kind == Closure.UPCALL_INTEREST_TIMED_OUT) {
297 console.log("Timeout while requesting the ccndid. Cannot registerPrefix for " +
298 this.name.to_uri() + " .");
299 return Closure.RESULT_OK;
300 }
301 if (!(kind == Closure.UPCALL_CONTENT ||
302 kind == Closure.UPCALL_CONTENT_UNVERIFIED))
303 // The upcall is not for us.
304 return Closure.RESULT_ERR;
305
306 var co = upcallInfo.contentObject;
307 if (!co.signedInfo || !co.signedInfo.publisher
308 || !co.signedInfo.publisher.publisherPublicKeyDigest)
309 console.log
310 ("ContentObject doesn't have a publisherPublicKeyDigest. Cannot set ccndid and registerPrefix for "
311 + this.name.to_uri() + " .");
312 else {
313 if (LOG>3) console.log('Got ccndid from ccnd.');
314 this.ndn.ccndid = co.signedInfo.publisher.publisherPublicKeyDigest;
315 if (LOG>3) console.log(this.ndn.ccndid);
316
317 this.ndn.registerPrefixHelper(this.name, this.callerClosure, this.flag);
Jeff Thompson75771cb2013-01-20 23:27:38 -0800318 }
Jeff Thompsond771b122013-01-26 19:04:41 -0800319
320 return Closure.RESULT_OK;
321};
322
Jeff Thompson537ce142013-01-26 20:02:48 -0800323/*
324 * Do the work of registerPrefix once we know we are connected with a ccndid.
325 */
Jeff Thompsond771b122013-01-26 19:04:41 -0800326NDN.prototype.registerPrefixHelper = function(name, closure, flag) {
Jeff Thompson75771cb2013-01-20 23:27:38 -0800327 var fe = new ForwardingEntry('selfreg', name, null, null, 3, 2147483647);
328 var bytes = encodeForwardingEntry(fe);
329
330 var si = new SignedInfo();
331 si.setFields();
332
333 var co = new ContentObject(new Name(), si, bytes, new Signature());
334 co.sign();
335 var coBinary = encodeToBinaryContentObject(co);
336
337 //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');
338 var nodename = this.ccndid;
339 var interestName = new Name(['ccnx', nodename, 'selfreg', coBinary]);
340
341 var interest = new Interest(interestName);
342 interest.scope = 1;
343 if (LOG > 3) console.log('Send Interest registration packet.');
344
345 var csEntry = new CSEntry(name.getName(), closure);
346 NDN.CSTable.push(csEntry);
347
348 this.transport.send(encodeToBinaryInterest(interest));
Jeff Thompson75771cb2013-01-20 23:27:38 -0800349};
350
351/*
352 * This is called when an entire binary XML element is received, such as a ContentObject or Interest.
353 * Look up in the PITTable and call the closure callback.
354 */
355NDN.prototype.onReceivedElement = function(element) {
Jeff Thompsona46083c2013-01-20 23:55:21 -0800356 if (LOG>3) console.log('Complete element received. Length ' + element.length + '. Start decoding.');
Jeff Thompson75771cb2013-01-20 23:27:38 -0800357 var decoder = new BinaryXMLDecoder(element);
358 // Dispatch according to packet type
359 if (decoder.peekStartElement(CCNProtocolDTags.Interest)) { // Interest packet
360 if (LOG > 3) console.log('Interest packet received.');
361
362 var interest = new Interest();
363 interest.from_ccnb(decoder);
364 if (LOG > 3) console.log(interest);
365 var nameStr = escape(interest.name.getName());
366 if (LOG > 3) console.log(nameStr);
367
368 var entry = getEntryForRegisteredPrefix(nameStr);
369 if (entry != null) {
370 //console.log(entry);
371 var info = new UpcallInfo(this, interest, 0, null);
372 var ret = entry.closure.upcall(Closure.UPCALL_INTEREST, info);
373 if (ret == Closure.RESULT_INTEREST_CONSUMED && info.contentObject != null)
374 this.transport.send(encodeToBinaryContentObject(info.contentObject));
375 }
376 } else if (decoder.peekStartElement(CCNProtocolDTags.ContentObject)) { // Content packet
377 if (LOG > 3) console.log('ContentObject packet received.');
378
379 var co = new ContentObject();
380 co.from_ccnb(decoder);
381
Jeff Thompsond771b122013-01-26 19:04:41 -0800382 var pitEntry = NDN.getEntryForExpressedInterest(co.name);
383 if (pitEntry != null) {
Jeff Thompson0ee721e2013-02-18 17:46:55 -0800384 // Cancel interest timer
385 clearTimeout(pitEntry.timerID);
386
Jeff Thompsond771b122013-01-26 19:04:41 -0800387 // Remove PIT entry from NDN.PITTable
388 var index = NDN.PITTable.indexOf(pitEntry);
389 if (index >= 0)
390 NDN.PITTable.splice(index, 1);
Jeff Thompson75771cb2013-01-20 23:27:38 -0800391
Jeff Thompsond771b122013-01-26 19:04:41 -0800392 var currentClosure = pitEntry.closure;
Jeff Thompson0ee721e2013-02-18 17:46:55 -0800393
Jeff Thompsond771b122013-01-26 19:04:41 -0800394 if (this.verify == false) {
395 // Pass content up without verifying the signature
396 currentClosure.upcall(Closure.UPCALL_CONTENT_UNVERIFIED, new UpcallInfo(this, null, 0, co));
397 return;
Jeff Thompson75771cb2013-01-20 23:27:38 -0800398 }
Wentao Shangd4607392013-01-24 23:08:49 -0800399
Jeff Thompsond771b122013-01-26 19:04:41 -0800400 // Key verification
401
402 // Recursive key fetching & verification closure
403 var KeyFetchClosure = function KeyFetchClosure(content, closure, key, sig, wit) {
404 this.contentObject = content; // unverified content object
405 this.closure = closure; // closure corresponding to the contentObject
406 this.keyName = key; // name of current key to be fetched
407 this.sigHex = sig; // hex signature string to be verified
408 this.witness = wit;
409
410 Closure.call(this);
411 };
412
413 var thisNDN = this;
414 KeyFetchClosure.prototype.upcall = function(kind, upcallInfo) {
415 if (kind == Closure.UPCALL_INTEREST_TIMED_OUT) {
416 console.log("In KeyFetchClosure.upcall: interest time out.");
417 console.log(this.keyName.contentName.getName());
418 } else if (kind == Closure.UPCALL_CONTENT) {
419 //console.log("In KeyFetchClosure.upcall: signature verification passed");
420
421 var rsakey = decodeSubjectPublicKeyInfo(upcallInfo.contentObject.content);
422 var verified = rsakey.verifyByteArray(this.contentObject.rawSignatureData, this.witness, this.sigHex);
423
424 var flag = (verified == true) ? Closure.UPCALL_CONTENT : Closure.UPCALL_CONTENT_BAD;
425 //console.log("raise encapsulated closure");
426 this.closure.upcall(flag, new UpcallInfo(thisNDN, null, 0, this.contentObject));
427
428 // Store key in cache
429 var keyEntry = new KeyStoreEntry(keylocator.keyName, rsakey, new Date().getTime());
430 NDN.addKeyEntry(keyEntry);
431 //console.log(NDN.KeyStore);
432 } else if (kind == Closure.UPCALL_CONTENT_BAD) {
433 console.log("In KeyFetchClosure.upcall: signature verification failed");
Wentao Shangd4607392013-01-24 23:08:49 -0800434 }
Jeff Thompsond771b122013-01-26 19:04:41 -0800435 };
Jeff Thompson75771cb2013-01-20 23:27:38 -0800436
Jeff Thompsond771b122013-01-26 19:04:41 -0800437 if (co.signedInfo && co.signedInfo.locator && co.signature) {
438 if (LOG > 3) console.log("Key verification...");
439 var sigHex = DataUtils.toHex(co.signature.signature).toLowerCase();
Jeff Thompson75771cb2013-01-20 23:27:38 -0800440
Jeff Thompsond771b122013-01-26 19:04:41 -0800441 var wit = null;
442 if (co.signature.Witness != null) {
443 wit = new Witness();
444 wit.decode(co.signature.Witness);
445 }
Jeff Thompson75771cb2013-01-20 23:27:38 -0800446
Jeff Thompsond771b122013-01-26 19:04:41 -0800447 var keylocator = co.signedInfo.locator;
448 if (keylocator.type == KeyLocatorType.KEYNAME) {
449 if (LOG > 3) console.log("KeyLocator contains KEYNAME");
450 //var keyname = keylocator.keyName.contentName.getName();
451 //console.log(nameStr);
452 //console.log(keyname);
Jeff Thompson75771cb2013-01-20 23:27:38 -0800453
Jeff Thompsond771b122013-01-26 19:04:41 -0800454 if (keylocator.keyName.contentName.match(co.name)) {
455 if (LOG > 3) console.log("Content is key itself");
Jeff Thompson75771cb2013-01-20 23:27:38 -0800456
Jeff Thompsond771b122013-01-26 19:04:41 -0800457 var rsakey = decodeSubjectPublicKeyInfo(co.content);
458 var verified = rsakey.verifyByteArray(co.rawSignatureData, wit, sigHex);
459 var flag = (verified == true) ? Closure.UPCALL_CONTENT : Closure.UPCALL_CONTENT_BAD;
460
461 currentClosure.upcall(flag, new UpcallInfo(this, null, 0, co));
462
463 // SWT: We don't need to store key here since the same key will be
464 // stored again in the closure.
465 //var keyEntry = new KeyStoreEntry(keylocator.keyName, rsakey, new Date().getTime());
466 //NDN.addKeyEntry(keyEntry);
467 //console.log(NDN.KeyStore);
468 } else {
469 // Check local key store
470 var keyEntry = NDN.getKeyByName(keylocator.keyName);
471 if (keyEntry) {
472 // Key found, verify now
473 if (LOG > 3) console.log("Local key cache hit");
474 var rsakey = keyEntry.rsaKey;
Jeff Thompson75771cb2013-01-20 23:27:38 -0800475 var verified = rsakey.verifyByteArray(co.rawSignatureData, wit, sigHex);
476 var flag = (verified == true) ? Closure.UPCALL_CONTENT : Closure.UPCALL_CONTENT_BAD;
Jeff Thompson75771cb2013-01-20 23:27:38 -0800477
Jeff Thompsond771b122013-01-26 19:04:41 -0800478 // Raise callback
479 currentClosure.upcall(flag, new UpcallInfo(this, null, 0, co));
480 } else {
481 // Not found, fetch now
482 if (LOG > 3) console.log("Fetch key according to keylocator");
483 var nextClosure = new KeyFetchClosure(co, currentClosure, keylocator.keyName, sigHex, wit);
484 this.expressInterest(keylocator.keyName.contentName.getPrefix(4), nextClosure);
Jeff Thompson75771cb2013-01-20 23:27:38 -0800485 }
Jeff Thompson75771cb2013-01-20 23:27:38 -0800486 }
Jeff Thompsond771b122013-01-26 19:04:41 -0800487 } else if (keylocator.type == KeyLocatorType.KEY) {
488 if (LOG > 3) console.log("Keylocator contains KEY");
489
490 var rsakey = decodeSubjectPublicKeyInfo(co.signedInfo.locator.publicKey);
491 var verified = rsakey.verifyByteArray(co.rawSignatureData, wit, sigHex);
492
493 var flag = (verified == true) ? Closure.UPCALL_CONTENT : Closure.UPCALL_CONTENT_BAD;
494 // Raise callback
495 currentClosure.upcall(Closure.UPCALL_CONTENT, new UpcallInfo(this, null, 0, co));
496
497 // Since KeyLocator does not contain key name for this key,
498 // we have no way to store it as a key entry in KeyStore.
499 } else {
500 var cert = keylocator.certificate;
501 console.log("KeyLocator contains CERT");
502 console.log(cert);
503
504 // TODO: verify certificate
Jeff Thompson75771cb2013-01-20 23:27:38 -0800505 }
506 }
507 }
508 } else
509 console.log('Incoming packet is not Interest or ContentObject. Discard now.');
Wentao Shangb42483a2013-01-03 15:32:32 -0800510};
Jeff Thompsond3a80dc2012-12-16 17:52:43 -0800511
512/*
513 * Assume this.getHostAndPort is not null. This is called when this.host is null or its host
Jeff Thompsona5668d52013-01-26 16:23:27 -0800514 * is not alive. Get a host and port, connect, then execute onConnected().
Jeff Thompsond3a80dc2012-12-16 17:52:43 -0800515 */
Jeff Thompsona5668d52013-01-26 16:23:27 -0800516NDN.prototype.connectAndExecute = function(onConnected) {
Jeff Thompsond3a80dc2012-12-16 17:52:43 -0800517 var hostAndPort = this.getHostAndPort();
518 if (hostAndPort == null) {
519 console.log('ERROR: No more hosts from getHostAndPort');
520 this.host = null;
521 return;
522 }
523
524 if (hostAndPort.host == this.host && hostAndPort.port == this.port) {
525 console.log('ERROR: The host returned by getHostAndPort is not alive: ' +
526 this.host + ":" + this.port);
527 return;
528 }
529
530 this.host = hostAndPort.host;
531 this.port = hostAndPort.port;
Jeff Thompsona5668d52013-01-26 16:23:27 -0800532 if (LOG>3) console.log("Connect: trying host from getHostAndPort: " + this.host);
Jeff Thompsond3a80dc2012-12-16 17:52:43 -0800533
Jeff Thompson75771cb2013-01-20 23:27:38 -0800534 // Fetch any content.
535 var interest = new Interest(new Name("/"));
Jeff Thompson42806a12012-12-29 18:19:39 -0800536 interest.interestLifetime = 4000; // milliseconds
Jeff Thompsond3a80dc2012-12-16 17:52:43 -0800537
538 var thisNDN = this;
539 var timerID = setTimeout(function() {
Jeff Thompsona5668d52013-01-26 16:23:27 -0800540 if (LOG>3) console.log("Connect: timeout waiting for host " + thisNDN.host);
Jeff Thompsond3a80dc2012-12-16 17:52:43 -0800541 // Try again.
Jeff Thompsona5668d52013-01-26 16:23:27 -0800542 thisNDN.connectAndExecute(onConnected);
Jeff Thompsond3a80dc2012-12-16 17:52:43 -0800543 }, 3000);
544
Jeff Thompsonf668acb2013-01-26 20:29:46 -0800545 this.reconnectAndExpressInterest
546 (interest, new NDN.ConnectClosure(this, onConnected, timerID));
Wentao Shangb42483a2013-01-03 15:32:32 -0800547};
Jeff Thompsond3a80dc2012-12-16 17:52:43 -0800548
Jeff Thompsona5668d52013-01-26 16:23:27 -0800549NDN.ConnectClosure = function ConnectClosure(ndn, onConnected, timerID) {
Jeff Thompsond3a80dc2012-12-16 17:52:43 -0800550 // Inherit from Closure.
551 Closure.call(this);
552
553 this.ndn = ndn;
Jeff Thompsona5668d52013-01-26 16:23:27 -0800554 this.onConnected = onConnected;
Jeff Thompsond3a80dc2012-12-16 17:52:43 -0800555 this.timerID = timerID;
556};
557
558NDN.ConnectClosure.prototype.upcall = function(kind, upcallInfo) {
559 if (!(kind == Closure.UPCALL_CONTENT ||
Jeff Thompson75771cb2013-01-20 23:27:38 -0800560 kind == Closure.UPCALL_CONTENT_UNVERIFIED))
Jeff Thompsond3a80dc2012-12-16 17:52:43 -0800561 // The upcall is not for us.
562 return Closure.RESULT_ERR;
563
Jeff Thompsona5668d52013-01-26 16:23:27 -0800564 // The host is alive, so cancel the timeout and continue with onConnected().
Jeff Thompsond3a80dc2012-12-16 17:52:43 -0800565 clearTimeout(this.timerID);
Jeff Thompsond771b122013-01-26 19:04:41 -0800566
567 // Call NDN.onopen after success
568 this.ndn.readyStatus = NDN.OPENED;
569 this.ndn.onopen();
570
Jeff Thompsona5668d52013-01-26 16:23:27 -0800571 this.onConnected();
Jeff Thompsond3a80dc2012-12-16 17:52:43 -0800572
573 return Closure.RESULT_OK;
574};
575
Jeff Thompson75771cb2013-01-20 23:27:38 -0800576/*
577 * A BinaryXmlElementReader lets you call onReceivedData multiple times which uses a
578 * BinaryXMLStructureDecoder to detect the end of a binary XML element and calls
579 * elementListener.onReceivedElement(element) with the element.
580 * This handles the case where a single call to onReceivedData may contain multiple elements.
581 */
582var BinaryXmlElementReader = function BinaryXmlElementReader(elementListener) {
583 this.elementListener = elementListener;
584 this.dataParts = [];
585 this.structureDecoder = new BinaryXMLStructureDecoder();
586};
587
588BinaryXmlElementReader.prototype.onReceivedData = function(/* Uint8Array */ rawData) {
Jeff Thompson75771cb2013-01-20 23:27:38 -0800589 // Process multiple objects in the data.
590 while(true) {
591 // Scan the input to check if a whole ccnb object has been read.
592 this.structureDecoder.seek(0);
593 if (this.structureDecoder.findElementEnd(rawData)) {
594 // Got the remainder of an object. Report to the caller.
595 this.dataParts.push(rawData.subarray(0, this.structureDecoder.offset));
Jeff Thompson0790af82013-01-26 19:54:27 -0800596 var element = DataUtils.concatArrays(this.dataParts);
597 this.dataParts = [];
598 try {
599 this.elementListener.onReceivedElement(element);
600 } catch (ex) {
601 console.log("BinaryXmlElementReader: ignoring exception from onReceivedElement: " + ex);
602 }
Jeff Thompson75771cb2013-01-20 23:27:38 -0800603
604 // Need to read a new object.
605 rawData = rawData.subarray(this.structureDecoder.offset, rawData.length);
Jeff Thompson75771cb2013-01-20 23:27:38 -0800606 this.structureDecoder = new BinaryXMLStructureDecoder();
607 if (rawData.length == 0)
608 // No more data in the packet.
609 return;
610
611 // else loop back to decode.
612 }
613 else {
614 // Save for a later call to concatArrays so that we only copy data once.
615 this.dataParts.push(rawData);
Jeff Thompsona46083c2013-01-20 23:55:21 -0800616 if (LOG>3) console.log('Incomplete packet received. Length ' + rawData.length + '. Wait for more input.');
Jeff Thompson75771cb2013-01-20 23:27:38 -0800617 return;
618 }
619 }
620}