pax_global_header00006660000000000000000000000064130177354750014526gustar00rootroot0000000000000052 comment=c92f322dc1b04c958dc9e5da67e6e6947df407bb sntp-2.0.2/000077500000000000000000000000001301773547500125135ustar00rootroot00000000000000sntp-2.0.2/.gitignore000077500000000000000000000002621301773547500145060ustar00rootroot00000000000000.idea *.iml npm-debug.log dump.rdb node_modules results.tap results.xml npm-shrinkwrap.json config.json .DS_Store */.DS_Store */*/.DS_Store ._* */._* */*/._* coverage.* lib-cov sntp-2.0.2/.npmignore000077500000000000000000000000261301773547500145130ustar00rootroot00000000000000* !lib/** !.npmignore sntp-2.0.2/.travis.yml000077500000000000000000000001151301773547500146240ustar00rootroot00000000000000language: node_js node_js: - "4" - "6" - "7" - "node" sudo: false sntp-2.0.2/LICENSE000077500000000000000000000031751301773547500135310ustar00rootroot00000000000000Copyright (c) 2012-2016, Eran Hammer and Project contributors All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * The names of any contributors may not be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * * The complete list of contributors can be found at: https://github.com/hueniverse/sntp/graphs/contributors sntp-2.0.2/README.md000077500000000000000000000035521301773547500140020ustar00rootroot00000000000000# sntp An SNTP v4 client (RFC4330) for node. Simpy connects to the NTP or SNTP server requested and returns the server time along with the roundtrip duration and clock offset. To adjust the local time to the NTP time, add the returned `t` offset to the local time. [![Build Status](https://secure.travis-ci.org/hueniverse/sntp.png)](http://travis-ci.org/hueniverse/sntp) # Usage ```javascript var Sntp = require('sntp'); // All options are optional var options = { host: 'nist1-sj.ustiming.org', // Defaults to pool.ntp.org port: 123, // Defaults to 123 (NTP) resolveReference: true, // Default to false (not resolving) timeout: 1000 // Defaults to zero (no timeout) }; // Request server time Sntp.time(options, function (err, time) { if (err) { console.log('Failed: ' + err.message); process.exit(1); } console.log('Local clock is off by: ' + time.t + ' milliseconds'); process.exit(0); }); ``` If an application needs to maintain continuous time synchronization, the module provides a stateful method for querying the current offset only when the last one is too old (defaults to daily). ```javascript // Request offset once Sntp.offset(function (err, offset) { console.log(offset); // New (served fresh) // Request offset again Sntp.offset(function (err, offset) { console.log(offset); // Identical (served from cache) }); }); ``` To set a background offset refresh, start the interval and use the provided now() method. If for any reason the client fails to obtain an up-to-date offset, the current system clock is used. ```javascript var before = Sntp.now(); // System time without offset Sntp.start(function () { var now = Sntp.now(); // With offset Sntp.stop(); }); ``` sntp-2.0.2/examples/000077500000000000000000000000001301773547500143315ustar00rootroot00000000000000sntp-2.0.2/examples/offset.js000077500000000000000000000006231301773547500161610ustar00rootroot00000000000000'use strict'; // Load modules const Sntp = require('../lib'); // Declare internals const internals = {}; // Request offset once Sntp.offset((err, offset1) => { console.log(err, offset1); // New (served fresh) // Request offset again Sntp.offset((err, offset2) => { console.log(err, offset2); // Identical (served from cache) }); }); sntp-2.0.2/examples/time.js000077500000000000000000000012771301773547500156370ustar00rootroot00000000000000'use strict'; // Load modules const Sntp = require('../lib'); // Declare internals const internals = {}; // All options are optional const options = { host: 'nist1-sj.ustiming.org', // Defaults to pool.ntp.org port: 123, // Defaults to 123 (NTP) resolveReference: true, // Default to false (not resolving) timeout: 1000 // Defaults to zero (no timeout) }; // Request server time Sntp.time(options, (err, time) => { if (err) { console.log('Failed: ' + err.message); process.exit(1); } console.log(time); console.log('Local clock is off by: ' + time.t + ' milliseconds'); process.exit(0); }); sntp-2.0.2/lib/000077500000000000000000000000001301773547500132615ustar00rootroot00000000000000sntp-2.0.2/lib/index.js000077500000000000000000000226161301773547500147400ustar00rootroot00000000000000'use strict'; // Load modules const Dgram = require('dgram'); const Dns = require('dns'); const Hoek = require('hoek'); // Declare internals const internals = {}; exports.time = function (options, callback) { if (arguments.length !== 2) { callback = arguments[0]; options = {}; } const 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 let timeoutId = 0; let sent = 0; // Ensure callback is only called once const finish = Hoek.once((err, result) => { if (timeoutId) { clearTimeout(timeoutId); timeoutId = 0; } socket.removeAllListeners(); socket.once('error', Hoek.ignore); socket.close(); return callback(err, result); }); // Create UDP socket const socket = Dgram.createSocket('udp4'); socket.once('error', (err) => finish(err)); // Listen to incoming messages socket.on('message', (buffer, rinfo) => { const received = Date.now(); const 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 const T1 = message.originateTimestamp; const T2 = message.receiveTimestamp; const T3 = message.transmitTimestamp; const 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, (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(() => { timeoutId = 0; return finish(new Error('Timeout')); }, settings.timeout); } // Construct NTP message const message = new Buffer(48); for (let 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, (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 const 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 const vn = ((buffer[0] & 0x38) >> 3); this.version = vn; // Mode const 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 const 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) const 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) { let seconds = 0; let fraction = 0; for (let i = 0; i < 4; ++i) { seconds = (seconds * 256) + buffer[offset + i]; } for (let 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) { const seconds = Math.floor(ts / 1000) + 2208988800; const 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 = {}; } const now = Date.now(); const 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(() => callback(null, internals.last.offset)); return; } exports.time(options, (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(() => callback()); return; } exports.offset(options, (ignoreErr, offset) => { internals.now.intervalId = setInterval(() => { exports.offset(options, Hoek.ignore); }, 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 () { const now = Date.now(); if (!exports.isLive() || now >= internals.last.expires) { return now; } return now + internals.last.offset; }; sntp-2.0.2/package.json000077500000000000000000000011701301773547500150030ustar00rootroot00000000000000{ "name": "sntp", "description": "SNTP Client", "version": "2.0.2", "author": "Eran Hammer (http://hueniverse.com)", "repository": { "type": "git", "url": "git://github.com/hueniverse/sntp" }, "main": "lib/index.js", "keywords": [ "sntp", "ntp", "time" ], "engines": { "node": ">=4.0.0" }, "dependencies": { "hoek": "4.x.x" }, "devDependencies": { "code": "4.x.x", "lab": "10.x.x" }, "scripts": { "test": "lab -a code -t 100 -L -m 20000", "test-cov-html": "lab -a code -r html -o coverage.html -m 20000" }, "license": "BSD-3-Clause" } sntp-2.0.2/test/000077500000000000000000000000001301773547500134725ustar00rootroot00000000000000sntp-2.0.2/test/index.js000077500000000000000000000314021301773547500151420ustar00rootroot00000000000000'use strict'; // Load modules const Dgram = require('dgram'); const Code = require('code'); const Hoek = require('hoek'); const Lab = require('lab'); const Sntp = require('../lib'); // Declare internals const internals = {}; // Test shortcuts const lab = exports.lab = Lab.script(); const describe = lab.describe; const it = lab.it; const expect = Code.expect; describe('SNTP', () => { const origDate = Date.now; Date.now = () => { return origDate() - 5; }; describe('time()', () => { it('returns consistent result over multiple tries', (done) => { Sntp.time((err, time1) => { expect(err).to.not.exist(); expect(time1).to.exist(); const t1 = time1.t; Sntp.time((err, time2) => { expect(err).to.not.exist(); expect(time2).to.exist(); const t2 = time2.t; expect(Math.abs(t1 - t2)).to.be.below(200); done(); }); }); }); it('resolves reference IP', (done) => { Sntp.time({ host: 'ntp.exnet.com', resolveReference: true }, (err, time) => { expect(err).to.not.exist(); expect(time).to.exist(); expect(time.referenceHost).to.exist(); done(); }); }); it('times out on no response', (done) => { Sntp.time({ port: 124, timeout: 100 }, (err, time) => { expect(err).to.exist(); expect(time).to.not.exist(); expect(err.message).to.equal('Timeout'); done(); }); }); it('errors on error event', { parallel: false }, (done) => { const orig = Dgram.createSocket; Dgram.createSocket = function (type) { Dgram.createSocket = orig; const socket = Dgram.createSocket(type); setImmediate(() => { socket.emit('error', new Error('Fake')); }); return socket; }; Sntp.time((err, time) => { expect(err).to.exist(); expect(time).to.not.exist(); expect(err.message).to.equal('Fake'); done(); }); }); it('errors on incorrect sent size', { parallel: false }, (done) => { const orig = Dgram.Socket.prototype.send; Dgram.Socket.prototype.send = function (buf, offset, length, port, address, callback) { Dgram.Socket.prototype.send = orig; return callback(null, 40); }; Sntp.time((err, time) => { expect(err).to.exist(); expect(time).to.not.exist(); expect(err.message).to.equal('Could not send entire message'); done(); }); }); it('times out on invalid host', (done) => { Sntp.time({ host: 'error', timeout: 10000 }, (err, time) => { expect(err).to.exist(); expect(time).to.not.exist(); expect(err.message).to.contain('getaddrinfo'); done(); }); }); it('fails on bad response buffer size', (done, onCleanup) => { const server = Dgram.createSocket('udp4'); onCleanup((next) => server.close(next)); server.on('message', (message, remote) => { const msg = new Buffer(10); server.send(msg, 0, msg.length, remote.port, remote.address, Hoek.ignore); }); server.bind(49123); Sntp.time({ host: 'localhost', port: 49123 }, (err, time) => { expect(err).to.exist(); expect(err.message).to.equal('Invalid server response'); done(); }); }); const messup = function (bytes, onCleanup) { const server = Dgram.createSocket('udp4'); onCleanup((next) => server.close(next)); server.on('message', (message, remote) => { const msg = new Buffer([ 0x24, 0x01, 0x00, 0xe3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x41, 0x43, 0x54, 0x53, 0xd4, 0xa8, 0x2d, 0xc7, 0x1c, 0x5d, 0x49, 0x1b, 0xd4, 0xa8, 0x2d, 0xe6, 0x67, 0xef, 0x9d, 0xb2, 0xd4, 0xa8, 0x2d, 0xe6, 0x71, 0xed, 0xb5, 0xfb, 0xd4, 0xa8, 0x2d, 0xe6, 0x71, 0xee, 0x6c, 0xc5 ]); for (let i = 0; i < bytes.length; ++i) { msg[bytes[i][0]] = bytes[i][1]; } server.send(msg, 0, msg.length, remote.port, remote.address, Hoek.ignore); }); server.bind(49123); }; it('fails on bad version', (done, onCleanup) => { messup([[0, (0 << 6) + (3 << 3) + (4 << 0)]], onCleanup); Sntp.time({ host: 'localhost', port: 49123 }, (err, time) => { expect(err).to.exist(); expect(time.version).to.equal(3); expect(err.message).to.equal('Invalid server response'); done(); }); }); it('fails on bad originateTimestamp', (done, onCleanup) => { messup([[24, 0x83], [25, 0xaa], [26, 0x7e], [27, 0x80], [28, 0], [29, 0], [30, 0], [31, 0]], onCleanup); Sntp.time({ host: 'localhost', port: 49123 }, (err, time) => { expect(err).to.exist(); expect(err.message).to.equal('Invalid server response'); done(); }); }); it('fails on bad receiveTimestamp', (done, onCleanup) => { messup([[32, 0x83], [33, 0xaa], [34, 0x7e], [35, 0x80], [36, 0], [37, 0], [38, 0], [39, 0]], onCleanup); Sntp.time({ host: 'localhost', port: 49123 }, (err, time) => { expect(err).to.exist(); expect(err.message).to.equal('Invalid server response'); done(); }); }); it('fails on bad originate timestamp and alarm li', (done, onCleanup) => { messup([[0, (3 << 6) + (4 << 3) + (4 << 0)]], onCleanup); Sntp.time({ host: 'localhost', port: 49123 }, (err, time) => { expect(err).to.exist(); expect(err.message).to.equal('Wrong originate timestamp'); expect(time.leapIndicator).to.equal('alarm'); done(); }); }); it('returns time with death stratum and last61 li', (done, onCleanup) => { messup([[0, (1 << 6) + (4 << 3) + (4 << 0)], [1, 0]], onCleanup); Sntp.time({ host: 'localhost', port: 49123 }, (err, time) => { expect(err).to.exist(); expect(time.stratum).to.equal('death'); expect(time.leapIndicator).to.equal('last-minute-61'); done(); }); }); it('returns time with reserved stratum and last59 li', (done, onCleanup) => { messup([[0, (2 << 6) + (4 << 3) + (4 << 0)], [1, 0x1f]], onCleanup); Sntp.time({ host: 'localhost', port: 49123 }, (err, time) => { expect(err).to.exist(); expect(time.stratum).to.equal('reserved'); expect(time.leapIndicator).to.equal('last-minute-59'); done(); }); }); it('fails on bad mode (symmetric-active)', (done, onCleanup) => { messup([[0, (0 << 6) + (4 << 3) + (1 << 0)]], onCleanup); Sntp.time({ host: 'localhost', port: 49123 }, (err, time) => { expect(err).to.exist(); expect(time.mode).to.equal('symmetric-active'); done(); }); }); it('fails on bad mode (symmetric-passive)', (done, onCleanup) => { messup([[0, (0 << 6) + (4 << 3) + (2 << 0)]], onCleanup); Sntp.time({ host: 'localhost', port: 49123 }, (err, time) => { expect(err).to.exist(); expect(time.mode).to.equal('symmetric-passive'); done(); }); }); it('fails on bad mode (client)', (done, onCleanup) => { messup([[0, (0 << 6) + (4 << 3) + (3 << 0)]], onCleanup); Sntp.time({ host: 'localhost', port: 49123 }, (err, time) => { expect(err).to.exist(); expect(time.mode).to.equal('client'); done(); }); }); it('fails on bad mode (broadcast)', (done, onCleanup) => { messup([[0, (0 << 6) + (4 << 3) + (5 << 0)]], onCleanup); Sntp.time({ host: 'localhost', port: 49123 }, (err, time) => { expect(err).to.exist(); expect(time.mode).to.equal('broadcast'); done(); }); }); it('fails on bad mode (reserved)', (done, onCleanup) => { messup([[0, (0 << 6) + (4 << 3) + (6 << 0)]], onCleanup); Sntp.time({ host: 'localhost', port: 49123 }, (err, time) => { expect(err).to.exist(); expect(time.mode).to.equal('reserved'); done(); }); }); }); describe('offset()', () => { it('gets the current offset', (done) => { Sntp.offset((err, offset) => { expect(err).to.not.exist(); expect(offset).to.not.equal(0); done(); }); }); it('gets the current offset from cache', (done) => { Sntp.offset((err, offset1) => { expect(err).to.not.exist(); expect(offset1).to.not.equal(0); Sntp.offset({}, (err, offset2) => { expect(err).to.not.exist(); expect(offset2).to.equal(offset1); done(); }); }); }); it('gets the new offset on different server (host)', { parallel: false }, (done, onCleanup) => { Sntp.offset((err, offset1) => { expect(err).to.not.exist(); expect(offset1).to.not.equal(0); Sntp.offset({ host: 'us.pool.ntp.org' }, (err, offset2) => { expect(err).to.not.exist(); expect(offset2).to.not.equal(0); done(); }); }); }); it('gets the new offset on different server (port)', { parallel: false }, (done, onCleanup) => { Sntp.offset((err, offset1) => { expect(err).to.not.exist(); expect(offset1).to.not.equal(0); Sntp.offset({ port: 123 }, (err, offset2) => { expect(err).to.not.exist(); expect(offset2).to.not.equal(0); done(); }); }); }); it('fails getting the current offset on invalid server', (done) => { Sntp.offset({ host: 'error' }, (err, offset) => { expect(err).to.exist(); expect(offset).to.equal(0); done(); }); }); }); describe('now()', () => { it('starts auto-sync, gets now, then stops', { parallel: false }, (done, onCleanup) => { Sntp.stop(); const before = Sntp.now(); expect(before).to.be.about(Date.now(), 5); Sntp.start(() => { const now = Sntp.now(); expect(now).to.not.equal(Date.now()); Sntp.stop(); done(); }); }); it('starts twice', { parallel: false }, (done, onCleanup) => { Sntp.stop(); Sntp.start(() => { Sntp.start(() => { const now = Sntp.now(); expect(now).to.not.equal(Date.now()); Sntp.stop(); done(); }); }); }); it('starts auto-sync, gets now, waits, gets again after timeout', (done) => { Sntp.stop(); const before = Sntp.now(); expect(before).to.be.about(Date.now(), 5); Sntp.start({ clockSyncRefresh: 100 }, () => { const now = Sntp.now(); expect(now).to.not.equal(Date.now()); expect(now).to.be.about(Sntp.now(), 5); setTimeout(() => { expect(Sntp.now()).to.not.equal(now); Sntp.stop(); done(); }, 110); }); }); }); });