UNPKG

ndn-js

Version:

A JavaScript client library for Named Data Networking

364 lines (323 loc) 14 kB
/** * Copyright (C) 2015-2019 Regents of the University of California. * @author: Jeff Thompson <jefft0@remap.ucla.edu> * @author: From ndn-group-encrypt src/schedule https://github.com/named-data/ndn-group-encrypt * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. * A copy of the GNU Lesser General Public License is in the file COPYING. */ /** @ignore */ var Interval = require('./interval.js').Interval; /** @ignore */ var RepetitiveInterval = require('./repetitive-interval.js').RepetitiveInterval; /** @ignore */ var Tlv = require('../encoding/tlv/tlv.js').Tlv; /** @ignore */ var TlvEncoder = require('../encoding/tlv/tlv-encoder.js').TlvEncoder; /** @ignore */ var TlvDecoder = require('../encoding/tlv/tlv-decoder.js').TlvDecoder; /** @ignore */ var Blob = require('../util/blob.js').Blob; /** * Schedule is used to manage the times when a member can access data using two * sets of RepetitiveInterval as follows. whiteIntervalList is an ordered * set for the times a member is allowed to access to data, and * blackIntervalList is for the times a member is not allowed. * Create a Schedule with one of these forms: * Schedule() A Schedule with empty whiteIntervalList and blackIntervalList. * Schedule(schedule). A copy of the given schedule. * @note This class is an experimental feature. The API may change. * @constructor */ var Schedule = function Schedule(value) { if (typeof value === 'object' && value instanceof Schedule) { // Make a copy. var schedule = value; // RepetitiveInterval is immutable, so we don't need to make a deep copy. this.whiteIntervalList_ = schedule.whiteIntervalList_.slice(0); this.blackIntervalList_ = schedule.blackIntervalList_.slice(0); } else { // The default constructor. this.whiteIntervalList_ = []; this.blackIntervalList_ = []; } }; exports.Schedule = Schedule; /** * Add the repetitiveInterval to the whiteIntervalList. * @param {RepetitiveInterval} repetitiveInterval The RepetitiveInterval to add. * If the list already contains the same RepetitiveInterval, this does nothing. * @return {Schedule} This Schedule so you can chain calls to add. */ Schedule.prototype.addWhiteInterval = function(repetitiveInterval) { // RepetitiveInterval is immutable, so we don't need to make a copy. Schedule.sortedSetAdd_(this.whiteIntervalList_, repetitiveInterval); return this; }; /** * Add the repetitiveInterval to the blackIntervalList. * @param {RepetitiveInterval} repetitiveInterval The RepetitiveInterval to add. * If the list already contains the same RepetitiveInterval, this does nothing. * @return {Schedule} This Schedule so you can chain calls to add. */ Schedule.prototype.addBlackInterval = function(repetitiveInterval) { // RepetitiveInterval is immutable, so we don't need to make a copy. Schedule.sortedSetAdd_(this.blackIntervalList_, repetitiveInterval); return this; }; /** * Get the interval that covers the time stamp. This iterates over the two * repetitive interval sets and find the shortest interval that allows a group * member to access the data. If there is no interval covering the time stamp, * this returns false for isPositive and a negative interval. * @param {number} timeStamp The time stamp as milliseconds since Jan 1, 1970 UTC. * @return {object} An associative array with fields * (isPositive, interval) where * isPositive is true if the returned interval is positive or false if negative, * and interval is the Interval covering the time stamp, or a negative interval * if not found. */ Schedule.prototype.getCoveringInterval = function(timeStamp) { var blackPositiveResult = new Interval(true); var whitePositiveResult = new Interval(true); var blackNegativeResult = new Interval(); var whiteNegativeResult = new Interval(); // Get the black result. Schedule.calculateIntervalResult_ (this.blackIntervalList_, timeStamp, blackPositiveResult, blackNegativeResult); // If the black positive result is not empty, then isPositive must be false. if (!blackPositiveResult.isEmpty()) return { isPositive: false, interval: blackPositiveResult }; // Get the whiteResult. Schedule.calculateIntervalResult_ (this.whiteIntervalList_, timeStamp, whitePositiveResult, whiteNegativeResult); if (whitePositiveResult.isEmpty() && !whiteNegativeResult.isValid()) { // There is no white interval covering the time stamp. // Return false and a 24-hour interval. var timeStampDateOnly = RepetitiveInterval.toDateOnlyMilliseconds_(timeStamp); return { isPositive: false, interval: new Interval (timeStampDateOnly, timeStampDateOnly + RepetitiveInterval.MILLISECONDS_IN_DAY) }; } if (!whitePositiveResult.isEmpty()) { // There is white interval covering the time stamp. // Return true and calculate the intersection. if (blackNegativeResult.isValid()) return { isPositive: true, interval: whitePositiveResult.intersectWith(blackNegativeResult) }; else return { isPositive: true, interval: whitePositiveResult }; } else // There is no white interval covering the time stamp. // Return false. return { isPositive: false, interval: whiteNegativeResult }; }; /** * Encode this Schedule. * @return {Blob} The encoded buffer. */ Schedule.prototype.wireEncode = function() { // For now, don't use WireFormat and hardcode to use TLV since the encoding // doesn't go out over the wire, only into the local SQL database. var encoder = new TlvEncoder(256); var saveLength = encoder.getLength(); // Encode backwards. // Encode the blackIntervalList. var saveLengthForList = encoder.getLength(); for (var i = this.blackIntervalList_.length - 1; i >= 0; i--) Schedule.encodeRepetitiveInterval_(this.blackIntervalList_[i], encoder); encoder.writeTypeAndLength (Tlv.Encrypt_BlackIntervalList, encoder.getLength() - saveLengthForList); // Encode the whiteIntervalList. saveLengthForList = encoder.getLength(); for (var i = this.whiteIntervalList_.length - 1; i >= 0; i--) Schedule.encodeRepetitiveInterval_(this.whiteIntervalList_[i], encoder); encoder.writeTypeAndLength (Tlv.Encrypt_WhiteIntervalList, encoder.getLength() - saveLengthForList); encoder.writeTypeAndLength (Tlv.Encrypt_Schedule, encoder.getLength() - saveLength); return new Blob(encoder.getOutput(), false); }; /** * Decode the input and update this Schedule object. * @param {Blob|Buffer} input The input buffer to decode. For Buffer, this reads * from position() to limit(), but does not change the position. * @throws DecodingException For invalid encoding. */ Schedule.prototype.wireDecode = function(input) { // If input is a blob, get its buf(). var decodeBuffer = typeof input === 'object' && input instanceof Blob ? input.buf() : input; // For now, don't use WireFormat and hardcode to use TLV since the encoding // doesn't go out over the wire, only into the local SQL database. var decoder = new TlvDecoder(decodeBuffer); var endOffset = decoder.readNestedTlvsStart(Tlv.Encrypt_Schedule); // Decode the whiteIntervalList. this.whiteIntervalList_ = []; var listEndOffset = decoder.readNestedTlvsStart(Tlv.Encrypt_WhiteIntervalList); while (decoder.getOffset() < listEndOffset) Schedule.sortedSetAdd_ (this.whiteIntervalList_, Schedule.decodeRepetitiveInterval_(decoder)); decoder.finishNestedTlvs(listEndOffset); // Decode the blackIntervalList. this.blackIntervalList_ = []; listEndOffset = decoder.readNestedTlvsStart(Tlv.Encrypt_BlackIntervalList); while (decoder.getOffset() < listEndOffset) Schedule.sortedSetAdd_ (this.blackIntervalList_, Schedule.decodeRepetitiveInterval_(decoder)); decoder.finishNestedTlvs(listEndOffset); decoder.finishNestedTlvs(endOffset); }; /** * Insert element into the list, sorted using element.compare(). If it is a * duplicate of an existing list element, don't add it. */ Schedule.sortedSetAdd_ = function(list, element) { // Find the index of the first element where it is not less than element. var i = 0; while (i < list.length) { var comparison = list[i].compare(element); if (comparison == 0) // Don't add a duplicate. return; if (!(comparison < 0)) break; ++i; } list.splice(i, 0, element); }; /** * Encode the RepetitiveInterval as NDN-TLV to the encoder. * @param {RepetitiveInterval} repetitiveInterval The RepetitiveInterval to encode. * @param {TlvEncoder} encoder The TlvEncoder to receive the encoding. */ Schedule.encodeRepetitiveInterval_ = function(repetitiveInterval, encoder) { var saveLength = encoder.getLength(); // Encode backwards. // The RepeatUnit enum has the same values as the encoding. encoder.writeNonNegativeIntegerTlv (Tlv.Encrypt_RepeatUnit, repetitiveInterval.getRepeatUnit()); encoder.writeNonNegativeIntegerTlv (Tlv.Encrypt_NRepeats, repetitiveInterval.getNRepeats()); encoder.writeNonNegativeIntegerTlv (Tlv.Encrypt_IntervalEndHour, repetitiveInterval.getIntervalEndHour()); encoder.writeNonNegativeIntegerTlv (Tlv.Encrypt_IntervalStartHour, repetitiveInterval.getIntervalStartHour()); // Use Blob to convert the string to UTF8 encoding. encoder.writeBlobTlv(Tlv.Encrypt_EndDate, new Blob(Schedule.toIsoString(repetitiveInterval.getEndDate())).buf()); encoder.writeBlobTlv(Tlv.Encrypt_StartDate, new Blob(Schedule.toIsoString(repetitiveInterval.getStartDate())).buf()); encoder.writeTypeAndLength (Tlv.Encrypt_RepetitiveInterval, encoder.getLength() - saveLength); }; /** * Decode the input as an NDN-TLV RepetitiveInterval. * @param {TlvDecoder} decoder The decoder with the input to decode. * @return {RepetitiveInterval} A new RepetitiveInterval with the decoded result. */ Schedule.decodeRepetitiveInterval_ = function(decoder) { var endOffset = decoder.readNestedTlvsStart(Tlv.Encrypt_RepetitiveInterval); // Use Blob to convert UTF8 to a string. var startDate = Schedule.fromIsoString (new Blob(decoder.readBlobTlv(Tlv.Encrypt_StartDate), true).toString()); var endDate = Schedule.fromIsoString (new Blob(decoder.readBlobTlv(Tlv.Encrypt_EndDate), true).toString()); var startHour = decoder.readNonNegativeIntegerTlv(Tlv.Encrypt_IntervalStartHour); var endHour = decoder.readNonNegativeIntegerTlv(Tlv.Encrypt_IntervalEndHour); var nRepeats = decoder.readNonNegativeIntegerTlv(Tlv.Encrypt_NRepeats); // The RepeatUnit enum has the same values as the encoding. var repeatUnit = decoder.readNonNegativeIntegerTlv(Tlv.Encrypt_RepeatUnit); decoder.finishNestedTlvs(endOffset); return new RepetitiveInterval (startDate, endDate, startHour, endHour, nRepeats, repeatUnit); }; /** * A helper function to calculate black interval results or white interval * results. * @param {Array} list The set of RepetitiveInterval, which can be the white * list or the black list. * @param {number} timeStamp The time stamp as milliseconds since Jan 1, 1970 UTC. * @param {Interval} positiveResult The positive result which is updated. * @param {Interval} negativeResult The negative result which is updated. */ Schedule.calculateIntervalResult_ = function (list, timeStamp, positiveResult, negativeResult) { for (var i = 0; i < list.length; ++i) { var element = list[i]; var result = element.getInterval(timeStamp); var tempInterval = result.interval; if (result.isPositive == true) positiveResult.unionWith(tempInterval); else { if (!negativeResult.isValid()) negativeResult.set(tempInterval); else negativeResult.intersectWith(tempInterval); } } }; /** * Convert a UNIX timestamp to ISO time representation with the "T" in the middle. * @param {number} msSince1970 Timestamp as milliseconds since Jan 1, 1970 UTC. * @return {string} The string representation. */ Schedule.toIsoString = function(msSince1970) { var utcTime = new Date(Math.round(msSince1970)); return utcTime.getUTCFullYear() + Schedule.to2DigitString(utcTime.getUTCMonth() + 1) + Schedule.to2DigitString(utcTime.getUTCDate()) + "T" + Schedule.to2DigitString(utcTime.getUTCHours()) + Schedule.to2DigitString(utcTime.getUTCMinutes()) + Schedule.to2DigitString(utcTime.getUTCSeconds()); }; /** * A private method to zero pad an integer to 2 digits. * @param {number} x The number to pad. Assume it is a non-negative integer. * @return {string} The padded string. */ Schedule.to2DigitString = function(x) { var result = x.toString(); return result.length === 1 ? "0" + result : result; }; /** * Convert an ISO time representation with the "T" in the middle to a UNIX * timestamp. * @param {string} timeString The ISO time representation. * @return {number} The timestamp as milliseconds since Jan 1, 1970 UTC. */ Schedule.fromIsoString = function(timeString) { if (timeString.length != 15 || timeString.substr(8, 1) != 'T') throw new Error("fromIsoString: Format is not the expected yyyymmddThhmmss"); return Date.UTC (parseInt(timeString.substr(0, 4)), parseInt(timeString.substr(4, 2) - 1), parseInt(timeString.substr(6, 2)), parseInt(timeString.substr(9, 2)), parseInt(timeString.substr(11, 2)), parseInt(timeString.substr(13, 2))); };