Bug 874771 - Implement SNTP support (Gecko end). r=gene

This commit is contained in:
Shao Hang Kao 2013-06-22 22:22:05 +08:00
parent 242a797ff7
commit e39458f3a8
4 changed files with 487 additions and 33 deletions

View File

@ -747,6 +747,14 @@ pref("ping.manifestURL", "https://marketplace.firefox.com/packaged.webapp");
// Enable the disk space watcher
pref("disk_space_watcher.enabled", true);
// SNTP preferences.
pref("network.sntp.maxRetryCount", 10);
pref("network.sntp.refreshPeriod", 86400); // In seconds.
pref("network.sntp.pools", // Servers separated by ';'.
"0.pool.ntp.org;1.pool.ntp.org;2.pool.ntp.org;3.pool.ntp.org");
pref("network.sntp.port", 123);
pref("network.sntp.timeout", 30); // In seconds.
// Enable promise
pref("dom.promise.enabled", false);

View File

@ -19,6 +19,7 @@ const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/Sntp.jsm");
var RIL = {};
Cu.import("resource://gre/modules/ril_consts.js", RIL);
@ -58,8 +59,10 @@ const kMozSettingsChangedObserverTopic = "mozsettings-changed";
const kSysMsgListenerReadyObserverTopic = "system-message-listener-ready";
const kSysClockChangeObserverTopic = "system-clock-change";
const kScreenStateChangedTopic = "screen-state-changed";
const kTimeNitzAutomaticUpdateEnabled = "time.nitz.automatic-update.enabled";
const kTimeNitzAvailable = "time.nitz.available";
const kClockAutoUpdateEnabled = "time.clock.automatic-update.enabled";
const kClockAutoUpdateAvailable = "time.clock.automatic-update.available";
const kTimezoneAutoUpdateEnabled = "time.timezone.automatic-update.enabled";
const kTimezoneAutoUpdateAvailable = "time.timezone.automatic-update.available";
const kCellBroadcastSearchList = "ril.cellbroadcast.searchlist";
const kCellBroadcastDisabled = "ril.cellbroadcast.disabled";
const kPrefenceChangedObserverTopic = "nsPref:changed";
@ -709,12 +712,19 @@ function RadioInterface(options) {
lock.get("ril.data.enabled", this);
lock.get("ril.data.apnSettings", this);
// Read the 'time.nitz.automatic-update.enabled' setting to see if
// we need to adjust the system clock time and time zone by NITZ.
lock.get(kTimeNitzAutomaticUpdateEnabled, this);
// Read the 'time.clock.automatic-update.enabled' setting to see if
// we need to adjust the system clock time by NITZ or SNTP.
lock.get(kClockAutoUpdateEnabled, this);
// Set "time.nitz.available" to false when starting up.
this.setNitzAvailable(false);
// Read the 'time.timezone.automatic-update.enabled' setting to see if
// we need to adjust the system timezone by NITZ.
lock.get(kTimezoneAutoUpdateEnabled, this);
// Set "time.clock.automatic-update.available" to false when starting up.
this.setClockAutoUpdateAvailable(false);
// Set "time.timezone.automatic-update.available" to false when starting up.
this.setTimezoneAutoUpdateAvailable(false);
// Read the Cell Broadcast Search List setting, string of integers or integer
// ranges separated by comma, to set listening channels.
@ -726,11 +736,20 @@ function RadioInterface(options) {
Services.obs.addObserver(this, kSysClockChangeObserverTopic, false);
Services.obs.addObserver(this, kScreenStateChangedTopic, false);
Services.obs.addObserver(this, kNetworkInterfaceStateChangedTopic, false);
Services.prefs.addObserver(kCellBroadcastDisabled, this, false);
this.portAddressedSmsApps = {};
this.portAddressedSmsApps[WAP.WDP_PORT_PUSH] = this.handleSmsWdpPortPush.bind(this);
this._sntp = new Sntp(this.setClockBySntp.bind(this),
Services.prefs.getIntPref('network.sntp.maxRetryCount'),
Services.prefs.getIntPref('network.sntp.refreshPeriod'),
Services.prefs.getIntPref('network.sntp.timeout'),
Services.prefs.getCharPref('network.sntp.pools').split(';'),
Services.prefs.getIntPref('network.sntp.port'));
}
RadioInterface.prototype = {
classID: RADIOINTERFACE_CID,
@ -1860,22 +1879,35 @@ RadioInterface.prototype = {
},
/**
* Set the setting value of "time.nitz.available".
* Set the setting value of "time.clock.automatic-update.available".
*/
setNitzAvailable: function setNitzAvailable(value) {
gSettingsService.createLock().set(kTimeNitzAvailable, value, null,
setClockAutoUpdateAvailable: function setClockAutoUpdateAvailable(value) {
gSettingsService.createLock().set(kClockAutoUpdateAvailable, value, null,
"fromInternalSetting");
},
/**
* Set the NITZ message in our system time.
* Set the setting value of "time.timezone.automatic-update.available".
*/
setNitzTime: function setNitzTime(message) {
setTimezoneAutoUpdateAvailable: function setTimezoneAutoUpdateAvailable(value) {
gSettingsService.createLock().set(kTimezoneAutoUpdateAvailable, value, null,
"fromInternalSetting");
},
/**
* Set the system clock by NITZ.
*/
setClockByNitz: function setClockByNitz(message) {
// To set the system clock time. Note that there could be a time diff
// between when the NITZ was received and when the time is actually set.
gTimeService.set(
message.networkTimeInMS + (Date.now() - message.receiveTimeInMS));
},
/**
* Set the system time zone by NITZ.
*/
setTimezoneByNitz: function setTimezoneByNitz(message) {
// To set the sytem timezone. Note that we need to convert the time zone
// value to a UTC repesentation string in the format of "UTC(+/-)hh:mm".
// Ex, time zone -480 is "UTC-08:00"; time zone 630 is "UTC+10:30".
@ -1898,15 +1930,36 @@ RadioInterface.prototype = {
*/
handleNitzTime: function handleNitzTime(message) {
// Got the NITZ info received from the ril_worker.
this.setNitzAvailable(true);
this.setClockAutoUpdateAvailable(true);
this.setTimezoneAutoUpdateAvailable(true);
// Cache the latest NITZ message whenever receiving it.
this._lastNitzMessage = message;
// Set the received NITZ time if the setting is enabled.
if (this._nitzAutomaticUpdateEnabled) {
this.setNitzTime(message);
// Set the received NITZ clock if the setting is enabled.
if (this._clockAutoUpdateEnabled) {
this.setClockByNitz(message);
}
// Set the received NITZ timezone if the setting is enabled.
if (this._timezoneAutoUpdateEnabled) {
this.setTimezoneByNitz(message);
}
},
/**
* Set the system clock by SNTP.
*/
setClockBySntp: function setClockBySntp(offset) {
// Got the SNTP info.
this.setClockAutoUpdateAvailable(true);
if (!this._clockAutoUpdateEnabled) {
return;
}
if (this._lastNitzMessage) {
debug("SNTP: NITZ available, discard SNTP");
return;
}
gTimeService.set(Date.now() + offset);
},
handleIccMbdn: function handleIccMbdn(message) {
@ -2023,11 +2076,24 @@ RadioInterface.prototype = {
Services.obs.removeObserver(this, kMozSettingsChangedObserverTopic);
Services.obs.removeObserver(this, kSysClockChangeObserverTopic);
Services.obs.removeObserver(this, kScreenStateChangedTopic);
Services.obs.removeObserver(this, kNetworkInterfaceStateChangedTopic);
Services.prefs.removeObserver(kCellBroadcastDisabled, this);
break;
case kSysClockChangeObserverTopic:
let offset = parseInt(data, 10);
if (this._lastNitzMessage) {
this._lastNitzMessage.receiveTimeInMS += parseInt(data, 10);
this._lastNitzMessage.receiveTimeInMS += offset;
}
this._sntp.updateOffset(offset);
break;
case kNetworkInterfaceStateChangedTopic:
let network = subject.QueryInterface(Ci.nsINetworkInterface);
if (network.state == Ci.nsINetworkInterface.NETWORK_STATE_CONNECTED) {
// Check SNTP when we have data connection, this may not take
// effect immediately before the setting get enabled.
if (this._sntp.isExpired()) {
this._sntp.request();
}
}
break;
case kScreenStateChangedTopic:
@ -2053,28 +2119,51 @@ RadioInterface.prototype = {
apnSettings: null,
// Flag to determine whether to use NITZ. It corresponds to the
// 'time.nitz.automatic-update.enabled' setting from the UI.
_nitzAutomaticUpdateEnabled: null,
// Flag to determine whether to update system clock automatically. It
// corresponds to the 'time.clock.automatic-update.enabled' setting.
_clockAutoUpdateEnabled: null,
// Flag to determine whether to update system timezone automatically. It
// corresponds to the 'time.clock.automatic-update.enabled' setting.
_timezoneAutoUpdateEnabled: null,
// Remember the last NITZ message so that we can set the time based on
// the network immediately when users enable network-based time.
_lastNitzMessage: null,
// Object that handles SNTP.
_sntp: null,
// Cell Broadcast settings values.
_cellBroadcastSearchListStr: null,
handleSettingsChange: function handleSettingsChange(aName, aResult, aMessage) {
// Don't allow any content processes to modify the setting
// "time.nitz.available" except for the chrome process.
let isNitzAvailable = (this._lastNitzMessage !== null);
if (aName === kTimeNitzAvailable && aMessage !== "fromInternalSetting" &&
aResult !== isNitzAvailable) {
if (DEBUG) {
this.debug("Content processes cannot modify 'time.nitz.available'. Restore!");
// "time.clock.automatic-update.available" except for the chrome process.
if (aName === kClockAutoUpdateAvailable &&
aMessage !== "fromInternalSetting") {
let isClockAutoUpdateAvailable = this._lastNitzMessage !== null ||
this._sntp.isAvailable();
if (aResult !== isClockAutoUpdateAvailable) {
debug("Content processes cannot modify 'time.clock.automatic-update.available'. Restore!");
// Restore the setting to the current value.
this.setClockAutoUpdateAvailable(isClockAutoUpdateAvailable);
}
}
// Don't allow any content processes to modify the setting
// "time.timezone.automatic-update.available" except for the chrome
// process.
if (aName === kTimezoneAutoUpdateAvailable &&
aMessage !== "fromInternalSetting") {
let isTimezoneAutoUpdateAvailable = this._lastNitzMessage !== null;
if (aResult !== isTimezoneAutoUpdateAvailable) {
if (DEBUG) {
this.debug("Content processes cannot modify 'time.timezone.automatic-update.available'. Restore!");
}
// Restore the setting to the current value.
this.setTimezoneAutoUpdateAvailable(isTimezoneAutoUpdateAvailable);
}
// Restore the setting to the current value.
this.setNitzAvailable(isNitzAvailable);
}
this.handle(aName, aResult);
@ -2117,12 +2206,34 @@ RadioInterface.prototype = {
this.updateRILNetworkInterface();
}
break;
case kTimeNitzAutomaticUpdateEnabled:
this._nitzAutomaticUpdateEnabled = aResult;
case kClockAutoUpdateEnabled:
this._clockAutoUpdateEnabled = aResult;
if (!this._clockAutoUpdateEnabled) {
break;
}
// Set the latest cached NITZ time if the setting is enabled.
if (this._nitzAutomaticUpdateEnabled && this._lastNitzMessage) {
this.setNitzTime(this._lastNitzMessage);
// Set the latest cached NITZ time if it's available.
if (this._lastNitzMessage) {
this.setClockByNitz(this._lastNitzMessage);
} else if (gNetworkManager.active && gNetworkManager.active.state ==
Ci.nsINetworkInterface.NETWORK_STATE_CONNECTED) {
// Set the latest cached SNTP time if it's available.
if (!this._sntp.isExpired()) {
this.setClockBySntp(this._sntp.getOffset());
} else {
// Or refresh the SNTP.
this._sntp.request();
}
}
break;
case kTimezoneAutoUpdateEnabled:
this._timezoneAutoUpdateEnabled = aResult;
if (this._timezoneAutoUpdateEnabled) {
// Apply the latest cached NITZ for timezone if it's available.
if (this._timezoneAutoUpdateEnabled && this._lastNitzMessage) {
this.setTimezoneByNitz(this._lastNitzMessage);
}
}
break;
case kCellBroadcastSearchList:

334
toolkit/modules/Sntp.jsm Normal file
View File

@ -0,0 +1,334 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
this.EXPORTED_SYMBOLS = [
"Sntp",
];
const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
// Set to true to see debug messages.
let DEBUG = false;
/**
* Constructor of Sntp.
*
* @param dataAvailableCb
* Callback function gets called when SNTP offset available. Signature
* is function dataAvailableCb(offsetInMS).
* @param maxRetryCount
* Maximum retry count when SNTP failed to connect to server; set to
* zero to disable the retry.
* @param refreshPeriodInSecs
* Refresh period; set to zero to disable refresh.
* @param timeoutInSecs
* Timeout value used for connection.
* @param pools
* SNTP server lists separated by ';'.
* @param port
* SNTP port.
*/
this.Sntp = function Sntp(dataAvailableCb, maxRetryCount, refreshPeriodInSecs,
timeoutInSecs, pools, port) {
if (dataAvailableCb != null) {
this._dataAvailableCb = dataAvailableCb;
}
if (maxRetryCount != null) {
this._maxRetryCount = maxRetryCount;
}
if (refreshPeriodInSecs != null) {
this._refreshPeriodInMS = refreshPeriodInSecs * 1000;
}
if (timeoutInSecs != null) {
this._timeoutInMS = timeoutInSecs * 1000;
}
if (pools != null && Array.isArray(pools) && pools.length > 0) {
this._pools = pools;
}
if (port != null) {
this._port = port;
}
}
Sntp.prototype = {
isAvailable: function isAvailable() {
return this._cachedOffset != null;
},
isExpired: function isExpired() {
let valid = this._cachedOffset != null && this._cachedTimeInMS != null;
if (this._refreshPeriodInMS > 0) {
valid = valid && Date.now() < this._cachedTimeInMS +
this._refreshPeriodInMS;
}
return !valid;
},
request: function request() {
this._request();
},
getOffset: function getOffset() {
return this._cachedOffset;
},
/**
* Indicates the system clock has been changed by [offset]ms so we need to
* adjust the stored value.
*/
updateOffset: function updateOffset(offset) {
if (this._cachedOffset != null) {
this._cachedOffset -= offset;
}
},
/**
* Used to schedule a retry or periodic updates.
*/
_schedule: function _schedule(timeInMS) {
if (this._updateTimer == null) {
this._updateTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
}
this._updateTimer.initWithCallback(this._request.bind(this),
timeInMS,
Ci.nsITimer.TYPE_ONE_SHOT);
debug("Scheduled SNTP request in " + timeInMS + "ms");
},
/**
* Handle the SNTP response.
*/
_handleSntp: function _handleSntp(originateTimeInMS, receiveTimeInMS,
transmitTimeInMS, respondTimeInMS) {
let clockOffset = Math.floor(((receiveTimeInMS - originateTimeInMS) +
(transmitTimeInMS - respondTimeInMS)) / 2);
debug("Clock offset: " + clockOffset);
// We've succeeded so clear the retry status.
this._retryCount = 0;
this._retryPeriodInMS = 0;
// Cache the latest SNTP offset whenever receiving it.
this._cachedOffset = clockOffset;
this._cachedTimeInMS = respondTimeInMS;
if (this._dataAvailableCb != null) {
this._dataAvailableCb(clockOffset);
}
this._schedule(this._refreshPeriodInMS);
},
/**
* Used for retry SNTP requests.
*/
_retry: function _retry() {
this._retryCount++;
if (this._retryCount > this._maxRetryCount) {
debug ("stop retrying SNTP");
// Clear so we can start with clean status next time we have network.
this._retryCount = 0;
this._retryPeriodInMS = 0;
return;
}
this._retryPeriodInMS = Math.max(1000, this._retryPeriodInMS * 2);
this._schedule(this._retryPeriodInMS);
},
/**
* Request SNTP.
*/
_request: function _request() {
function GetRequest() {
let NTP_PACKET_SIZE = 48;
let NTP_MODE_CLIENT = 3;
let NTP_VERSION = 3;
let TRANSMIT_TIME_OFFSET = 40;
// Send the NTP request.
let requestTimeInMS = Date.now();
let s = requestTimeInMS / 1000;
let ms = requestTimeInMS % 1000;
// NTP time is relative to 1900.
s += OFFSET_1900_TO_1970;
let f = ms * 0x100000000 / 1000;
s = Math.floor(s);
f = Math.floor(f);
let buffer = new ArrayBuffer(NTP_PACKET_SIZE);
let data = new DataView(buffer);
data.setUint8(0, NTP_MODE_CLIENT | (NTP_VERSION << 3));
data.setUint32(TRANSMIT_TIME_OFFSET, s, false);
data.setUint32(TRANSMIT_TIME_OFFSET + 4, f, false);
return String.fromCharCode.apply(null, new Uint8Array(buffer));
}
function SNTPListener() {};
SNTPListener.prototype = {
onStartRequest: function onStartRequest(request, context) {
},
onStopRequest: function onStopRequest(request, context, status) {
if (!Components.isSuccessCode(status)) {
debug ("Connection failed");
this._requesting = false;
this._retry();
}
}.bind(this),
onDataAvailable: function onDataAvailable(request, context, inputStream,
offset, count) {
function GetTimeStamp(binaryInputStream) {
let s = binaryInputStream.read32();
let f = binaryInputStream.read32();
return Math.floor(
((s - OFFSET_1900_TO_1970) * 1000) + ((f * 1000) / 0x100000000)
);
}
debug ("Data available: " + count + " bytes");
try {
let binaryInputStream = Cc["@mozilla.org/binaryinputstream;1"]
.createInstance(Ci.nsIBinaryInputStream);
binaryInputStream.setInputStream(inputStream);
// We don't need first 24 bytes.
for (let i = 0; i < 6; i++) {
binaryInputStream.read32();
}
// Offset 24: originate time.
let originateTimeInMS = GetTimeStamp(binaryInputStream);
// Offset 32: receive time.
let receiveTimeInMS = GetTimeStamp(binaryInputStream);
// Offset 40: transmit time.
let transmitTimeInMS = GetTimeStamp(binaryInputStream);
let respondTimeInMS = Date.now();
this._handleSntp(originateTimeInMS, receiveTimeInMS,
transmitTimeInMS, respondTimeInMS);
this._requesting = false;
} catch (e) {
debug ("SNTPListener Error: " + e.message);
this._requesting = false;
this._retry();
}
inputStream.close();
}.bind(this)
};
function SNTPRequester() {}
SNTPRequester.prototype = {
onOutputStreamReady: function(stream) {
try {
let data = GetRequest();
let bytes_write = stream.write(data, data.length);
debug ("SNTP: sent " + bytes_write + " bytes");
stream.close();
} catch (e) {
debug ("SNTPRequester Error: " + e.message);
this._requesting = false;
this._retry();
}
}.bind(this)
};
// Number of seconds between Jan 1, 1900 and Jan 1, 1970.
// 70 years plus 17 leap days.
let OFFSET_1900_TO_1970 = ((365 * 70) + 17) * 24 * 60 * 60;
if (this._requesting) {
return;
}
if (this._pools.length < 1) {
debug("No server defined");
return;
}
if (this._updateTimer) {
this._updateTimer.cancel();
}
debug ("Making request");
this._requesting = true;
let currentThread = Cc["@mozilla.org/thread-manager;1"]
.getService().currentThread;
let socketTransportService =
Cc["@mozilla.org/network/socket-transport-service;1"]
.getService(Ci.nsISocketTransportService);
let pump = Cc["@mozilla.org/network/input-stream-pump;1"]
.createInstance(Ci.nsIInputStreamPump);
let transport = socketTransportService
.createTransport(["udp"],
1,
this._pools[Math.floor(this._pools.length * Math.random())],
this._port,
null);
transport.setTimeout(Ci.nsISocketTransport.TIMEOUT_CONNECT, this._timeoutInMS);
transport.setTimeout(Ci.nsISocketTransport.TIMEOUT_READ_WRITE, this._timeoutInMS);
let outStream = transport.openOutputStream(0, 0, 0)
.QueryInterface(Ci.nsIAsyncOutputStream);
let inStream = transport.openInputStream(0, 0, 0);
pump.init(inStream, -1, -1, 0, 0, false);
pump.asyncRead(new SNTPListener(), null);
outStream.asyncWait(new SNTPRequester(), 0, 0, currentThread);
},
// Callback function.
_dataAvailableCb: null,
// Sntp servers.
_pools: [
"0.pool.ntp.org",
"1.pool.ntp.org",
"2.pool.ntp.org",
"3.pool.ntp.org"
],
// The SNTP port.
_port: 123,
// Maximum retry count allowed when request failed.
_maxRetryCount: 0,
// Refresh period.
_refreshPeriodInMS: 0,
// Timeout value used for connecting.
_timeoutInMS: 30 * 1000,
// Cached SNTP offset.
_cachedOffset: null,
// The time point when we cache the offset.
_cachedTimeInMS: null,
// Flag to avoid redundant requests.
_requesting: false,
// Retry counter.
_retryCount: 0,
// Retry time offset (in seconds).
_retryPeriodInMS: 0,
// Timer used for retries & daily updates.
_updateTimer: null
};
let debug;
if (DEBUG) {
debug = function (s) {
dump("-*- Sntp: " + s + "\n");
};
} else {
debug = function (s) {};
}

View File

@ -29,6 +29,7 @@ EXTRA_JS_MODULES += [
'RemoteWebProgress.jsm',
'SelectContentHelper.jsm',
'SelectParentHelper.jsm',
'Sntp.jsm',
'Sqlite.jsm',
'Task.jsm',
'TelemetryTimestamps.jsm',