Wed, 31 Dec 2014 06:09:35 +0100
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 }