Source: util/segment-fetcher.js

  1. /**
  2. * Copyright (C) 2015-2016 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 NdnCommon = require('./ndn-common.js').NdnCommon;
  24. /**
  25. * SegmentFetcher is a utility class to fetch the latest version of segmented data.
  26. *
  27. * SegmentFetcher assumes that the data is named /<prefix>/<version>/<segment>,
  28. * where:
  29. * - <prefix> is the specified name prefix,
  30. * - <version> is an unknown version that needs to be discovered, and
  31. * - <segment> is a segment number. (The number of segments is unknown and is
  32. * controlled by the `FinalBlockId` field in at least the last Data packet.
  33. *
  34. * The following logic is implemented in SegmentFetcher:
  35. *
  36. * 1. Express the first Interest to discover the version:
  37. *
  38. * >> Interest: /<prefix>?ChildSelector=1&MustBeFresh=true
  39. *
  40. * 2. Infer the latest version of the Data: <version> = Data.getName().get(-2)
  41. *
  42. * 3. If the segment number in the retrieved packet == 0, go to step 5.
  43. *
  44. * 4. Send an Interest for segment 0:
  45. *
  46. * >> Interest: /<prefix>/<version>/<segment=0>
  47. *
  48. * 5. Keep sending Interests for the next segment while the retrieved Data does
  49. * not have a FinalBlockId or the FinalBlockId != Data.getName().get(-1).
  50. *
  51. * >> Interest: /<prefix>/<version>/<segment=(N+1))>
  52. *
  53. * 6. Call the onComplete callback with a Blob that concatenates the content
  54. * from all the segmented objects.
  55. *
  56. * If an error occurs during the fetching process, the onError callback is called
  57. * with a proper error code. The following errors are possible:
  58. *
  59. * - `INTEREST_TIMEOUT`: if any of the Interests times out
  60. * - `DATA_HAS_NO_SEGMENT`: if any of the retrieved Data packets don't have a segment
  61. * as the last component of the name (not counting the implicit digest)
  62. * - `SEGMENT_VERIFICATION_FAILED`: if any retrieved segment fails
  63. * the user-provided VerifySegment callback
  64. * - `IO_ERROR`: for I/O errors when sending an Interest.
  65. *
  66. * In order to validate individual segments, a verifySegment callback needs to
  67. * be specified. If the callback returns false, the fetching process is aborted
  68. * with SEGMENT_VERIFICATION_FAILED. If data validation is not required, the
  69. * provided DontVerifySegment object can be used.
  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
  80. * (face, interest, SegmentFetcher.DontVerifySegment, onComplete, onError);
  81. *
  82. * This is a private constructor to create a new SegmentFetcher to use the Face.
  83. * An application should use SegmentFetcher.fetch.
  84. * @param {Face} face This calls face.expressInterest to fetch more segments.
  85. * @param {function} verifySegment When a Data packet is received this calls
  86. * verifySegment(data) where data is a Data object. If it returns False then
  87. * abort fetching and call onError with
  88. * SegmentFetcher.ErrorCode.SEGMENT_VERIFICATION_FAILED.
  89. * NOTE: The library will log any exceptions thrown by this callback, but for
  90. * better error handling the callback should catch and properly handle any
  91. * exceptions.
  92. * @param {function} onComplete When all segments are received, call
  93. * onComplete(content) where content is a Blob which has the concatenation of
  94. * the content of all the segments.
  95. * NOTE: The library will log any exceptions thrown by this callback, but for
  96. * better error handling the callback should catch and properly handle any
  97. * exceptions.
  98. * @param {function} onError Call onError.onError(errorCode, message) for
  99. * timeout or an error processing segments. errorCode is a value from
  100. * SegmentFetcher.ErrorCode and message is a related string.
  101. * NOTE: The library will log any exceptions thrown by this callback, but for
  102. * better error handling the callback should catch and properly handle any
  103. * exceptions.
  104. * @constructor
  105. */
  106. var SegmentFetcher = function SegmentFetcher
  107. (face, verifySegment, onComplete, onError)
  108. {
  109. this.face = face;
  110. this.verifySegment = verifySegment;
  111. this.onComplete = onComplete;
  112. this.onError = onError;
  113. this.contentParts = []; // of Buffer
  114. };
  115. exports.SegmentFetcher = SegmentFetcher;
  116. /**
  117. * An ErrorCode value is passed in the onError callback.
  118. */
  119. SegmentFetcher.ErrorCode = {
  120. INTEREST_TIMEOUT: 1,
  121. DATA_HAS_NO_SEGMENT: 2,
  122. SEGMENT_VERIFICATION_FAILED: 3
  123. };
  124. /**
  125. * DontVerifySegment may be used in fetch to skip validation of Data packets.
  126. */
  127. SegmentFetcher.DontVerifySegment = function(data)
  128. {
  129. return true;
  130. };
  131. /**
  132. * Initiate segment fetching. For more details, see the documentation for the
  133. * class.
  134. * @param {Face} face This calls face.expressInterest to fetch more segments.
  135. * @param {Interest} baseInterest An Interest for the initial segment of the
  136. * requested data, where baseInterest.getName() has the name prefix. This
  137. * interest may include a custom InterestLifetime and selectors that will
  138. * propagate to all subsequent Interests. The only exception is that the initial
  139. * Interest will be forced to include selectors "ChildSelector=1" and
  140. * "MustBeFresh=true" which will be turned off in subsequent Interests.
  141. * @param {function} verifySegment When a Data packet is received this calls
  142. * verifySegment(data) where data is a Data object. If it returns False then
  143. * abort fetching and call onError with
  144. * SegmentFetcher.ErrorCode.SEGMENT_VERIFICATION_FAILED. If data validation is
  145. * not required, use SegmentFetcher.DontVerifySegment.
  146. * NOTE: The library will log any exceptions thrown by this callback, but for
  147. * better error handling the callback should catch and properly handle any
  148. * exceptions.
  149. * @param {function} onComplete When all segments are received, call
  150. * onComplete(content) where content is a Blob which has the concatenation of
  151. * the content of all the segments.
  152. * NOTE: The library will log any exceptions thrown by this callback, but for
  153. * better error handling the callback should catch and properly handle any
  154. * exceptions.
  155. * @param {function} onError Call onError.onError(errorCode, message) for
  156. * timeout or an error processing segments. errorCode is a value from
  157. * SegmentFetcher.ErrorCode and message is a related string.
  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. */
  162. SegmentFetcher.fetch = function
  163. (face, baseInterest, verifySegment, onComplete, onError)
  164. {
  165. new SegmentFetcher(face, verifySegment, onComplete, onError).fetchFirstSegment
  166. (baseInterest);
  167. };
  168. SegmentFetcher.prototype.fetchFirstSegment = function(baseInterest)
  169. {
  170. var interest = new Interest(baseInterest);
  171. interest.setChildSelector(1);
  172. interest.setMustBeFresh(true);
  173. var thisSegmentFetcher = this;
  174. this.face.expressInterest
  175. (interest,
  176. function(originalInterest, data)
  177. { thisSegmentFetcher.onData(originalInterest, data); },
  178. function(interest) { thisSegmentFetcher.onTimeout(interest); });
  179. };
  180. SegmentFetcher.prototype.fetchNextSegment = function
  181. (originalInterest, dataName, segment)
  182. {
  183. // Start with the original Interest to preserve any special selectors.
  184. var interest = new Interest(originalInterest);
  185. // Changing a field clears the nonce so that the library will generate a new
  186. // one.
  187. interest.setMustBeFresh(false);
  188. interest.setName(dataName.getPrefix(-1).appendSegment(segment));
  189. var thisSegmentFetcher = this;
  190. this.face.expressInterest
  191. (interest, function(originalInterest, data)
  192. { thisSegmentFetcher.onData(originalInterest, data); },
  193. function(interest) { thisSegmentFetcher.onTimeout(interest); });
  194. };
  195. SegmentFetcher.prototype.onData = function(originalInterest, data)
  196. {
  197. if (!this.verifySegment(data)) {
  198. try {
  199. this.onError
  200. (SegmentFetcher.ErrorCode.SEGMENT_VERIFICATION_FAILED,
  201. "Segment verification failed");
  202. } catch (ex) {
  203. console.log("Error in onError: " + NdnCommon.getErrorWithStackTrace(ex));
  204. }
  205. return;
  206. }
  207. if (!SegmentFetcher.endsWithSegmentNumber(data.getName())) {
  208. // We don't expect a name without a segment number. Treat it as a bad packet.
  209. try {
  210. this.onError
  211. (SegmentFetcher.ErrorCode.DATA_HAS_NO_SEGMENT,
  212. "Got an unexpected packet without a segment number: " +
  213. data.getName().toUri());
  214. } catch (ex) {
  215. console.log("Error in onError: " + NdnCommon.getErrorWithStackTrace(ex));
  216. }
  217. }
  218. else {
  219. var currentSegment = 0;
  220. try {
  221. currentSegment = data.getName().get(-1).toSegment();
  222. }
  223. catch (ex) {
  224. try {
  225. this.onError
  226. (SegmentFetcher.ErrorCode.DATA_HAS_NO_SEGMENT,
  227. "Error decoding the name segment number " +
  228. data.getName().get(-1).toEscapedString() + ": " + ex);
  229. } catch (ex) {
  230. console.log("Error in onError: " + NdnCommon.getErrorWithStackTrace(ex));
  231. }
  232. return;
  233. }
  234. var expectedSegmentNumber = this.contentParts.length;
  235. if (currentSegment != expectedSegmentNumber)
  236. // Try again to get the expected segment. This also includes the case
  237. // where the first segment is not segment 0.
  238. this.fetchNextSegment
  239. (originalInterest, data.getName(), expectedSegmentNumber);
  240. else {
  241. // Save the content and check if we are finished.
  242. this.contentParts.push(data.getContent().buf());
  243. if (data.getMetaInfo().getFinalBlockId().getValue().size() > 0) {
  244. var finalSegmentNumber = 0;
  245. try {
  246. finalSegmentNumber = (data.getMetaInfo().getFinalBlockId().toSegment());
  247. }
  248. catch (ex) {
  249. try {
  250. this.onError
  251. (SegmentFetcher.ErrorCode.DATA_HAS_NO_SEGMENT,
  252. "Error decoding the FinalBlockId segment number " +
  253. data.getMetaInfo().getFinalBlockId().toEscapedString() +
  254. ": " + ex);
  255. } catch (ex) {
  256. console.log("Error in onError: " + NdnCommon.getErrorWithStackTrace(ex));
  257. }
  258. return;
  259. }
  260. if (currentSegment == finalSegmentNumber) {
  261. // We are finished.
  262. // Concatenate to get content.
  263. var content = Buffer.concat(this.contentParts);
  264. try {
  265. this.onComplete(new Blob(content, false));
  266. } catch (ex) {
  267. console.log("Error in onComplete: " + NdnCommon.getErrorWithStackTrace(ex));
  268. }
  269. return;
  270. }
  271. }
  272. // Fetch the next segment.
  273. this.fetchNextSegment
  274. (originalInterest, data.getName(), expectedSegmentNumber + 1);
  275. }
  276. }
  277. };
  278. SegmentFetcher.prototype.onTimeout = function(interest)
  279. {
  280. try {
  281. this.onError
  282. (SegmentFetcher.ErrorCode.INTEREST_TIMEOUT,
  283. "Time out for interest " + interest.getName().toUri());
  284. } catch (ex) {
  285. console.log("Error in onError: " + NdnCommon.getErrorWithStackTrace(ex));
  286. }
  287. };
  288. /**
  289. * Check if the last component in the name is a segment number.
  290. * @param {Name} name The name to check.
  291. * @returns {boolean} True if the name ends with a segment number, otherwise false.
  292. */
  293. SegmentFetcher.endsWithSegmentNumber = function(name)
  294. {
  295. return name.size() >= 1 &&
  296. name.get(-1).getValue().size() >= 1 &&
  297. name.get(-1).getValue().buf()[0] == 0;
  298. };