pax_global_header00006660000000000000000000000064135251300020014501gustar00rootroot0000000000000052 comment=165a00f84c46c1c5d430cdea3078685ac8833d07
sntp-3.1.2/000077500000000000000000000000001352513000200124705ustar00rootroot00000000000000sntp-3.1.2/.gitignore000077500000000000000000000001541352513000200144630ustar00rootroot00000000000000**/node_modules
**/package-lock.json
coverage.*
**/.DS_Store
**/._*
**/*.pem
**/.vs
**/.vscode
**/.idea
sntp-3.1.2/.npmignore000077500000000000000000000000261352513000200144700ustar00rootroot00000000000000*
!lib/**
!.npmignore
sntp-3.1.2/.travis.yml000077500000000000000000000002231352513000200146010ustar00rootroot00000000000000language: node_js
node_js:
- "8"
- "10"
- "12"
- "node"
sudo: false
install:
- "npm install"
os:
- "linux"
- "osx"
- "windows"
sntp-3.1.2/CHANGELOG.md000066400000000000000000000005421352513000200143020ustar00rootroot00000000000000Breaking changes are documented using GitHub issues, see [issues labeled "release notes"](https://github.com/hapijs/sntp/issues?q=is%3Aissue+label%3A%22release+notes%22).
If you want changes of a specific minor or patch release, you can browse the [GitHub milestones](https://github.com/hapijs/sntp/milestones?state=closed&direction=asc&sort=due_date).
sntp-3.1.2/LICENSE.md000077500000000000000000000026751352513000200141110ustar00rootroot00000000000000Copyright (c) 2012-2019, Sideway Inc, 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 OFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
sntp-3.1.2/README.md000077500000000000000000000042451352513000200137570ustar00rootroot00000000000000
# 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.
[](http://travis-ci.org/hapijs/sntp)
# Usage
```javascript
const Sntp = require('@hapi/sntp');
// 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
const exec = async function () {
try {
const time = await Sntp.time(options);
console.log('Local clock is off by: ' + time.t + ' milliseconds');
process.exit(0);
}
catch (err) {
console.log('Failed: ' + err.message);
process.exit(1);
}
};
exec();
```
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
const exec = async function () {
const offset1 = await Sntp.offset();
console.log(offset1); // New (served fresh)
// Request offset again
const offset2 = await Sntp.offset();
console.log(offset2); // Identical (served from cache)
};
exec();
```
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
const before = Sntp.now(); // System time without offset
const exec = async function () {
await Sntp.start();
const now = Sntp.now(); // With offset
Sntp.stop();
};
exec();
```
sntp-3.1.2/lib/000077500000000000000000000000001352513000200132365ustar00rootroot00000000000000sntp-3.1.2/lib/index.js000077500000000000000000000220521352513000200147070ustar00rootroot00000000000000'use strict';
const Dgram = require('dgram');
const Dns = require('dns');
const Boom = require('@hapi/boom');
const Bounce = require('@hapi/bounce');
const Hoek = require('@hapi/hoek');
const Teamwork = require('@hapi/teamwork');
const internals = {};
exports.time = async function (options = {}) {
const settings = Hoek.clone(options);
settings.host = settings.host || 'time.google.com';
settings.port = settings.port || 123;
settings.resolveReference = settings.resolveReference || false;
const team = new Teamwork();
// Set timeout
const timeoutId = (settings.timeout ? setTimeout(() => team.attend(new Boom('Timeout')), settings.timeout) : null);
// Create UDP socket
const socket = Dgram.createSocket('udp4');
socket.once('error', (err) => team.attend(err));
// Listen to incoming messages
socket.on('message', (buffer, rinfo) => {
const received = Date.now();
const message = new internals.NtpMessage(buffer);
if (!message.isValid) {
const error = new Boom('Invalid server response');
error.time = message;
return team.attend(error);
}
if (message.originateTimestamp !== sent) {
const error = new Boom('Wrong originate timestamp');
error.time = message;
return team.attend(error);
}
// 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 (message.stratum !== 'secondary' ||
!settings.resolveReference) {
return team.attend(message);
}
// Resolve reference IP address
Dns.reverse(message.referenceId, (err, domains) => {
if (/* $lab:coverage:off$ */ !err /* $lab:coverage:on$ */) {
message.referenceHost = domains[0];
}
return team.attend(message);
});
});
// Construct NTP message
const message = Buffer.alloc(48); // Zero-filled message
message[0] = (0 << 6) + (4 << 3) + (3 << 0); // Set version number to 4 and Mode to 3 (client)
const 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 team.attend(err || new Boom('Could not send entire message'));
}
});
try {
return await team.work;
}
finally {
clearTimeout(timeoutId);
socket.removeAllListeners();
socket.once('error', Hoek.ignore);
try {
socket.close();
}
catch (ignoreErr) { } // Ignore errors if the socket is already closed
}
};
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
this.isValid = (this.version === 4 &&
this.stratum !== 'reserved' &&
this.mode === 'server' &&
this.originateTimestamp &&
this.receiveTimestamp &&
this.transmitTimestamp);
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 = async function (options = {}) {
const now = Date.now();
const clockSyncRefresh = options.clockSyncRefresh || 24 * 60 * 60 * 1000; // Daily
if (now < internals.last.expires &&
internals.last.host === options.host &&
internals.last.port === options.port) {
return internals.last.offset;
}
const time = await exports.time(options);
internals.last = {
offset: Math.round(time.t),
expires: now + clockSyncRefresh,
host: options.host,
port: options.port
};
return internals.last.offset;
};
// Now singleton
internals.now = {
started: false,
intervalId: null
};
exports.start = async function (options = {}) {
if (internals.now.started) {
return;
}
const tick = async () => {
try {
await exports.offset(options);
}
catch (err) {
if (options.onError) {
options.onError(err);
}
Bounce.rethrow(err, 'system');
}
};
internals.now.started = true;
internals.now.intervalId = setInterval(tick, options.clockSyncRefresh || 24 * 60 * 60 * 1000); // Daily
await exports.offset(options);
};
exports.stop = function () {
if (!internals.now.started) {
return;
}
clearInterval(internals.now.intervalId);
internals.now.started = false;
internals.now.intervalId = null;
};
exports.isLive = function () {
return internals.now.started;
};
exports.now = function () {
const now = Date.now();
if (exports.isLive()) {
return now + internals.last.offset;
}
return now;
};
sntp-3.1.2/package.json000077500000000000000000000011351352513000200147610ustar00rootroot00000000000000{
"name": "@hapi/sntp",
"description": "SNTP Client",
"version": "3.1.2",
"repository": "git://github.com/hapijs/sntp",
"main": "lib/index.js",
"keywords": [
"sntp",
"ntp",
"time"
],
"dependencies": {
"@hapi/boom": "7.x.x",
"@hapi/bounce": "1.x.x",
"@hapi/hoek": "8.x.x",
"@hapi/teamwork": "3.x.x"
},
"devDependencies": {
"@hapi/code": "6.x.x",
"@hapi/lab": "20.x.x"
},
"scripts": {
"test": "lab -a @hapi/code -t 100 -L -m 20000",
"test-cov-html": "lab -a @hapi/code -r html -o coverage.html -m 20000"
},
"license": "BSD-3-Clause"
}
sntp-3.1.2/test/000077500000000000000000000000001352513000200134475ustar00rootroot00000000000000sntp-3.1.2/test/index.js000077500000000000000000000256751352513000200151360ustar00rootroot00000000000000'use strict';
const Dgram = require('dgram');
const Code = require('@hapi/code');
const Hoek = require('@hapi/hoek');
const Lab = require('@hapi/lab');
const Sntp = require('..');
const Teamwork = require('@hapi/teamwork');
const internals = {};
const { describe, it } = exports.lab = Lab.script();
const expect = Code.expect;
describe('SNTP', () => {
const origDate = Date.now;
Date.now = () => {
return origDate() - 5;
};
describe('time()', () => {
it('returns consistent result over multiple tries', async () => {
const time1 = await Sntp.time();
expect(time1).to.exist();
const t1 = time1.t;
const time2 = await Sntp.time();
expect(time2).to.exist();
const t2 = time2.t;
expect(Math.abs(t1 - t2)).to.be.below(200);
});
it('resolves reference IP', async () => {
const time = await Sntp.time({ host: 'ntp.exnet.com', resolveReference: true });
expect(time).to.exist();
expect(time.referenceHost).to.exist();
});
it('times out on no response', async () => {
await expect(Sntp.time({ port: 124, timeout: 100 })).to.reject('Timeout');
});
it('errors on error event', async () => {
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;
};
await expect(Sntp.time()).to.reject('Fake');
});
it('errors on incorrect sent size', async () => {
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);
};
await expect(Sntp.time()).to.reject('Could not send entire message');
});
it('times out on invalid host', async () => {
await expect(Sntp.time({ host: 'no-such-hostname' })).to.reject(/getaddrinfo/);
});
it('fails on bad response buffer size', async (flags) => {
const server = Dgram.createSocket('udp4');
flags.onCleanup = (next) => server.close(next);
server.on('message', (message, remote) => {
const msg = Buffer.alloc(10);
server.send(msg, 0, msg.length, remote.port, remote.address, Hoek.ignore);
});
server.bind(49123);
await expect(Sntp.time({ host: 'localhost', port: 49123 })).to.reject('Invalid server response');
});
const messup = function (bytes, flags) {
const server = Dgram.createSocket('udp4');
flags.onCleanup = (next) => server.close(next);
server.on('message', (message, remote) => {
const msg = Buffer.from([
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', async (flags) => {
messup([[0, (0 << 6) + (3 << 3) + (4 << 0)]], flags);
const err = await expect(Sntp.time({ host: 'localhost', port: 49123 })).to.reject('Invalid server response');
expect(err.time.version).to.equal(3);
});
it('fails on bad originateTimestamp', async (flags) => {
messup([[24, 0x83], [25, 0xaa], [26, 0x7e], [27, 0x80], [28, 0], [29, 0], [30, 0], [31, 0]], flags);
await expect(Sntp.time({ host: 'localhost', port: 49123 })).to.reject('Invalid server response');
});
it('fails on bad receiveTimestamp', async (flags) => {
messup([[32, 0x83], [33, 0xaa], [34, 0x7e], [35, 0x80], [36, 0], [37, 0], [38, 0], [39, 0]], flags);
await expect(Sntp.time({ host: 'localhost', port: 49123 })).to.reject('Invalid server response');
});
it('fails on bad originate timestamp and alarm li', async (flags) => {
messup([[0, (3 << 6) + (4 << 3) + (4 << 0)]], flags);
const err = await expect(Sntp.time({ host: 'localhost', port: 49123 })).to.reject('Wrong originate timestamp');
expect(err.time.leapIndicator).to.equal('alarm');
});
it('returns time with death stratum and last61 li', async (flags) => {
messup([[0, (1 << 6) + (4 << 3) + (4 << 0)], [1, 0]], flags);
const err = await expect(Sntp.time({ host: 'localhost', port: 49123 })).to.reject();
expect(err.time.stratum).to.equal('death');
expect(err.time.leapIndicator).to.equal('last-minute-61');
});
it('returns time with reserved stratum and last59 li', async (flags) => {
messup([[0, (2 << 6) + (4 << 3) + (4 << 0)], [1, 0x1f]], flags);
const err = await expect(Sntp.time({ host: 'localhost', port: 49123 })).to.reject();
expect(err.time.stratum).to.equal('reserved');
expect(err.time.leapIndicator).to.equal('last-minute-59');
});
it('fails on bad mode (symmetric-active)', async (flags) => {
messup([[0, (0 << 6) + (4 << 3) + (1 << 0)]], flags);
const err = await expect(Sntp.time({ host: 'localhost', port: 49123 })).to.reject();
expect(err.time.mode).to.equal('symmetric-active');
});
it('fails on bad mode (symmetric-passive)', async (flags) => {
messup([[0, (0 << 6) + (4 << 3) + (2 << 0)]], flags);
const err = await expect(Sntp.time({ host: 'localhost', port: 49123 })).to.reject();
expect(err.time.mode).to.equal('symmetric-passive');
});
it('fails on bad mode (client)', async (flags) => {
messup([[0, (0 << 6) + (4 << 3) + (3 << 0)]], flags);
const err = await expect(Sntp.time({ host: 'localhost', port: 49123 })).to.reject();
expect(err.time.mode).to.equal('client');
});
it('fails on bad mode (broadcast)', async (flags) => {
messup([[0, (0 << 6) + (4 << 3) + (5 << 0)]], flags);
const err = await expect(Sntp.time({ host: 'localhost', port: 49123 })).to.reject();
expect(err.time.mode).to.equal('broadcast');
});
it('fails on bad mode (reserved)', async (flags) => {
messup([[0, (0 << 6) + (4 << 3) + (6 << 0)]], flags);
const err = await expect(Sntp.time({ host: 'localhost', port: 49123 })).to.reject();
expect(err.time.mode).to.equal('reserved');
});
});
describe('offset()', () => {
it('gets the current offset', async () => {
const offset = await Sntp.offset();
expect(offset).to.not.equal(0);
});
it('gets the current offset from cache', async () => {
const offset1 = await Sntp.offset();
expect(offset1).to.not.equal(0);
const offset2 = await Sntp.offset({});
expect(offset2).to.equal(offset1);
});
it('gets the new offset on different server (host)', async (flags) => {
const offset1 = await Sntp.offset();
expect(offset1).to.not.equal(0);
const offset2 = await Sntp.offset({ host: 'us.pool.ntp.org' });
expect(offset2).to.not.equal(0);
});
it('gets the new offset on different server (port)', async (flags) => {
const offset1 = await Sntp.offset();
expect(offset1).to.not.equal(0);
const offset2 = await Sntp.offset({ port: 123 });
expect(offset2).to.not.equal(0);
});
it('fails getting the current offset on invalid server', async () => {
await expect(Sntp.offset({ host: 'no-such-host-error', timeout: 100 })).to.reject();
});
});
describe('start()', () => {
it('returns error (direct)', async (flags) => {
Sntp.stop();
await expect(Sntp.start({ host: 'no-such-host-error', onError: Hoek.ignore, timeout: 10 })).to.reject();
Sntp.stop();
});
it('returns error (handler)', async (flags) => {
Sntp.stop();
const team = new Teamwork();
const onError = (err) => {
expect(err).to.be.an.error();
Sntp.stop();
team.attend();
};
const orig = Sntp.offset;
Sntp.offset = () => {
Sntp.offset = orig;
};
await Sntp.start({ host: 'no-such-host-error', onError, clockSyncRefresh: 100, timeout: 10 });
await team.work;
});
it('ignores errors', async (flags) => {
Sntp.stop();
const orig = Sntp.offset;
Sntp.offset = () => {
Sntp.offset = orig;
};
await expect(Sntp.start({ host: 'no-such-host-error', clockSyncRefresh: 100, timeout: 10 })).to.not.reject();
await Hoek.wait(110);
});
});
describe('now()', () => {
it('starts auto-sync, gets now, then stops', async (flags) => {
Sntp.stop();
const before = Sntp.now();
expect(before).to.be.about(Date.now(), 5);
await Sntp.start();
const now = Sntp.now();
expect(now).to.not.equal(Date.now());
Sntp.stop();
});
it('starts twice', async (flags) => {
Sntp.stop();
await Sntp.start();
await Sntp.start();
const now = Sntp.now();
expect(now).to.not.equal(Date.now());
Sntp.stop();
});
it('starts auto-sync, gets now, waits, gets again after timeout', async () => {
Sntp.stop();
const before = Sntp.now();
expect(before).to.be.about(Date.now(), 5);
await Sntp.start({ clockSyncRefresh: 100 });
const now = Sntp.now();
expect(now).to.not.equal(Date.now());
expect(now).to.be.about(Sntp.now(), 5);
await Hoek.wait(110);
expect(Sntp.now()).to.not.equal(now);
Sntp.stop();
});
});
});