| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412 | // Load modulesvar Dgram = require('dgram');var Dns = require('dns');var Hoek = require('hoek');// Declare internalsvar 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 singletoninternals.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 singletoninternals.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 () {};
 |