toolkit/modules/Sntp.jsm

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

     1 /* This Source Code Form is subject to the terms of the Mozilla Public
     2  * License, v. 2.0. If a copy of the MPL was not distributed with this
     3  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     5 "use strict";
     7 this.EXPORTED_SYMBOLS = [
     8   "Sntp",
     9 ];
    11 const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
    13 // Set to true to see debug messages.
    14 let DEBUG = false;
    16 /**
    17  * Constructor of Sntp.
    18  *
    19  * @param dataAvailableCb
    20  *        Callback function gets called when SNTP offset available. Signature
    21  *        is function dataAvailableCb(offsetInMS).
    22  * @param maxRetryCount
    23  *        Maximum retry count when SNTP failed to connect to server; set to
    24  *        zero to disable the retry.
    25  * @param refreshPeriodInSecs
    26  *        Refresh period; set to zero to disable refresh.
    27  * @param timeoutInSecs
    28  *        Timeout value used for connection.
    29  * @param pools
    30  *        SNTP server lists separated by ';'.
    31  * @param port
    32  *        SNTP port.
    33  */
    34 this.Sntp = function Sntp(dataAvailableCb, maxRetryCount, refreshPeriodInSecs,
    35                           timeoutInSecs, pools, port) {
    36   if (dataAvailableCb != null) {
    37     this._dataAvailableCb = dataAvailableCb;
    38   }
    39   if (maxRetryCount != null) {
    40     this._maxRetryCount = maxRetryCount;
    41   }
    42   if (refreshPeriodInSecs != null) {
    43     this._refreshPeriodInMS = refreshPeriodInSecs * 1000;
    44   }
    45   if (timeoutInSecs != null) {
    46     this._timeoutInMS = timeoutInSecs * 1000;
    47   }
    48   if (pools != null && Array.isArray(pools) && pools.length > 0) {
    49     this._pools = pools;
    50   }
    51   if (port != null) {
    52     this._port = port;
    53   }
    54 }
    56 Sntp.prototype = {
    57   isAvailable: function isAvailable() {
    58     return this._cachedOffset != null;
    59   },
    61   isExpired: function isExpired() {
    62     let valid = this._cachedOffset != null && this._cachedTimeInMS != null;
    63     if (this._refreshPeriodInMS > 0) {
    64       valid = valid && Date.now() < this._cachedTimeInMS +
    65                                     this._refreshPeriodInMS;
    66     }
    67     return !valid;
    68   },
    70   request: function request() {
    71     this._request();
    72   },
    74   getOffset: function getOffset() {
    75     return this._cachedOffset;
    76   },
    78   /**
    79    * Indicates the system clock has been changed by [offset]ms so we need to
    80    * adjust the stored value.
    81    */
    82   updateOffset: function updateOffset(offset) {
    83     if (this._cachedOffset != null) {
    84       this._cachedOffset -= offset;
    85     }
    86   },
    88   /**
    89    * Used to schedule a retry or periodic updates.
    90    */
    91   _schedule: function _schedule(timeInMS) {
    92     if (this._updateTimer == null) {
    93       this._updateTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
    94     }
    96     this._updateTimer.initWithCallback(this._request.bind(this),
    97                                        timeInMS,
    98                                        Ci.nsITimer.TYPE_ONE_SHOT);
    99     debug("Scheduled SNTP request in " + timeInMS + "ms");
   100   },
   102   /**
   103    * Handle the SNTP response.
   104    */
   105   _handleSntp: function _handleSntp(originateTimeInMS, receiveTimeInMS,
   106                                     transmitTimeInMS, respondTimeInMS) {
   107     let clockOffset = Math.floor(((receiveTimeInMS - originateTimeInMS) +
   108                                  (transmitTimeInMS - respondTimeInMS)) / 2);
   109     debug("Clock offset: " + clockOffset);
   111     // We've succeeded so clear the retry status.
   112     this._retryCount = 0;
   113     this._retryPeriodInMS = 0;
   115     // Cache the latest SNTP offset whenever receiving it.
   116     this._cachedOffset = clockOffset;
   117     this._cachedTimeInMS = respondTimeInMS;
   119     if (this._dataAvailableCb != null) {
   120       this._dataAvailableCb(clockOffset);
   121     }
   123     this._schedule(this._refreshPeriodInMS);
   124   },
   126   /**
   127    * Used for retry SNTP requests.
   128    */
   129   _retry: function _retry() {
   130     this._retryCount++;
   131     if (this._retryCount > this._maxRetryCount) {
   132       debug ("stop retrying SNTP");
   133       // Clear so we can start with clean status next time we have network.
   134       this._retryCount = 0;
   135       this._retryPeriodInMS = 0;
   136       return;
   137     }
   138     this._retryPeriodInMS = Math.max(1000, this._retryPeriodInMS * 2);
   140     this._schedule(this._retryPeriodInMS);
   141   },
   143   /**
   144    * Request SNTP.
   145    */
   146   _request: function _request() {
   147     function GetRequest() {
   148       let NTP_PACKET_SIZE = 48;
   149       let NTP_MODE_CLIENT = 3;
   150       let NTP_VERSION = 3;
   151       let TRANSMIT_TIME_OFFSET = 40;
   153       // Send the NTP request.
   154       let requestTimeInMS = Date.now();
   155       let s = requestTimeInMS / 1000;
   156       let ms = requestTimeInMS % 1000;
   157       // NTP time is relative to 1900.
   158       s += OFFSET_1900_TO_1970;
   159       let f = ms * 0x100000000 / 1000;
   160       s = Math.floor(s);
   161       f = Math.floor(f);
   163       let buffer = new ArrayBuffer(NTP_PACKET_SIZE);
   164       let data = new DataView(buffer);
   165       data.setUint8(0, NTP_MODE_CLIENT | (NTP_VERSION << 3));
   166       data.setUint32(TRANSMIT_TIME_OFFSET, s, false);
   167       data.setUint32(TRANSMIT_TIME_OFFSET + 4, f, false);
   169       return String.fromCharCode.apply(null, new Uint8Array(buffer));
   170     }
   172     function SNTPListener() {};
   173     SNTPListener.prototype = {
   174       onStartRequest: function onStartRequest(request, context) {
   175       },
   177       onStopRequest: function onStopRequest(request, context, status) {
   178         if (!Components.isSuccessCode(status)) {
   179           debug ("Connection failed");
   180           this._requesting = false;
   181           this._retry();
   182         }
   183       }.bind(this),
   185       onDataAvailable: function onDataAvailable(request, context, inputStream,
   186                                                 offset, count) {
   187         function GetTimeStamp(binaryInputStream) {
   188           let s = binaryInputStream.read32();
   189           let f = binaryInputStream.read32();
   190           return Math.floor(
   191             ((s - OFFSET_1900_TO_1970) * 1000) + ((f * 1000) / 0x100000000)
   192           );
   193         }
   194         debug ("Data available: " + count + " bytes");
   196         try {
   197           let binaryInputStream = Cc["@mozilla.org/binaryinputstream;1"]
   198                                     .createInstance(Ci.nsIBinaryInputStream);
   199           binaryInputStream.setInputStream(inputStream);
   200           // We don't need first 24 bytes.
   201           for (let i = 0; i < 6; i++) {
   202             binaryInputStream.read32();
   203           }
   204           // Offset 24: originate time.
   205           let originateTimeInMS = GetTimeStamp(binaryInputStream);
   206           // Offset 32: receive time.
   207           let receiveTimeInMS = GetTimeStamp(binaryInputStream);
   208           // Offset 40: transmit time.
   209           let transmitTimeInMS = GetTimeStamp(binaryInputStream);
   210           let respondTimeInMS = Date.now();
   212           this._handleSntp(originateTimeInMS, receiveTimeInMS,
   213                            transmitTimeInMS, respondTimeInMS);
   214           this._requesting = false;
   215         } catch (e) {
   216           debug ("SNTPListener Error: " + e.message);
   217           this._requesting = false;
   218           this._retry();
   219         }
   220         inputStream.close();
   221       }.bind(this)
   222     };
   224     function SNTPRequester() {}
   225     SNTPRequester.prototype = {
   226       onOutputStreamReady: function(stream) {
   227         try {
   228           let data = GetRequest();
   229           let bytes_write = stream.write(data, data.length);
   230           debug ("SNTP: sent " + bytes_write + " bytes");
   231           stream.close();
   232         } catch (e) {
   233           debug ("SNTPRequester Error: " + e.message);
   234           this._requesting = false;
   235           this._retry();
   236         }
   237       }.bind(this)
   238     };
   240     // Number of seconds between Jan 1, 1900 and Jan 1, 1970.
   241     // 70 years plus 17 leap days.
   242     let OFFSET_1900_TO_1970 = ((365 * 70) + 17) * 24 * 60 * 60;
   244     if (this._requesting) {
   245       return;
   246     }
   247     if (this._pools.length < 1) {
   248       debug("No server defined");
   249       return;
   250     }
   251     if (this._updateTimer) {
   252       this._updateTimer.cancel();
   253     }
   255     debug ("Making request");
   256     this._requesting = true;
   258     let currentThread = Cc["@mozilla.org/thread-manager;1"]
   259                           .getService().currentThread;
   260     let socketTransportService =
   261       Cc["@mozilla.org/network/socket-transport-service;1"]
   262         .getService(Ci.nsISocketTransportService);
   263     let pump = Cc["@mozilla.org/network/input-stream-pump;1"]
   264                  .createInstance(Ci.nsIInputStreamPump);
   265     let transport = socketTransportService
   266       .createTransport(["udp"],
   267                        1,
   268                        this._pools[Math.floor(this._pools.length * Math.random())],
   269                        this._port,
   270                        null);
   272     transport.setTimeout(Ci.nsISocketTransport.TIMEOUT_CONNECT, this._timeoutInMS);
   273     transport.setTimeout(Ci.nsISocketTransport.TIMEOUT_READ_WRITE, this._timeoutInMS);
   275     let outStream = transport.openOutputStream(0, 0, 0)
   276                              .QueryInterface(Ci.nsIAsyncOutputStream);
   277     let inStream = transport.openInputStream(0, 0, 0);
   279     pump.init(inStream, -1, -1, 0, 0, false);
   280     pump.asyncRead(new SNTPListener(), null);
   282     outStream.asyncWait(new SNTPRequester(), 0, 0, currentThread);
   283   },
   285   // Callback function.
   286   _dataAvailableCb: null,
   288   // Sntp servers.
   289   _pools: [
   290     "0.pool.ntp.org",
   291     "1.pool.ntp.org",
   292     "2.pool.ntp.org",
   293     "3.pool.ntp.org"
   294   ],
   296   // The SNTP port.
   297   _port: 123,
   299   // Maximum retry count allowed when request failed.
   300   _maxRetryCount: 0,
   302   // Refresh period.
   303   _refreshPeriodInMS: 0,
   305   // Timeout value used for connecting.
   306   _timeoutInMS: 30 * 1000,
   308   // Cached SNTP offset.
   309   _cachedOffset: null,
   311   // The time point when we cache the offset.
   312   _cachedTimeInMS: null,
   314   // Flag to avoid redundant requests.
   315   _requesting: false,
   317   // Retry counter.
   318   _retryCount: 0,
   320   // Retry time offset (in seconds).
   321   _retryPeriodInMS: 0,
   323   // Timer used for retries & daily updates.
   324   _updateTimer: null
   325 };
   327 let debug;
   328 if (DEBUG) {
   329   debug = function (s) {
   330     dump("-*- Sntp: " + s + "\n");
   331   };
   332 } else {
   333   debug = function (s) {};
   334 }

mercurial