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.

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

mercurial