react / wstein / node_modules / jest-cli / node_modules / jsdom / node_modules / request / node_modules / hawk / node_modules / sntp / lib / index.js
81154 views// Load modules12var Dgram = require('dgram');3var Dns = require('dns');4var Hoek = require('hoek');567// Declare internals89var internals = {};101112exports.time = function (options, callback) {1314if (arguments.length !== 2) {15callback = arguments[0];16options = {};17}1819var settings = Hoek.clone(options);20settings.host = settings.host || 'pool.ntp.org';21settings.port = settings.port || 123;22settings.resolveReference = settings.resolveReference || false;2324// Declare variables used by callback2526var timeoutId = 0;27var sent = 0;2829// Ensure callback is only called once3031var finish = function (err, result) {3233if (timeoutId) {34clearTimeout(timeoutId);35timeoutId = 0;36}3738socket.removeAllListeners();39socket.once('error', internals.ignore);40socket.close();41return callback(err, result);42};4344finish = Hoek.once(finish);4546// Create UDP socket4748var socket = Dgram.createSocket('udp4');4950socket.once('error', function (err) {5152return finish(err);53});5455// Listen to incoming messages5657socket.on('message', function (buffer, rinfo) {5859var received = Date.now();6061var message = new internals.NtpMessage(buffer);62if (!message.isValid) {63return finish(new Error('Invalid server response'), message);64}6566if (message.originateTimestamp !== sent) {67return finish(new Error('Wrong originate timestamp'), message);68}6970// Timestamp Name ID When Generated71// ------------------------------------------------------------72// Originate Timestamp T1 time request sent by client73// Receive Timestamp T2 time request received by server74// Transmit Timestamp T3 time reply sent by server75// Destination Timestamp T4 time reply received by client76//77// The roundtrip delay d and system clock offset t are defined as:78//79// d = (T4 - T1) - (T3 - T2) t = ((T2 - T1) + (T3 - T4)) / 28081var T1 = message.originateTimestamp;82var T2 = message.receiveTimestamp;83var T3 = message.transmitTimestamp;84var T4 = received;8586message.d = (T4 - T1) - (T3 - T2);87message.t = ((T2 - T1) + (T3 - T4)) / 2;88message.receivedLocally = received;8990if (!settings.resolveReference ||91message.stratum !== 'secondary') {9293return finish(null, message);94}9596// Resolve reference IP address9798Dns.reverse(message.referenceId, function (err, domains) {99100if (/* $lab:coverage:off$ */ !err /* $lab:coverage:on$ */) {101message.referenceHost = domains[0];102}103104return finish(null, message);105});106});107108// Set timeout109110if (settings.timeout) {111timeoutId = setTimeout(function () {112113timeoutId = 0;114return finish(new Error('Timeout'));115}, settings.timeout);116}117118// Construct NTP message119120var message = new Buffer(48);121for (var i = 0; i < 48; i++) { // Zero message122message[i] = 0;123}124125message[0] = (0 << 6) + (4 << 3) + (3 << 0) // Set version number to 4 and Mode to 3 (client)126sent = Date.now();127internals.fromMsecs(sent, message, 40); // Set transmit timestamp (returns as originate)128129// Send NTP request130131socket.send(message, 0, message.length, settings.port, settings.host, function (err, bytes) {132133if (err ||134bytes !== 48) {135136return finish(err || new Error('Could not send entire message'));137}138});139};140141142internals.NtpMessage = function (buffer) {143144this.isValid = false;145146// Validate147148if (buffer.length !== 48) {149return;150}151152// Leap indicator153154var li = (buffer[0] >> 6);155switch (li) {156case 0: this.leapIndicator = 'no-warning'; break;157case 1: this.leapIndicator = 'last-minute-61'; break;158case 2: this.leapIndicator = 'last-minute-59'; break;159case 3: this.leapIndicator = 'alarm'; break;160}161162// Version163164var vn = ((buffer[0] & 0x38) >> 3);165this.version = vn;166167// Mode168169var mode = (buffer[0] & 0x7);170switch (mode) {171case 1: this.mode = 'symmetric-active'; break;172case 2: this.mode = 'symmetric-passive'; break;173case 3: this.mode = 'client'; break;174case 4: this.mode = 'server'; break;175case 5: this.mode = 'broadcast'; break;176case 0:177case 6:178case 7: this.mode = 'reserved'; break;179}180181// Stratum182183var stratum = buffer[1];184if (stratum === 0) {185this.stratum = 'death';186}187else if (stratum === 1) {188this.stratum = 'primary';189}190else if (stratum <= 15) {191this.stratum = 'secondary';192}193else {194this.stratum = 'reserved';195}196197// Poll interval (msec)198199this.pollInterval = Math.round(Math.pow(2, buffer[2])) * 1000;200201// Precision (msecs)202203this.precision = Math.pow(2, buffer[3]) * 1000;204205// Root delay (msecs)206207var rootDelay = 256 * (256 * (256 * buffer[4] + buffer[5]) + buffer[6]) + buffer[7];208this.rootDelay = 1000 * (rootDelay / 0x10000);209210// Root dispersion (msecs)211212this.rootDispersion = ((buffer[8] << 8) + buffer[9] + ((buffer[10] << 8) + buffer[11]) / Math.pow(2, 16)) * 1000;213214// Reference identifier215216this.referenceId = '';217switch (this.stratum) {218case 'death':219case 'primary':220this.referenceId = String.fromCharCode(buffer[12]) + String.fromCharCode(buffer[13]) + String.fromCharCode(buffer[14]) + String.fromCharCode(buffer[15]);221break;222case 'secondary':223this.referenceId = '' + buffer[12] + '.' + buffer[13] + '.' + buffer[14] + '.' + buffer[15];224break;225}226227// Reference timestamp228229this.referenceTimestamp = internals.toMsecs(buffer, 16);230231// Originate timestamp232233this.originateTimestamp = internals.toMsecs(buffer, 24);234235// Receive timestamp236237this.receiveTimestamp = internals.toMsecs(buffer, 32);238239// Transmit timestamp240241this.transmitTimestamp = internals.toMsecs(buffer, 40);242243// Validate244245if (this.version === 4 &&246this.stratum !== 'reserved' &&247this.mode === 'server' &&248this.originateTimestamp &&249this.receiveTimestamp &&250this.transmitTimestamp) {251252this.isValid = true;253}254255return this;256};257258259internals.toMsecs = function (buffer, offset) {260261var seconds = 0;262var fraction = 0;263264for (var i = 0; i < 4; ++i) {265seconds = (seconds * 256) + buffer[offset + i];266}267268for (i = 4; i < 8; ++i) {269fraction = (fraction * 256) + buffer[offset + i];270}271272return ((seconds - 2208988800 + (fraction / Math.pow(2, 32))) * 1000);273};274275276internals.fromMsecs = function (ts, buffer, offset) {277278var seconds = Math.floor(ts / 1000) + 2208988800;279var fraction = Math.round((ts % 1000) / 1000 * Math.pow(2, 32));280281buffer[offset + 0] = (seconds & 0xFF000000) >> 24;282buffer[offset + 1] = (seconds & 0x00FF0000) >> 16;283buffer[offset + 2] = (seconds & 0x0000FF00) >> 8;284buffer[offset + 3] = (seconds & 0x000000FF);285286buffer[offset + 4] = (fraction & 0xFF000000) >> 24;287buffer[offset + 5] = (fraction & 0x00FF0000) >> 16;288buffer[offset + 6] = (fraction & 0x0000FF00) >> 8;289buffer[offset + 7] = (fraction & 0x000000FF);290};291292293// Offset singleton294295internals.last = {296offset: 0,297expires: 0,298host: '',299port: 0300};301302303exports.offset = function (options, callback) {304305if (arguments.length !== 2) {306callback = arguments[0];307options = {};308}309310var now = Date.now();311var clockSyncRefresh = options.clockSyncRefresh || 24 * 60 * 60 * 1000; // Daily312313if (internals.last.offset &&314internals.last.host === options.host &&315internals.last.port === options.port &&316now < internals.last.expires) {317318process.nextTick(function () {319320callback(null, internals.last.offset);321});322323return;324}325326exports.time(options, function (err, time) {327328if (err) {329return callback(err, 0);330}331332internals.last = {333offset: Math.round(time.t),334expires: now + clockSyncRefresh,335host: options.host,336port: options.port337};338339return callback(null, internals.last.offset);340});341};342343344// Now singleton345346internals.now = {347intervalId: 0348};349350351exports.start = function (options, callback) {352353if (arguments.length !== 2) {354callback = arguments[0];355options = {};356}357358if (internals.now.intervalId) {359process.nextTick(function () {360361callback();362});363364return;365}366367exports.offset(options, function (err, offset) {368369internals.now.intervalId = setInterval(function () {370371exports.offset(options, function () { });372}, options.clockSyncRefresh || 24 * 60 * 60 * 1000); // Daily373374return callback();375});376};377378379exports.stop = function () {380381if (!internals.now.intervalId) {382return;383}384385clearInterval(internals.now.intervalId);386internals.now.intervalId = 0;387};388389390exports.isLive = function () {391392return !!internals.now.intervalId;393};394395396exports.now = function () {397398var now = Date.now();399if (!exports.isLive() ||400now >= internals.last.expires) {401402return now;403}404405return now + internals.last.offset;406};407408409internals.ignore = function () {410411};412413414