Source: util/segment-fetcher.js

  1. /**
  2. * Copyright (C) 2015-2018 Regents of the University of California.
  3. * @author: Jeff Thompson <jefft0@remap.ucla.edu>
  4. * @author: From ndn-cxx util/segment-fetcher https://github.com/named-data/ndn-cxx
  5. *
  6. * This program is free software: you can redistribute it and/or modify
  7. * it under the terms of the GNU Lesser General Public License as published by
  8. * the Free Software Foundation, either version 3 of the License, or
  9. * (at your option) any later version.
  10. *
  11. * This program is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. * GNU Lesser General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU Lesser General Public License
  17. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  18. * A copy of the GNU Lesser General Public License is in the file COPYING.
  19. */
  20. /** @ignore */
  21. var Interest = require('../interest.js').Interest; /** @ignore */
  22. var Blob = require('./blob.js').Blob; /** @ignore */
  23. var KeyChain = require('../security/key-chain.js').KeyChain; /** @ignore */
  24. var NdnCommon = require('./ndn-common.js').NdnCommon;
  25. /**
  26. * SegmentFetcher is a utility class to fetch the latest version of segmented data.
  27. *
  28. * SegmentFetcher assumes that the data is named /<prefix>/<version>/<segment>,
  29. * where:
  30. * - <prefix> is the specified name prefix,
  31. * - <version> is an unknown version that needs to be discovered, and
  32. * - <segment> is a segment number. (The number of segments is unknown and is
  33. * controlled by the `FinalBlockId` field in at least the last Data packet.
  34. *
  35. * The following logic is implemented in SegmentFetcher:
  36. *
  37. * 1. Express the first Interest to discover the version:
  38. *
  39. * >> Interest: /<prefix>?ChildSelector=1&MustBeFresh=true
  40. *
  41. * 2. Infer the latest version of the Data: <version> = Data.getName().get(-2)
  42. *
  43. * 3. If the segment number in the retrieved packet == 0, go to step 5.
  44. *
  45. * 4. Send an Interest for segment 0:
  46. *
  47. * >> Interest: /<prefix>/<version>/<segment=0>
  48. *
  49. * 5. Keep sending Interests for the next segment while the retrieved Data does
  50. * not have a FinalBlockId or the FinalBlockId != Data.getName().get(-1).
  51. *
  52. * >> Interest: /<prefix>/<version>/<segment=(N+1))>
  53. *
  54. * 6. Call the onComplete callback with a Blob that concatenates the content
  55. * from all the segmented objects.
  56. *
  57. * If an error occurs during the fetching process, the onError callback is called
  58. * with a proper error code. The following errors are possible:
  59. *
  60. * - `INTEREST_TIMEOUT`: if any of the Interests times out
  61. * - `DATA_HAS_NO_SEGMENT`: if any of the retrieved Data packets don't have a segment
  62. * as the last component of the name (not counting the implicit digest)
  63. * - `SEGMENT_VERIFICATION_FAILED`: if any retrieved segment fails
  64. * the user-provided VerifySegment callback or KeyChain verifyData.
  65. * - `IO_ERROR`: for I/O errors when sending an Interest.
  66. *
  67. * In order to validate individual segments, a KeyChain needs to be supplied.
  68. * If verifyData fails, the fetching process is aborted with
  69. * SEGMENT_VERIFICATION_FAILED. If data validation is not required, pass null.
  70. *
  71. * Example:
  72. * var onComplete = function(content) { ... }
  73. *
  74. * var onError = function(errorCode, message) { ... }
  75. *
  76. * var interest = new Interest(new Name("/data/prefix"));
  77. * interest.setInterestLifetimeMilliseconds(1000);
  78. *
  79. * SegmentFetcher.fetch(face, interest, null, onComplete, onError);
  80. *
  81. * This is a private constructor to create a new SegmentFetcher to use the Face.
  82. * An application should use SegmentFetcher.fetch. If validatorKeyChain is not
  83. * null, use it and ignore verifySegment. After creating the SegmentFetcher,
  84. * call fetchFirstSegment.
  85. * @param {Face} face This calls face.expressInterest to fetch more segments.
  86. * @param validatorKeyChain {KeyChain} If this is not null, use its verifyData
  87. * instead of the verifySegment callback.
  88. * @param {function} verifySegment When a Data packet is received this calls
  89. * verifySegment(data) where data is a Data object. If it returns False then
  90. * abort fetching and call onError with
  91. * SegmentFetcher.ErrorCode.SEGMENT_VERIFICATION_FAILED.
  92. * NOTE: The library will log any exceptions thrown by this callback, but for
  93. * better error handling the callback should catch and properly handle any
  94. * exceptions.
  95. * @param {function} onComplete When all segments are received, call
  96. * onComplete(content) where content is a Blob which has the concatenation of
  97. * the content of all the segments.
  98. * NOTE: The library will log any exceptions thrown by this callback, but for
  99. * better error handling the callback should catch and properly handle any
  100. * exceptions.
  101. * @param {function} onError Call onError.onError(errorCode, message) for
  102. * timeout or an error processing segments. errorCode is a value from
  103. * SegmentFetcher.ErrorCode and message is a related string.
  104. * NOTE: The library will log any exceptions thrown by this callback, but for
  105. * better error handling the callback should catch and properly handle any
  106. * exceptions.
  107. * @constructor
  108. */
  109. var SegmentFetcher = function SegmentFetcher
  110. (face, validatorKeyChain, verifySegment, onComplete, onError)
  111. {
  112. this.face = face;
  113. this.validatorKeyChain = validatorKeyChain;
  114. this.verifySegment = verifySegment;
  115. this.onComplete = onComplete;
  116. this.onError = onError;
  117. this.contentParts = []; // of Buffer
  118. };
  119. exports.SegmentFetcher = SegmentFetcher;
  120. /**
  121. * An ErrorCode value is passed in the onError callback.
  122. */
  123. SegmentFetcher.ErrorCode = {
  124. INTEREST_TIMEOUT: 1,
  125. DATA_HAS_NO_SEGMENT: 2,
  126. SEGMENT_VERIFICATION_FAILED: 3
  127. };
  128. /**
  129. * DontVerifySegment may be used in fetch to skip validation of Data packets.
  130. */
  131. SegmentFetcher.DontVerifySegment = function(data)
  132. {
  133. return true;
  134. };
  135. /**
  136. * Initiate segment fetching. For more details, see the documentation for the
  137. * class. There are two forms of fetch:
  138. * fetch(face, baseInterest, validatorKeyChain, onComplete, onError)
  139. * and
  140. * fetch(face, baseInterest, verifySegment, onComplete, onError)
  141. * @param {Face} face This calls face.expressInterest to fetch more segments.
  142. * @param {Interest} baseInterest An Interest for the initial segment of the
  143. * requested data, where baseInterest.getName() has the name prefix. This
  144. * interest may include a custom InterestLifetime and selectors that will
  145. * propagate to all subsequent Interests. The only exception is that the initial
  146. * Interest will be forced to include selectors "ChildSelector=1" and
  147. * "MustBeFresh=true" which will be turned off in subsequent Interests.
  148. * @param validatorKeyChain {KeyChain} When a Data packet is received this calls
  149. * validatorKeyChain.verifyData(data). If validation fails then abortfetching
  150. * and call onError with SEGMENT_VERIFICATION_FAILED. This does not make a copy
  151. * of the KeyChain; the object must remain valid while fetching.
  152. * If validatorKeyChain is null, this does not validate the data packet.
  153. * @param {function} verifySegment When a Data packet is received this calls
  154. * verifySegment(data) where data is a Data object. If it returns False then
  155. * abort fetching and call onError with
  156. * SegmentFetcher.ErrorCode.SEGMENT_VERIFICATION_FAILED. If data validation is
  157. * not required, use SegmentFetcher.DontVerifySegment.
  158. * NOTE: The library will log any exceptions thrown by this callback, but for
  159. * better error handling the callback should catch and properly handle any
  160. * exceptions.
  161. * @param {function} onComplete When all segments are received, call
  162. * onComplete(content) where content is a Blob which has the concatenation of
  163. * the content of all the segments.
  164. * NOTE: The library will log any exceptions thrown by this callback, but for
  165. * better error handling the callback should catch and properly handle any
  166. * exceptions.
  167. * @param {function} onError Call onError.onError(errorCode, message) for
  168. * timeout or an error processing segments. errorCode is a value from
  169. * SegmentFetcher.ErrorCode and message is a related string.
  170. * NOTE: The library will log any exceptions thrown by this callback, but for
  171. * better error handling the callback should catch and properly handle any
  172. * exceptions.
  173. */
  174. SegmentFetcher.fetch = function
  175. (face, baseInterest, validatorKeyChainOrVerifySegment, onComplete, onError)
  176. {
  177. if (validatorKeyChainOrVerifySegment == null ||
  178. validatorKeyChainOrVerifySegment instanceof KeyChain)
  179. new SegmentFetcher
  180. (face, validatorKeyChainOrVerifySegment, SegmentFetcher.DontVerifySegment,
  181. onComplete, onError)
  182. .fetchFirstSegment(baseInterest);
  183. else
  184. new SegmentFetcher
  185. (face, null, validatorKeyChainOrVerifySegment, onComplete, onError)
  186. .fetchFirstSegment(baseInterest);
  187. };
  188. SegmentFetcher.prototype.fetchFirstSegment = function(baseInterest)
  189. {
  190. var interest = new Interest(baseInterest);
  191. interest.setChildSelector(1);
  192. interest.setMustBeFresh(true);
  193. var thisSegmentFetcher = this;
  194. this.face.expressInterest
  195. (interest,
  196. function(originalInterest, data)
  197. { thisSegmentFetcher.onData(originalInterest, data); },
  198. function(interest) { thisSegmentFetcher.onTimeout(interest); });
  199. };
  200. SegmentFetcher.prototype.fetchNextSegment = function
  201. (originalInterest, dataName, segment)
  202. {
  203. // Start with the original Interest to preserve any special selectors.
  204. var interest = new Interest(originalInterest);
  205. // Changing a field clears the nonce so that the library will generate a new
  206. // one.
  207. interest.setChildSelector(0);
  208. interest.setMustBeFresh(false);
  209. interest.setName(dataName.getPrefix(-1).appendSegment(segment));
  210. var thisSegmentFetcher = this;
  211. this.face.expressInterest
  212. (interest, function(originalInterest, data)
  213. { thisSegmentFetcher.onData(originalInterest, data); },
  214. function(interest) { thisSegmentFetcher.onTimeout(interest); });
  215. };
  216. SegmentFetcher.prototype.onData = function(originalInterest, data)
  217. {
  218. if (this.validatorKeyChain != null) {
  219. try {
  220. var thisSegmentFetcher = this;
  221. this.validatorKeyChain.verifyData
  222. (data,
  223. function(localData) {
  224. thisSegmentFetcher.onVerified(localData, originalInterest);
  225. },
  226. this.onValidationFailed.bind(this));
  227. } catch (ex) {
  228. console.log("Error in KeyChain.verifyData: " + ex);
  229. }
  230. }
  231. else {
  232. if (!this.verifySegment(data)) {
  233. try {
  234. this.onError
  235. (SegmentFetcher.ErrorCode.SEGMENT_VERIFICATION_FAILED,
  236. "Segment verification failed");
  237. } catch (ex) {
  238. console.log("Error in onError: " + NdnCommon.getErrorWithStackTrace(ex));
  239. }
  240. return;
  241. }
  242. this.onVerified(data, originalInterest);
  243. }
  244. };
  245. SegmentFetcher.prototype.onVerified = function(data, originalInterest)
  246. {
  247. if (!SegmentFetcher.endsWithSegmentNumber(data.getName())) {
  248. // We don't expect a name without a segment number. Treat it as a bad packet.
  249. try {
  250. this.onError
  251. (SegmentFetcher.ErrorCode.DATA_HAS_NO_SEGMENT,
  252. "Got an unexpected packet without a segment number: " +
  253. data.getName().toUri());
  254. } catch (ex) {
  255. console.log("Error in onError: " + NdnCommon.getErrorWithStackTrace(ex));
  256. }
  257. }
  258. else {
  259. var currentSegment = 0;
  260. try {
  261. currentSegment = data.getName().get(-1).toSegment();
  262. }
  263. catch (ex) {
  264. try {
  265. this.onError
  266. (SegmentFetcher.ErrorCode.DATA_HAS_NO_SEGMENT,
  267. "Error decoding the name segment number " +
  268. data.getName().get(-1).toEscapedString() + ": " + ex);
  269. } catch (ex) {
  270. console.log("Error in onError: " + NdnCommon.getErrorWithStackTrace(ex));
  271. }
  272. return;
  273. }
  274. var expectedSegmentNumber = this.contentParts.length;
  275. if (currentSegment != expectedSegmentNumber)
  276. // Try again to get the expected segment. This also includes the case
  277. // where the first segment is not segment 0.
  278. this.fetchNextSegment
  279. (originalInterest, data.getName(), expectedSegmentNumber);
  280. else {
  281. // Save the content and check if we are finished.
  282. this.contentParts.push(data.getContent().buf());
  283. if (data.getMetaInfo().getFinalBlockId().getValue().size() > 0) {
  284. var finalSegmentNumber = 0;
  285. try {
  286. finalSegmentNumber = (data.getMetaInfo().getFinalBlockId().toSegment());
  287. }
  288. catch (ex) {
  289. try {
  290. this.onError
  291. (SegmentFetcher.ErrorCode.DATA_HAS_NO_SEGMENT,
  292. "Error decoding the FinalBlockId segment number " +
  293. data.getMetaInfo().getFinalBlockId().toEscapedString() +
  294. ": " + ex);
  295. } catch (ex) {
  296. console.log("Error in onError: " + NdnCommon.getErrorWithStackTrace(ex));
  297. }
  298. return;
  299. }
  300. if (currentSegment == finalSegmentNumber) {
  301. // We are finished.
  302. // Concatenate to get content.
  303. var content = Buffer.concat(this.contentParts);
  304. try {
  305. this.onComplete(new Blob(content, false));
  306. } catch (ex) {
  307. console.log("Error in onComplete: " + NdnCommon.getErrorWithStackTrace(ex));
  308. }
  309. return;
  310. }
  311. }
  312. // Fetch the next segment.
  313. this.fetchNextSegment
  314. (originalInterest, data.getName(), expectedSegmentNumber + 1);
  315. }
  316. }
  317. }
  318. SegmentFetcher.prototype.onValidationFailed = function(data, reason)
  319. {
  320. try {
  321. this.onError
  322. (SegmentFetcher.ErrorCode.SEGMENT_VERIFICATION_FAILED,
  323. "Segment verification failed for " + data.getName().toUri() +
  324. " . Reason: " + reason);
  325. } catch (ex) {
  326. console.log("Error in onError: " + NdnCommon.getErrorWithStackTrace(ex));
  327. }
  328. };
  329. SegmentFetcher.prototype.onTimeout = function(interest)
  330. {
  331. try {
  332. this.onError
  333. (SegmentFetcher.ErrorCode.INTEREST_TIMEOUT,
  334. "Time out for interest " + interest.getName().toUri());
  335. } catch (ex) {
  336. console.log("Error in onError: " + NdnCommon.getErrorWithStackTrace(ex));
  337. }
  338. };
  339. /**
  340. * Check if the last component in the name is a segment number.
  341. * @param {Name} name The name to check.
  342. * @return {boolean} True if the name ends with a segment number, otherwise false.
  343. */
  344. SegmentFetcher.endsWithSegmentNumber = function(name)
  345. {
  346. return name.size() >= 1 && name.get(-1).isSegment();
  347. };