Source: encrypt/schedule.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-group-encrypt src/schedule https://github.com/named-data/ndn-group-encrypt
  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 Interval = require('./interval.js').Interval; /** @ignore */
  22. var RepetitiveInterval = require('./repetitive-interval.js').RepetitiveInterval; /** @ignore */
  23. var Tlv = require('../encoding/tlv/tlv.js').Tlv; /** @ignore */
  24. var TlvEncoder = require('../encoding/tlv/tlv-encoder.js').TlvEncoder; /** @ignore */
  25. var TlvDecoder = require('../encoding/tlv/tlv-decoder.js').TlvDecoder; /** @ignore */
  26. var Blob = require('../util/blob.js').Blob;
  27. /**
  28. * Schedule is used to manage the times when a member can access data using two
  29. * sets of RepetitiveInterval as follows. whiteIntervalList is an ordered
  30. * set for the times a member is allowed to access to data, and
  31. * blackIntervalList is for the times a member is not allowed.
  32. * Create a Schedule with one of these forms:
  33. * Schedule() A Schedule with empty whiteIntervalList and blackIntervalList.
  34. * Schedule(schedule). A copy of the given schedule.
  35. * @note This class is an experimental feature. The API may change.
  36. * @constructor
  37. */
  38. var Schedule = function Schedule(value)
  39. {
  40. if (typeof value === 'object' && value instanceof Schedule) {
  41. // Make a copy.
  42. var schedule = value;
  43. // RepetitiveInterval is immutable, so we don't need to make a deep copy.
  44. this.whiteIntervalList_ = schedule.whiteIntervalList_.slice(0);
  45. this.blackIntervalList_ = schedule.blackIntervalList_.slice(0);
  46. }
  47. else {
  48. // The default constructor.
  49. this.whiteIntervalList_ = [];
  50. this.blackIntervalList_ = [];
  51. }
  52. };
  53. exports.Schedule = Schedule;
  54. /**
  55. * Add the repetitiveInterval to the whiteIntervalList.
  56. * @param {RepetitiveInterval} repetitiveInterval The RepetitiveInterval to add.
  57. * If the list already contains the same RepetitiveInterval, this does nothing.
  58. * @return {Schedule} This Schedule so you can chain calls to add.
  59. */
  60. Schedule.prototype.addWhiteInterval = function(repetitiveInterval)
  61. {
  62. // RepetitiveInterval is immutable, so we don't need to make a copy.
  63. Schedule.sortedSetAdd_(this.whiteIntervalList_, repetitiveInterval);
  64. return this;
  65. };
  66. /**
  67. * Add the repetitiveInterval to the blackIntervalList.
  68. * @param {RepetitiveInterval} repetitiveInterval The RepetitiveInterval to add.
  69. * If the list already contains the same RepetitiveInterval, this does nothing.
  70. * @return {Schedule} This Schedule so you can chain calls to add.
  71. */
  72. Schedule.prototype.addBlackInterval = function(repetitiveInterval)
  73. {
  74. // RepetitiveInterval is immutable, so we don't need to make a copy.
  75. Schedule.sortedSetAdd_(this.blackIntervalList_, repetitiveInterval);
  76. return this;
  77. };
  78. /**
  79. * Get the interval that covers the time stamp. This iterates over the two
  80. * repetitive interval sets and find the shortest interval that allows a group
  81. * member to access the data. If there is no interval covering the time stamp,
  82. * this returns false for isPositive and a negative interval.
  83. * @param {number} timeStamp The time stamp as milliseconds since Jan 1, 1970 UTC.
  84. * @return {object} An associative array with fields
  85. * (isPositive, interval) where
  86. * isPositive is true if the returned interval is positive or false if negative,
  87. * and interval is the Interval covering the time stamp, or a negative interval
  88. * if not found.
  89. */
  90. Schedule.prototype.getCoveringInterval = function(timeStamp)
  91. {
  92. var blackPositiveResult = new Interval(true);
  93. var whitePositiveResult = new Interval(true);
  94. var blackNegativeResult = new Interval();
  95. var whiteNegativeResult = new Interval();
  96. // Get the black result.
  97. Schedule.calculateIntervalResult_
  98. (this.blackIntervalList_, timeStamp, blackPositiveResult, blackNegativeResult);
  99. // If the black positive result is not empty, then isPositive must be false.
  100. if (!blackPositiveResult.isEmpty())
  101. return { isPositive: false, interval: blackPositiveResult };
  102. // Get the whiteResult.
  103. Schedule.calculateIntervalResult_
  104. (this.whiteIntervalList_, timeStamp, whitePositiveResult, whiteNegativeResult);
  105. if (whitePositiveResult.isEmpty() && !whiteNegativeResult.isValid()) {
  106. // There is no white interval covering the time stamp.
  107. // Return false and a 24-hour interval.
  108. var timeStampDateOnly =
  109. RepetitiveInterval.toDateOnlyMilliseconds_(timeStamp);
  110. return { isPositive: false,
  111. interval: new Interval
  112. (timeStampDateOnly,
  113. timeStampDateOnly + RepetitiveInterval.MILLISECONDS_IN_DAY) };
  114. }
  115. if (!whitePositiveResult.isEmpty()) {
  116. // There is white interval covering the time stamp.
  117. // Return true and calculate the intersection.
  118. if (blackNegativeResult.isValid())
  119. return { isPositive: true,
  120. interval: whitePositiveResult.intersectWith(blackNegativeResult) };
  121. else
  122. return { isPositive: true, interval: whitePositiveResult };
  123. }
  124. else
  125. // There is no white interval covering the time stamp.
  126. // Return false.
  127. return { isPositive: false, interval: whiteNegativeResult };
  128. };
  129. /**
  130. * Encode this Schedule.
  131. * @return {Blob} The encoded buffer.
  132. */
  133. Schedule.prototype.wireEncode = function()
  134. {
  135. // For now, don't use WireFormat and hardcode to use TLV since the encoding
  136. // doesn't go out over the wire, only into the local SQL database.
  137. var encoder = new TlvEncoder(256);
  138. var saveLength = encoder.getLength();
  139. // Encode backwards.
  140. // Encode the blackIntervalList.
  141. var saveLengthForList = encoder.getLength();
  142. for (var i = this.blackIntervalList_.length - 1; i >= 0; i--)
  143. Schedule.encodeRepetitiveInterval_(this.blackIntervalList_[i], encoder);
  144. encoder.writeTypeAndLength
  145. (Tlv.Encrypt_BlackIntervalList, encoder.getLength() - saveLengthForList);
  146. // Encode the whiteIntervalList.
  147. saveLengthForList = encoder.getLength();
  148. for (var i = this.whiteIntervalList_.length - 1; i >= 0; i--)
  149. Schedule.encodeRepetitiveInterval_(this.whiteIntervalList_[i], encoder);
  150. encoder.writeTypeAndLength
  151. (Tlv.Encrypt_WhiteIntervalList, encoder.getLength() - saveLengthForList);
  152. encoder.writeTypeAndLength
  153. (Tlv.Encrypt_Schedule, encoder.getLength() - saveLength);
  154. return new Blob(encoder.getOutput(), false);
  155. };
  156. /**
  157. * Decode the input and update this Schedule object.
  158. * @param {Blob|Buffer} input The input buffer to decode. For Buffer, this reads
  159. * from position() to limit(), but does not change the position.
  160. * @throws DecodingException For invalid encoding.
  161. */
  162. Schedule.prototype.wireDecode = function(input)
  163. {
  164. // If input is a blob, get its buf().
  165. var decodeBuffer = typeof input === 'object' && input instanceof Blob ?
  166. input.buf() : input;
  167. // For now, don't use WireFormat and hardcode to use TLV since the encoding
  168. // doesn't go out over the wire, only into the local SQL database.
  169. var decoder = new TlvDecoder(decodeBuffer);
  170. var endOffset = decoder.readNestedTlvsStart(Tlv.Encrypt_Schedule);
  171. // Decode the whiteIntervalList.
  172. this.whiteIntervalList_ = [];
  173. var listEndOffset = decoder.readNestedTlvsStart(Tlv.Encrypt_WhiteIntervalList);
  174. while (decoder.getOffset() < listEndOffset)
  175. Schedule.sortedSetAdd_
  176. (this.whiteIntervalList_, Schedule.decodeRepetitiveInterval_(decoder));
  177. decoder.finishNestedTlvs(listEndOffset);
  178. // Decode the blackIntervalList.
  179. this.blackIntervalList_ = [];
  180. listEndOffset = decoder.readNestedTlvsStart(Tlv.Encrypt_BlackIntervalList);
  181. while (decoder.getOffset() < listEndOffset)
  182. Schedule.sortedSetAdd_
  183. (this.blackIntervalList_, Schedule.decodeRepetitiveInterval_(decoder));
  184. decoder.finishNestedTlvs(listEndOffset);
  185. decoder.finishNestedTlvs(endOffset);
  186. };
  187. /**
  188. * Insert element into the list, sorted using element.compare(). If it is a
  189. * duplicate of an existing list element, don't add it.
  190. */
  191. Schedule.sortedSetAdd_ = function(list, element)
  192. {
  193. // Find the index of the first element where it is not less than element.
  194. var i = 0;
  195. while (i < list.length) {
  196. var comparison = list[i].compare(element);
  197. if (comparison == 0)
  198. // Don't add a duplicate.
  199. return;
  200. if (!(comparison < 0))
  201. break;
  202. ++i;
  203. }
  204. list.splice(i, 0, element);
  205. };
  206. /**
  207. * Encode the RepetitiveInterval as NDN-TLV to the encoder.
  208. * @param {RepetitiveInterval} repetitiveInterval The RepetitiveInterval to encode.
  209. * @param {TlvEncoder} encoder The TlvEncoder to receive the encoding.
  210. */
  211. Schedule.encodeRepetitiveInterval_ = function(repetitiveInterval, encoder)
  212. {
  213. var saveLength = encoder.getLength();
  214. // Encode backwards.
  215. // The RepeatUnit enum has the same values as the encoding.
  216. encoder.writeNonNegativeIntegerTlv
  217. (Tlv.Encrypt_RepeatUnit, repetitiveInterval.getRepeatUnit());
  218. encoder.writeNonNegativeIntegerTlv
  219. (Tlv.Encrypt_NRepeats, repetitiveInterval.getNRepeats());
  220. encoder.writeNonNegativeIntegerTlv
  221. (Tlv.Encrypt_IntervalEndHour, repetitiveInterval.getIntervalEndHour());
  222. encoder.writeNonNegativeIntegerTlv
  223. (Tlv.Encrypt_IntervalStartHour, repetitiveInterval.getIntervalStartHour());
  224. // Use Blob to convert the string to UTF8 encoding.
  225. encoder.writeBlobTlv(Tlv.Encrypt_EndDate,
  226. new Blob(Schedule.toIsoString(repetitiveInterval.getEndDate())).buf());
  227. encoder.writeBlobTlv(Tlv.Encrypt_StartDate,
  228. new Blob(Schedule.toIsoString(repetitiveInterval.getStartDate())).buf());
  229. encoder.writeTypeAndLength
  230. (Tlv.Encrypt_RepetitiveInterval, encoder.getLength() - saveLength);
  231. };
  232. /**
  233. * Decode the input as an NDN-TLV RepetitiveInterval.
  234. * @param {TlvDecoder} decoder The decoder with the input to decode.
  235. * @return {RepetitiveInterval} A new RepetitiveInterval with the decoded result.
  236. */
  237. Schedule.decodeRepetitiveInterval_ = function(decoder)
  238. {
  239. var endOffset = decoder.readNestedTlvsStart(Tlv.Encrypt_RepetitiveInterval);
  240. // Use Blob to convert UTF8 to a string.
  241. var startDate = Schedule.fromIsoString
  242. (new Blob(decoder.readBlobTlv(Tlv.Encrypt_StartDate), true).toString());
  243. var endDate = Schedule.fromIsoString
  244. (new Blob(decoder.readBlobTlv(Tlv.Encrypt_EndDate), true).toString());
  245. var startHour = decoder.readNonNegativeIntegerTlv(Tlv.Encrypt_IntervalStartHour);
  246. var endHour = decoder.readNonNegativeIntegerTlv(Tlv.Encrypt_IntervalEndHour);
  247. var nRepeats = decoder.readNonNegativeIntegerTlv(Tlv.Encrypt_NRepeats);
  248. // The RepeatUnit enum has the same values as the encoding.
  249. var repeatUnit = decoder.readNonNegativeIntegerTlv(Tlv.Encrypt_RepeatUnit);
  250. decoder.finishNestedTlvs(endOffset);
  251. return new RepetitiveInterval
  252. (startDate, endDate, startHour, endHour, nRepeats, repeatUnit);
  253. };
  254. /**
  255. * A helper function to calculate black interval results or white interval
  256. * results.
  257. * @param {Array} list The set of RepetitiveInterval, which can be the white
  258. * list or the black list.
  259. * @param {number} timeStamp The time stamp as milliseconds since Jan 1, 1970 UTC.
  260. * @param {Interval} positiveResult The positive result which is updated.
  261. * @param {Interval} negativeResult The negative result which is updated.
  262. */
  263. Schedule.calculateIntervalResult_ = function
  264. (list, timeStamp, positiveResult, negativeResult)
  265. {
  266. for (var i = 0; i < list.length; ++i) {
  267. var element = list[i];
  268. var result = element.getInterval(timeStamp);
  269. var tempInterval = result.interval;
  270. if (result.isPositive == true)
  271. positiveResult.unionWith(tempInterval);
  272. else {
  273. if (!negativeResult.isValid())
  274. negativeResult.set(tempInterval);
  275. else
  276. negativeResult.intersectWith(tempInterval);
  277. }
  278. }
  279. };
  280. /**
  281. * Convert a UNIX timestamp to ISO time representation with the "T" in the middle.
  282. * @param {number} msSince1970 Timestamp as milliseconds since Jan 1, 1970 UTC.
  283. * @return {string} The string representation.
  284. */
  285. Schedule.toIsoString = function(msSince1970)
  286. {
  287. var utcTime = new Date(Math.round(msSince1970));
  288. return utcTime.getUTCFullYear() +
  289. Schedule.to2DigitString(utcTime.getUTCMonth() + 1) +
  290. Schedule.to2DigitString(utcTime.getUTCDate()) +
  291. "T" +
  292. Schedule.to2DigitString(utcTime.getUTCHours()) +
  293. Schedule.to2DigitString(utcTime.getUTCMinutes()) +
  294. Schedule.to2DigitString(utcTime.getUTCSeconds());
  295. };
  296. /**
  297. * A private method to zero pad an integer to 2 digits.
  298. * @param {number} x The number to pad. Assume it is a non-negative integer.
  299. * @return {string} The padded string.
  300. */
  301. Schedule.to2DigitString = function(x)
  302. {
  303. var result = x.toString();
  304. return result.length === 1 ? "0" + result : result;
  305. };
  306. /**
  307. * Convert an ISO time representation with the "T" in the middle to a UNIX
  308. * timestamp.
  309. * @param {string} timeString The ISO time representation.
  310. * @return {number} The timestamp as milliseconds since Jan 1, 1970 UTC.
  311. */
  312. Schedule.fromIsoString = function(timeString)
  313. {
  314. if (timeString.length != 15 || timeString.substr(8, 1) != 'T')
  315. throw new Error("fromIsoString: Format is not the expected yyyymmddThhmmss");
  316. return Date.UTC
  317. (parseInt(timeString.substr(0, 4)),
  318. parseInt(timeString.substr(4, 2) - 1),
  319. parseInt(timeString.substr(6, 2)),
  320. parseInt(timeString.substr(9, 2)),
  321. parseInt(timeString.substr(11, 2)),
  322. parseInt(timeString.substr(13, 2)));
  323. };