You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					413 lines
				
				9.5 KiB
			
		
		
			
		
	
	
					413 lines
				
				9.5 KiB
			| 
								 
											3 years ago
										 
									 | 
							
								// Load modules
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								var Dgram = require('dgram');
							 | 
						||
| 
								 | 
							
								var Dns = require('dns');
							 | 
						||
| 
								 | 
							
								var Hoek = require('hoek');
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								// Declare internals
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								var internals = {};
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								exports.time = function (options, callback) {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if (arguments.length !== 2) {
							 | 
						||
| 
								 | 
							
								        callback = arguments[0];
							 | 
						||
| 
								 | 
							
								        options = {};
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    var settings = Hoek.clone(options);
							 | 
						||
| 
								 | 
							
								    settings.host = settings.host || 'pool.ntp.org';
							 | 
						||
| 
								 | 
							
								    settings.port = settings.port || 123;
							 | 
						||
| 
								 | 
							
								    settings.resolveReference = settings.resolveReference || false;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    // Declare variables used by callback
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    var timeoutId = 0;
							 | 
						||
| 
								 | 
							
								    var sent = 0;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    // Ensure callback is only called once
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    var finish = function (err, result) {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        if (timeoutId) {
							 | 
						||
| 
								 | 
							
								            clearTimeout(timeoutId);
							 | 
						||
| 
								 | 
							
								            timeoutId = 0;
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        socket.removeAllListeners();
							 | 
						||
| 
								 | 
							
								        socket.once('error', internals.ignore);
							 | 
						||
| 
								 | 
							
								        socket.close();
							 | 
						||
| 
								 | 
							
								        return callback(err, result);
							 | 
						||
| 
								 | 
							
								    };
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    finish = Hoek.once(finish);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    // Create UDP socket
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    var socket = Dgram.createSocket('udp4');
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    socket.once('error', function (err) {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        return finish(err);
							 | 
						||
| 
								 | 
							
								    });
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    // Listen to incoming messages
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    socket.on('message', function (buffer, rinfo) {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        var received = Date.now();
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        var message = new internals.NtpMessage(buffer);
							 | 
						||
| 
								 | 
							
								        if (!message.isValid) {
							 | 
						||
| 
								 | 
							
								            return finish(new Error('Invalid server response'), message);
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        if (message.originateTimestamp !== sent) {
							 | 
						||
| 
								 | 
							
								            return finish(new Error('Wrong originate timestamp'), message);
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        // Timestamp Name          ID   When Generated
							 | 
						||
| 
								 | 
							
								        // ------------------------------------------------------------
							 | 
						||
| 
								 | 
							
								        // Originate Timestamp     T1   time request sent by client
							 | 
						||
| 
								 | 
							
								        // Receive Timestamp       T2   time request received by server
							 | 
						||
| 
								 | 
							
								        // Transmit Timestamp      T3   time reply sent by server
							 | 
						||
| 
								 | 
							
								        // Destination Timestamp   T4   time reply received by client
							 | 
						||
| 
								 | 
							
								        //
							 | 
						||
| 
								 | 
							
								        // The roundtrip delay d and system clock offset t are defined as:
							 | 
						||
| 
								 | 
							
								        //
							 | 
						||
| 
								 | 
							
								        // d = (T4 - T1) - (T3 - T2)     t = ((T2 - T1) + (T3 - T4)) / 2
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        var T1 = message.originateTimestamp;
							 | 
						||
| 
								 | 
							
								        var T2 = message.receiveTimestamp;
							 | 
						||
| 
								 | 
							
								        var T3 = message.transmitTimestamp;
							 | 
						||
| 
								 | 
							
								        var T4 = received;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        message.d = (T4 - T1) - (T3 - T2);
							 | 
						||
| 
								 | 
							
								        message.t = ((T2 - T1) + (T3 - T4)) / 2;
							 | 
						||
| 
								 | 
							
								        message.receivedLocally = received;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        if (!settings.resolveReference ||
							 | 
						||
| 
								 | 
							
								            message.stratum !== 'secondary') {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								            return finish(null, message);
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        // Resolve reference IP address
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        Dns.reverse(message.referenceId, function (err, domains) {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								            if (/* $lab:coverage:off$ */ !err /* $lab:coverage:on$ */) {
							 | 
						||
| 
								 | 
							
								                message.referenceHost = domains[0];
							 | 
						||
| 
								 | 
							
								            }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								            return finish(null, message);
							 | 
						||
| 
								 | 
							
								        });
							 | 
						||
| 
								 | 
							
								    });
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    // Set timeout
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if (settings.timeout) {
							 | 
						||
| 
								 | 
							
								        timeoutId = setTimeout(function () {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								            timeoutId = 0;
							 | 
						||
| 
								 | 
							
								            return finish(new Error('Timeout'));
							 | 
						||
| 
								 | 
							
								        }, settings.timeout);
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    // Construct NTP message
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    var message = new Buffer(48);
							 | 
						||
| 
								 | 
							
								    for (var i = 0; i < 48; i++) {                      // Zero message
							 | 
						||
| 
								 | 
							
								        message[i] = 0;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    message[0] = (0 << 6) + (4 << 3) + (3 << 0)         // Set version number to 4 and Mode to 3 (client)
							 | 
						||
| 
								 | 
							
								    sent = Date.now();
							 | 
						||
| 
								 | 
							
								    internals.fromMsecs(sent, message, 40);               // Set transmit timestamp (returns as originate)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    // Send NTP request
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    socket.send(message, 0, message.length, settings.port, settings.host, function (err, bytes) {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        if (err ||
							 | 
						||
| 
								 | 
							
								            bytes !== 48) {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								            return finish(err || new Error('Could not send entire message'));
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								    });
							 | 
						||
| 
								 | 
							
								};
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								internals.NtpMessage = function (buffer) {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    this.isValid = false;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    // Validate
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if (buffer.length !== 48) {
							 | 
						||
| 
								 | 
							
								        return;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    // Leap indicator
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    var li = (buffer[0] >> 6);
							 | 
						||
| 
								 | 
							
								    switch (li) {
							 | 
						||
| 
								 | 
							
								        case 0: this.leapIndicator = 'no-warning'; break;
							 | 
						||
| 
								 | 
							
								        case 1: this.leapIndicator = 'last-minute-61'; break;
							 | 
						||
| 
								 | 
							
								        case 2: this.leapIndicator = 'last-minute-59'; break;
							 | 
						||
| 
								 | 
							
								        case 3: this.leapIndicator = 'alarm'; break;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    // Version
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    var vn = ((buffer[0] & 0x38) >> 3);
							 | 
						||
| 
								 | 
							
								    this.version = vn;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    // Mode
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    var mode = (buffer[0] & 0x7);
							 | 
						||
| 
								 | 
							
								    switch (mode) {
							 | 
						||
| 
								 | 
							
								        case 1: this.mode = 'symmetric-active'; break;
							 | 
						||
| 
								 | 
							
								        case 2: this.mode = 'symmetric-passive'; break;
							 | 
						||
| 
								 | 
							
								        case 3: this.mode = 'client'; break;
							 | 
						||
| 
								 | 
							
								        case 4: this.mode = 'server'; break;
							 | 
						||
| 
								 | 
							
								        case 5: this.mode = 'broadcast'; break;
							 | 
						||
| 
								 | 
							
								        case 0:
							 | 
						||
| 
								 | 
							
								        case 6:
							 | 
						||
| 
								 | 
							
								        case 7: this.mode = 'reserved'; break;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    // Stratum
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    var stratum = buffer[1];
							 | 
						||
| 
								 | 
							
								    if (stratum === 0) {
							 | 
						||
| 
								 | 
							
								        this.stratum = 'death';
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    else if (stratum === 1) {
							 | 
						||
| 
								 | 
							
								        this.stratum = 'primary';
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    else if (stratum <= 15) {
							 | 
						||
| 
								 | 
							
								        this.stratum = 'secondary';
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    else {
							 | 
						||
| 
								 | 
							
								        this.stratum = 'reserved';
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    // Poll interval (msec)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    this.pollInterval = Math.round(Math.pow(2, buffer[2])) * 1000;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    // Precision (msecs)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    this.precision = Math.pow(2, buffer[3]) * 1000;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    // Root delay (msecs)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    var rootDelay = 256 * (256 * (256 * buffer[4] + buffer[5]) + buffer[6]) + buffer[7];
							 | 
						||
| 
								 | 
							
								    this.rootDelay = 1000 * (rootDelay / 0x10000);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    // Root dispersion (msecs)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    this.rootDispersion = ((buffer[8] << 8) + buffer[9] + ((buffer[10] << 8) + buffer[11]) / Math.pow(2, 16)) * 1000;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    // Reference identifier
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    this.referenceId = '';
							 | 
						||
| 
								 | 
							
								    switch (this.stratum) {
							 | 
						||
| 
								 | 
							
								        case 'death':
							 | 
						||
| 
								 | 
							
								        case 'primary':
							 | 
						||
| 
								 | 
							
								            this.referenceId = String.fromCharCode(buffer[12]) + String.fromCharCode(buffer[13]) + String.fromCharCode(buffer[14]) + String.fromCharCode(buffer[15]);
							 | 
						||
| 
								 | 
							
								            break;
							 | 
						||
| 
								 | 
							
								        case 'secondary':
							 | 
						||
| 
								 | 
							
								            this.referenceId = '' + buffer[12] + '.' + buffer[13] + '.' + buffer[14] + '.' + buffer[15];
							 | 
						||
| 
								 | 
							
								            break;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    // Reference timestamp
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    this.referenceTimestamp = internals.toMsecs(buffer, 16);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    // Originate timestamp
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    this.originateTimestamp = internals.toMsecs(buffer, 24);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    // Receive timestamp
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    this.receiveTimestamp = internals.toMsecs(buffer, 32);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    // Transmit timestamp
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    this.transmitTimestamp = internals.toMsecs(buffer, 40);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    // Validate
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if (this.version === 4 &&
							 | 
						||
| 
								 | 
							
								        this.stratum !== 'reserved' &&
							 | 
						||
| 
								 | 
							
								        this.mode === 'server' &&
							 | 
						||
| 
								 | 
							
								        this.originateTimestamp &&
							 | 
						||
| 
								 | 
							
								        this.receiveTimestamp &&
							 | 
						||
| 
								 | 
							
								        this.transmitTimestamp) {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        this.isValid = true;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    return this;
							 | 
						||
| 
								 | 
							
								};
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								internals.toMsecs = function (buffer, offset) {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    var seconds = 0;
							 | 
						||
| 
								 | 
							
								    var fraction = 0;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    for (var i = 0; i < 4; ++i) {
							 | 
						||
| 
								 | 
							
								        seconds = (seconds * 256) + buffer[offset + i];
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    for (i = 4; i < 8; ++i) {
							 | 
						||
| 
								 | 
							
								        fraction = (fraction * 256) + buffer[offset + i];
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    return ((seconds - 2208988800 + (fraction / Math.pow(2, 32))) * 1000);
							 | 
						||
| 
								 | 
							
								};
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								internals.fromMsecs = function (ts, buffer, offset) {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    var seconds = Math.floor(ts / 1000) + 2208988800;
							 | 
						||
| 
								 | 
							
								    var fraction = Math.round((ts % 1000) / 1000 * Math.pow(2, 32));
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    buffer[offset + 0] = (seconds & 0xFF000000) >> 24;
							 | 
						||
| 
								 | 
							
								    buffer[offset + 1] = (seconds & 0x00FF0000) >> 16;
							 | 
						||
| 
								 | 
							
								    buffer[offset + 2] = (seconds & 0x0000FF00) >> 8;
							 | 
						||
| 
								 | 
							
								    buffer[offset + 3] = (seconds & 0x000000FF);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    buffer[offset + 4] = (fraction & 0xFF000000) >> 24;
							 | 
						||
| 
								 | 
							
								    buffer[offset + 5] = (fraction & 0x00FF0000) >> 16;
							 | 
						||
| 
								 | 
							
								    buffer[offset + 6] = (fraction & 0x0000FF00) >> 8;
							 | 
						||
| 
								 | 
							
								    buffer[offset + 7] = (fraction & 0x000000FF);
							 | 
						||
| 
								 | 
							
								};
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								// Offset singleton
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								internals.last = {
							 | 
						||
| 
								 | 
							
								    offset: 0,
							 | 
						||
| 
								 | 
							
								    expires: 0,
							 | 
						||
| 
								 | 
							
								    host: '',
							 | 
						||
| 
								 | 
							
								    port: 0
							 | 
						||
| 
								 | 
							
								};
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								exports.offset = function (options, callback) {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if (arguments.length !== 2) {
							 | 
						||
| 
								 | 
							
								        callback = arguments[0];
							 | 
						||
| 
								 | 
							
								        options = {};
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    var now = Date.now();
							 | 
						||
| 
								 | 
							
								    var clockSyncRefresh = options.clockSyncRefresh || 24 * 60 * 60 * 1000;                    // Daily
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if (internals.last.offset &&
							 | 
						||
| 
								 | 
							
								        internals.last.host === options.host &&
							 | 
						||
| 
								 | 
							
								        internals.last.port === options.port &&
							 | 
						||
| 
								 | 
							
								        now < internals.last.expires) {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        process.nextTick(function () {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								            callback(null, internals.last.offset);
							 | 
						||
| 
								 | 
							
								        });
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        return;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    exports.time(options, function (err, time) {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        if (err) {
							 | 
						||
| 
								 | 
							
								            return callback(err, 0);
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        internals.last = {
							 | 
						||
| 
								 | 
							
								            offset: Math.round(time.t),
							 | 
						||
| 
								 | 
							
								            expires: now + clockSyncRefresh,
							 | 
						||
| 
								 | 
							
								            host: options.host,
							 | 
						||
| 
								 | 
							
								            port: options.port
							 | 
						||
| 
								 | 
							
								        };
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        return callback(null, internals.last.offset);
							 | 
						||
| 
								 | 
							
								    });
							 | 
						||
| 
								 | 
							
								};
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								// Now singleton
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								internals.now = {
							 | 
						||
| 
								 | 
							
								    intervalId: 0
							 | 
						||
| 
								 | 
							
								};
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								exports.start = function (options, callback) {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if (arguments.length !== 2) {
							 | 
						||
| 
								 | 
							
								        callback = arguments[0];
							 | 
						||
| 
								 | 
							
								        options = {};
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if (internals.now.intervalId) {
							 | 
						||
| 
								 | 
							
								        process.nextTick(function () {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								            callback();
							 | 
						||
| 
								 | 
							
								        });
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        return;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    exports.offset(options, function (err, offset) {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        internals.now.intervalId = setInterval(function () {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								            exports.offset(options, function () { });
							 | 
						||
| 
								 | 
							
								        }, options.clockSyncRefresh || 24 * 60 * 60 * 1000);                                // Daily
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        return callback();
							 | 
						||
| 
								 | 
							
								    });
							 | 
						||
| 
								 | 
							
								};
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								exports.stop = function () {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if (!internals.now.intervalId) {
							 | 
						||
| 
								 | 
							
								        return;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    clearInterval(internals.now.intervalId);
							 | 
						||
| 
								 | 
							
								    internals.now.intervalId = 0;
							 | 
						||
| 
								 | 
							
								};
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								exports.isLive = function () {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    return !!internals.now.intervalId;
							 | 
						||
| 
								 | 
							
								};
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								exports.now = function () {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    var now = Date.now();
							 | 
						||
| 
								 | 
							
								    if (!exports.isLive() ||
							 | 
						||
| 
								 | 
							
								        now >= internals.last.expires) {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        return now;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    return now + internals.last.offset;
							 | 
						||
| 
								 | 
							
								};
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								internals.ignore = function () {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								};
							 |