diff options
Diffstat (limited to 'dom/system/gonk/tests')
54 files changed, 26190 insertions, 0 deletions
diff --git a/dom/system/gonk/tests/header_helpers.js b/dom/system/gonk/tests/header_helpers.js new file mode 100644 index 000000000..8d1144f75 --- /dev/null +++ b/dom/system/gonk/tests/header_helpers.js @@ -0,0 +1,217 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +var {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components; + + +var subscriptLoader = Cc["@mozilla.org/moz/jssubscript-loader;1"] + .getService(Ci.mozIJSSubScriptLoader); + +/** + * Start a new RIL worker. + * + * @param custom_ns + * Namespace with symbols to be injected into the new worker + * namespace. + * + * @return an object that represents the worker's namespace. + * + * @note that this does not start an actual worker thread. The worker + * is executed on the main thread, within a separate namespace object. + */ +function newWorker(custom_ns) { + let worker_ns = { + importScripts: function() { + Array.slice(arguments).forEach(function(script) { + if (!script.startsWith("resource:")) { + script = "resource://gre/modules/" + script; + } + subscriptLoader.loadSubScript(script, this); + }, this); + }, + + postRILMessage: function(message) { + }, + + postMessage: function(message) { + }, + + // Define these variables inside the worker scope so ES5 strict mode + // doesn't flip out. + onmessage: undefined, + onerror: undefined, + + DEBUG: true + }; + // The 'self' variable in a worker points to the worker's own namespace. + worker_ns.self = worker_ns; + + // Copy the custom definitions over. + for (let key in custom_ns) { + worker_ns[key] = custom_ns[key]; + } + + // fake require() for toolkit/components/workerloader/require.js + let require = (function() { + return function require(script) { + worker_ns.module = {}; + worker_ns.importScripts(script); + return worker_ns; + } + })(); + + Object.freeze(require); + Object.defineProperty(worker_ns, "require", { + value: require, + enumerable: true, + configurable: false + }); + + // Load the RIL worker itself. + worker_ns.importScripts("ril_worker.js"); + + // Register at least one client. + worker_ns.ContextPool.registerClient({ clientId: 0 }); + + return worker_ns; +} + +/** + * Create a buffered RIL worker. + * + * @return A worker object that stores sending octets in a internal buffer. + */ +function newUint8Worker() { + let worker = newWorker(); + let index = 0; // index for read + let buf = []; + + let context = worker.ContextPool._contexts[0]; + context.Buf.writeUint8 = function(value) { + buf.push(value); + }; + + context.Buf.readUint8 = function() { + return buf[index++]; + }; + + context.Buf.seekIncoming = function(offset) { + index += offset; + }; + + context.Buf.getReadAvailable = function() { + return buf.length - index; + }; + + worker.debug = do_print; + + return worker; +} + +/** + * Create a worker that keeps posted chrome message. + */ +function newInterceptWorker() { + let postedMessage; + let worker = newWorker({ + postRILMessage: function(data) { + }, + postMessage: function(message) { + postedMessage = message; + } + }); + return { + get postedMessage() { + return postedMessage; + }, + get worker() { + return worker; + } + }; +} + +/** + * Create a parcel suitable for postRILMessage(). + * + * @param fakeParcelSize + * Value to be written to parcel size field for testing + * incorrect/incomplete parcel reading. Replaced with correct + * one determined length of data if negative. + * @param response + * Response code of the incoming parcel. + * @param request + * Request code of the incoming parcel. + * @param data + * Extra data to be appended. + * + * @return an Uint8Array carrying all parcel data. + */ +function newIncomingParcel(fakeParcelSize, response, request, data) { + const UINT32_SIZE = 4; + const PARCEL_SIZE_SIZE = 4; + + let realParcelSize = data.length + 2 * UINT32_SIZE; + let buffer = new ArrayBuffer(realParcelSize + PARCEL_SIZE_SIZE); + let bytes = new Uint8Array(buffer); + + let writeIndex = 0; + function writeUint8(value) { + bytes[writeIndex] = value; + ++writeIndex; + } + + function writeInt32(value) { + writeUint8(value & 0xff); + writeUint8((value >> 8) & 0xff); + writeUint8((value >> 16) & 0xff); + writeUint8((value >> 24) & 0xff); + } + + function writeParcelSize(value) { + writeUint8((value >> 24) & 0xff); + writeUint8((value >> 16) & 0xff); + writeUint8((value >> 8) & 0xff); + writeUint8(value & 0xff); + } + + if (fakeParcelSize < 0) { + fakeParcelSize = realParcelSize; + } + writeParcelSize(fakeParcelSize); + + writeInt32(response); + writeInt32(request); + + // write parcel data + for (let ii = 0; ii < data.length; ++ii) { + writeUint8(data[ii]); + } + + return bytes; +} + +/** + * Create a parcel buffer which represents the hex string. + * + * @param hexString + * The HEX string to be converted. + * + * @return an Uint8Array carrying all parcel data. + */ +function hexStringToParcelByteArrayData(hexString) { + let length = Math.ceil((hexString.length / 2)); + let bytes = new Uint8Array(4 + length); + + bytes[0] = length & 0xFF; + bytes[1] = (length >> 8) & 0xFF; + bytes[2] = (length >> 16) & 0xFF; + bytes[3] = (length >> 24) & 0xFF; + + for (let i = 0; i < length; i ++) { + bytes[i + 4] = Number.parseInt(hexString.substr(i * 2, 2), 16); + } + + return bytes; +} diff --git a/dom/system/gonk/tests/marionette/head.js b/dom/system/gonk/tests/marionette/head.js new file mode 100644 index 000000000..5a6ee1272 --- /dev/null +++ b/dom/system/gonk/tests/marionette/head.js @@ -0,0 +1,345 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +MARIONETTE_CONTEXT = "chrome"; + +const SETTINGS_KEY_DATA_ENABLED = "ril.data.enabled"; +const SETTINGS_KEY_DATA_APN_SETTINGS = "ril.data.apnSettings"; +const SETTINGS_KEY_WIFI_ENABLED = "wifi.enabled"; + +const TOPIC_CONNECTION_STATE_CHANGED = "network-connection-state-changed"; +const TOPIC_NETWORK_ACTIVE_CHANGED = "network-active-changed"; + +const NETWORK_STATE_UNKNOWN = Ci.nsINetworkInfo.NETWORK_STATE_UNKNOWN; +const NETWORK_STATE_CONNECTING = Ci.nsINetworkInfo.NETWORK_STATE_CONNECTING; +const NETWORK_STATE_CONNECTED = Ci.nsINetworkInfo.NETWORK_STATE_CONNECTED; +const NETWORK_STATE_DISCONNECTING = Ci.nsINetworkInfo.NETWORK_STATE_DISCONNECTING; +const NETWORK_STATE_DISCONNECTED = Ci.nsINetworkInfo.NETWORK_STATE_DISCONNECTED; + +const NETWORK_TYPE_MOBILE = Ci.nsINetworkInfo.NETWORK_TYPE_MOBILE; +const NETWORK_TYPE_MOBILE_MMS = Ci.nsINetworkInfo.NETWORK_TYPE_MOBILE_MMS; +const NETWORK_TYPE_MOBILE_SUPL = Ci.nsINetworkInfo.NETWORK_TYPE_MOBILE_SUPL; +const NETWORK_TYPE_MOBILE_IMS = Ci.nsINetworkInfo.NETWORK_TYPE_MOBILE_IMS; +const NETWORK_TYPE_MOBILE_DUN = Ci.nsINetworkInfo.NETWORK_TYPE_MOBILE_DUN; +const NETWORK_TYPE_MOBILE_FOTA = Ci.nsINetworkInfo.NETWORK_TYPE_MOBILE_FOTA; + +const networkTypes = [ + NETWORK_TYPE_MOBILE, + NETWORK_TYPE_MOBILE_MMS, + NETWORK_TYPE_MOBILE_SUPL, + NETWORK_TYPE_MOBILE_IMS, + NETWORK_TYPE_MOBILE_DUN, + NETWORK_TYPE_MOBILE_FOTA +]; + +var Promise = Cu.import("resource://gre/modules/Promise.jsm").Promise; + +var ril = Cc["@mozilla.org/ril;1"].getService(Ci.nsIRadioInterfaceLayer); +ok(ril, "ril.constructor is " + ril.constructor); + +var radioInterface = ril.getRadioInterface(0); +ok(radioInterface, "radioInterface.constructor is " + radioInterface.constrctor); + +var _pendingEmulatorShellCmdCount = 0; +var _pendingEmulatorCmdCount = 0; + +/** + * Send emulator shell command with safe guard. + * + * We should only call |finish()| after all emulator shell command transactions + * end, so here comes with the pending counter. Resolve when the emulator + * shell gives response. Never reject. + * + * Fulfill params: + * result -- an array of emulator shell response lines. + * + * @param aCommands + * A string array commands to be passed to emulator through adb shell. + * + * @return A deferred promise. + */ +function runEmulatorShellCmdSafe(aCommands) { + return new Promise(function(aResolve, aReject) { + ++_pendingEmulatorShellCmdCount; + runEmulatorShell(aCommands, function(aResult) { + --_pendingEmulatorShellCmdCount; + + log("Emulator shell response: " + JSON.stringify(aResult)); + aResolve(aResult); + }); + }); +} + +/** + * Send emulator command with safe guard. + * + * We should only call |finish()| after all emulator command transactions + * end, so here comes with the pending counter. Resolve when the emulator + * gives positive response, and reject otherwise. + * + * Fulfill params: + * result -- an array of emulator response lines. + * Reject params: + * result -- an array of emulator response lines. + * + * @param aCommand + * A string command to be passed to emulator through its telnet console. + * + * @return A deferred promise. + */ +function runEmulatorCmdSafe(aCommand) { + log(aCommand); + return new Promise(function(aResolve, aReject) { + ++_pendingEmulatorCmdCount; + runEmulatorCmd(aCommand, function(aResult) { + --_pendingEmulatorCmdCount; + + log("Emulator console response: " + JSON.stringify(aResult)); + if (Array.isArray(aResult) && + aResult[aResult.length - 1] === "OK") { + aResolve(aResult); + } else { + aReject(aResult); + } + }); + }); +} + +/** + * Get mozSettings value specified by @aKey. + * + * Resolve if that mozSettings value is retrieved successfully, reject + * otherwise. + * + * Fulfill params: The corresponding mozSettings value of the key. + * Reject params: (none) + * + * @param aKey + * A string. + * @param aAllowError [optional] + * A boolean value. If set to true, an error response won't be treated + * as test failure. Default: false. + * + * @return A deferred promise. + */ +function getSettings(aKey, aAllowError) { + let request = window.navigator.mozSettings.createLock().get(aKey); + return request.then(function resolve(aValue) { + log("getSettings(" + aKey + ") - success"); + return aValue[aKey]; + }, function reject(aError) { + ok(aAllowError, "getSettings(" + aKey + ") - error"); + }); +} + +/** + * Set mozSettings values. + * + * Resolve if that mozSettings value is set successfully, reject otherwise. + * + * Fulfill params: (none) + * Reject params: (none) + * + * @param aKey + * A string key. + * @param aValue + * An object value. + * @param aAllowError [optional] + * A boolean value. If set to true, an error response won't be treated + * as test failure. Default: false. + * + * @return A deferred promise. + */ +function setSettings(aKey, aValue, aAllowError) { + let settings = {}; + settings[aKey] = aValue; + let lock = window.navigator.mozSettings.createLock(); + let request = lock.set(settings); + let deferred = Promise.defer(); + lock.onsettingstransactionsuccess = function () { + log("setSettings(" + JSON.stringify(settings) + ") - success"); + deferred.resolve(); + }; + lock.onsettingstransactionfailure = function () { + ok(aAllowError, "setSettings(" + JSON.stringify(settings) + ") - error"); + // We resolve even though we've thrown an error, since the ok() + // will do that. + deferred.resolve(); + }; + return deferred.promise; +} + +/** + * Wait for observer event. + * + * Resolve if that topic event occurs. Never reject. + * + * Fulfill params: the subject passed. + * + * @param aTopic + * A string topic name. + * + * @return A deferred promise. + */ +function waitForObserverEvent(aTopic) { + let obs = Cc["@mozilla.org/observer-service;1"].getService(Ci.nsIObserverService); + let deferred = Promise.defer(); + + obs.addObserver(function observer(subject, topic, data) { + if (topic === aTopic) { + obs.removeObserver(observer, aTopic); + deferred.resolve(subject); + } + }, aTopic, false); + + return deferred.promise; +} + +/** + * Wait for one named event. + * + * Resolve if that named event occurs. Never reject. + * + * Fulfill params: the DOMEvent passed. + * + * @param aEventTarget + * An EventTarget object. + * @param aEventName + * A string event name. + * @param aMatchFun [optional] + * A matching function returns true or false to filter the event. + * + * @return A deferred promise. + */ +function waitForTargetEvent(aEventTarget, aEventName, aMatchFun) { + return new Promise(function(aResolve, aReject) { + aEventTarget.addEventListener(aEventName, function onevent(aEvent) { + if (!aMatchFun || aMatchFun(aEvent)) { + aEventTarget.removeEventListener(aEventName, onevent); + ok(true, "Event '" + aEventName + "' got."); + aResolve(aEvent); + } + }); + }); +} + +/** + * Set the default data connection enabling state, wait for + * "network-connection-state-changed" event and verify state. + * + * Fulfill params: instance of nsIRilNetworkInfo of the network connected. + * + * @param aEnabled + * A boolean state. + * + * @return A deferred promise. + */ +function setDataEnabledAndWait(aEnabled) { + let promises = []; + promises.push(waitForObserverEvent(TOPIC_CONNECTION_STATE_CHANGED) + .then(function(aSubject) { + ok(aSubject instanceof Ci.nsIRilNetworkInfo, + "subject should be an instance of nsIRilNetworkInfo"); + is(aSubject.type, NETWORK_TYPE_MOBILE, + "subject.type should be " + NETWORK_TYPE_MOBILE); + is(aSubject.state, + aEnabled ? Ci.nsINetworkInfo.NETWORK_STATE_CONNECTED + : Ci.nsINetworkInfo.NETWORK_STATE_DISCONNECTED, + "subject.state should be " + aEnabled ? "CONNECTED" : "DISCONNECTED"); + + return aSubject; + })); + promises.push(setSettings(SETTINGS_KEY_DATA_ENABLED, aEnabled)); + + return Promise.all(promises).then(aValues => aValues[0]); +} + +/** + * Setup a certain type of data connection, wait for + * "network-connection-state-changed" event and verify state. + * + * Fulfill params: instance of nsIRilNetworkInfo of the network connected. + * + * @param aNetworkType + * The mobile network type to setup. + * + * @return A deferred promise. + */ +function setupDataCallAndWait(aNetworkType) { + log("setupDataCallAndWait: " + aNetworkType); + + let promises = []; + promises.push(waitForObserverEvent(TOPIC_CONNECTION_STATE_CHANGED) + .then(function(aSubject) { + ok(aSubject instanceof Ci.nsIRilNetworkInfo, + "subject should be an instance of nsIRilNetworkInfo"); + is(aSubject.type, aNetworkType, + "subject.type should be " + aNetworkType); + is(aSubject.state, Ci.nsINetworkInfo.NETWORK_STATE_CONNECTED, + "subject.state should be CONNECTED"); + + return aSubject; + })); + promises.push(radioInterface.setupDataCallByType(aNetworkType)); + + return Promise.all(promises).then(aValues => aValues[0]); +} + +/** + * Deactivate a certain type of data connection, wait for + * "network-connection-state-changed" event and verify state. + * + * Fulfill params: (none) + * + * @param aNetworkType + * The mobile network type to deactivate. + * + * @return A deferred promise. + */ +function deactivateDataCallAndWait(aNetworkType) { + log("deactivateDataCallAndWait: " + aNetworkType); + + let promises = []; + promises.push(waitForObserverEvent(TOPIC_CONNECTION_STATE_CHANGED) + .then(function(aSubject) { + ok(aSubject instanceof Ci.nsIRilNetworkInfo, + "subject should be an instance of nsIRilNetworkInfo"); + is(aSubject.type, aNetworkType, + "subject.type should be " + aNetworkType); + is(aSubject.state, Ci.nsINetworkInfo.NETWORK_STATE_DISCONNECTED, + "subject.state should be DISCONNECTED"); + })); + promises.push(radioInterface.deactivateDataCallByType(aNetworkType)); + + return Promise.all(promises); +} + +/** + * Wait for pending emulator transactions and call |finish()|. + */ +function cleanUp() { + // Use ok here so that we have at least one test run. + ok(true, ":: CLEANING UP ::"); + + waitFor(finish, function() { + return _pendingEmulatorShellCmdCount === 0 && + _pendingEmulatorCmdCount === 0; + }); +} + +/** + * Basic test routine helper. + * + * This helper does nothing but clean-ups. + * + * @param aTestCaseMain + * A function that takes no parameter. + */ +function startTestBase(aTestCaseMain) { + Promise.resolve() + .then(aTestCaseMain) + .then(cleanUp, function(aException) { + ok(false, "promise rejects during test: " + aException); + cleanUp(); + }); +} diff --git a/dom/system/gonk/tests/marionette/manifest.ini b/dom/system/gonk/tests/marionette/manifest.ini new file mode 100644 index 000000000..528fe3baf --- /dev/null +++ b/dom/system/gonk/tests/marionette/manifest.ini @@ -0,0 +1,19 @@ +[DEFAULT] +run-if = buildapp == 'b2g' + +[test_geolocation.js] +skip-if = true # Bug 808783 +[test_fakevolume.js] +[test_ril_code_quality.py] +[test_screen_state.js] +[test_dsds_numRadioInterfaces.js] +[test_data_connection.js] +[test_network_active_changed.js] +[test_multiple_data_connection.js] +[test_data_connection_proxy.js] +[test_network_interface_list_service.js] +[test_all_network_info.js] +[test_network_interface_mtu.js] +skip-if = android_version < '19' +[test_timezone_changes.js] +skip-if = android_version < '19' diff --git a/dom/system/gonk/tests/marionette/ril_jshint/README.md b/dom/system/gonk/tests/marionette/ril_jshint/README.md new file mode 100644 index 000000000..a63967d63 --- /dev/null +++ b/dom/system/gonk/tests/marionette/ril_jshint/README.md @@ -0,0 +1,9 @@ +Test RIL Code Quality +===================== + +For more information, please refer to + +* Bug 880643 - B2G RIL: Add a code quality test on try server for RIL javascript code in gecko +* Slide: https://speakerdeck.com/aknow/improve-code-quality-of-ril-code-by-jshint +* Document: https://hackpad.com/Code-Quality-Test-For-RIL-Javascript-Code-In-Gecko-cz5j7YIGiw8 + diff --git a/dom/system/gonk/tests/marionette/ril_jshint/jshint.js b/dom/system/gonk/tests/marionette/ril_jshint/jshint.js new file mode 100644 index 000000000..ec5263a5b --- /dev/null +++ b/dom/system/gonk/tests/marionette/ril_jshint/jshint.js @@ -0,0 +1,11096 @@ +//2.1.3 +var JSHINT; +(function () { +var require; +require=(function(e,t,n){function i(n,s){if(!t[n]){if(!e[n]){var o=typeof require=="function"&&require;if(!s&&o)return o(n,!0);if(r)return r(n,!0);throw new Error("Cannot find module '"+n+"'")}var u=t[n]={exports:{}};e[n][0].call(u.exports,function(t){var r=e[n][1][t];return i(r?r:t)},u,u.exports)}return t[n].exports}var r=typeof require=="function"&&require;for(var s=0;s<n.length;s++)i(n[s]);return i})({1:[function(require,module,exports){ +// shim for using process in browser + +var process = module.exports = {}; + +process.nextTick = (function () { + var canSetImmediate = typeof window !== 'undefined' + && window.setImmediate; + var canPost = typeof window !== 'undefined' + && window.postMessage && window.addEventListener + ; + + if (canSetImmediate) { + return function (f) { return window.setImmediate(f) }; + } + + if (canPost) { + var queue = []; + window.addEventListener('message', function (ev) { + if (ev.source === window && ev.data === 'process-tick') { + ev.stopPropagation(); + if (queue.length > 0) { + var fn = queue.shift(); + fn(); + } + } + }, true); + + return function nextTick(fn) { + queue.push(fn); + window.postMessage('process-tick', '*'); + }; + } + + return function nextTick(fn) { + setTimeout(fn, 0); + }; +})(); + +process.title = 'browser'; +process.browser = true; +process.env = {}; +process.argv = []; + +process.binding = function (name) { + throw new Error('process.binding is not supported'); +} + +// TODO(shtylman) +process.cwd = function () { return '/' }; +process.chdir = function (dir) { + throw new Error('process.chdir is not supported'); +}; + +},{}],2:[function(require,module,exports){ +(function(process){if (!process.EventEmitter) process.EventEmitter = function () {}; + +var EventEmitter = exports.EventEmitter = process.EventEmitter; +var isArray = typeof Array.isArray === 'function' + ? Array.isArray + : function (xs) { + return Object.prototype.toString.call(xs) === '[object Array]' + } +; +function indexOf (xs, x) { + if (xs.indexOf) return xs.indexOf(x); + for (var i = 0; i < xs.length; i++) { + if (x === xs[i]) return i; + } + return -1; +} + +// By default EventEmitters will print a warning if more than +// 10 listeners are added to it. This is a useful default which +// helps finding memory leaks. +// +// Obviously not all Emitters should be limited to 10. This function allows +// that to be increased. Set to zero for unlimited. +var defaultMaxListeners = 10; +EventEmitter.prototype.setMaxListeners = function(n) { + if (!this._events) this._events = {}; + this._events.maxListeners = n; +}; + + +EventEmitter.prototype.emit = function(type) { + // If there is no 'error' event listener then throw. + if (type === 'error') { + if (!this._events || !this._events.error || + (isArray(this._events.error) && !this._events.error.length)) + { + if (arguments[1] instanceof Error) { + throw arguments[1]; // Unhandled 'error' event + } else { + throw new Error("Uncaught, unspecified 'error' event."); + } + return false; + } + } + + if (!this._events) return false; + var handler = this._events[type]; + if (!handler) return false; + + if (typeof handler == 'function') { + switch (arguments.length) { + // fast cases + case 1: + handler.call(this); + break; + case 2: + handler.call(this, arguments[1]); + break; + case 3: + handler.call(this, arguments[1], arguments[2]); + break; + // slower + default: + var args = Array.prototype.slice.call(arguments, 1); + handler.apply(this, args); + } + return true; + + } else if (isArray(handler)) { + var args = Array.prototype.slice.call(arguments, 1); + + var listeners = handler.slice(); + for (var i = 0, l = listeners.length; i < l; i++) { + listeners[i].apply(this, args); + } + return true; + + } else { + return false; + } +}; + +// EventEmitter is defined in src/node_events.cc +// EventEmitter.prototype.emit() is also defined there. +EventEmitter.prototype.addListener = function(type, listener) { + if ('function' !== typeof listener) { + throw new Error('addListener only takes instances of Function'); + } + + if (!this._events) this._events = {}; + + // To avoid recursion in the case that type == "newListeners"! Before + // adding it to the listeners, first emit "newListeners". + this.emit('newListener', type, listener); + + if (!this._events[type]) { + // Optimize the case of one listener. Don't need the extra array object. + this._events[type] = listener; + } else if (isArray(this._events[type])) { + + // Check for listener leak + if (!this._events[type].warned) { + var m; + if (this._events.maxListeners !== undefined) { + m = this._events.maxListeners; + } else { + m = defaultMaxListeners; + } + + if (m && m > 0 && this._events[type].length > m) { + this._events[type].warned = true; + console.error('(node) warning: possible EventEmitter memory ' + + 'leak detected. %d listeners added. ' + + 'Use emitter.setMaxListeners() to increase limit.', + this._events[type].length); + console.trace(); + } + } + + // If we've already got an array, just append. + this._events[type].push(listener); + } else { + // Adding the second element, need to change to array. + this._events[type] = [this._events[type], listener]; + } + + return this; +}; + +EventEmitter.prototype.on = EventEmitter.prototype.addListener; + +EventEmitter.prototype.once = function(type, listener) { + var self = this; + self.on(type, function g() { + self.removeListener(type, g); + listener.apply(this, arguments); + }); + + return this; +}; + +EventEmitter.prototype.removeListener = function(type, listener) { + if ('function' !== typeof listener) { + throw new Error('removeListener only takes instances of Function'); + } + + // does not use listeners(), so no side effect of creating _events[type] + if (!this._events || !this._events[type]) return this; + + var list = this._events[type]; + + if (isArray(list)) { + var i = indexOf(list, listener); + if (i < 0) return this; + list.splice(i, 1); + if (list.length == 0) + delete this._events[type]; + } else if (this._events[type] === listener) { + delete this._events[type]; + } + + return this; +}; + +EventEmitter.prototype.removeAllListeners = function(type) { + if (arguments.length === 0) { + this._events = {}; + return this; + } + + // does not use listeners(), so no side effect of creating _events[type] + if (type && this._events && this._events[type]) this._events[type] = null; + return this; +}; + +EventEmitter.prototype.listeners = function(type) { + if (!this._events) this._events = {}; + if (!this._events[type]) this._events[type] = []; + if (!isArray(this._events[type])) { + this._events[type] = [this._events[type]]; + } + return this._events[type]; +}; + +})(require("__browserify_process")) +},{"__browserify_process":1}],3:[function(require,module,exports){ +(function(){// jshint -W001 + +"use strict"; + +// Identifiers provided by the ECMAScript standard. + +exports.reservedVars = { + arguments : false, + NaN : false +}; + +exports.ecmaIdentifiers = { + Array : false, + Boolean : false, + Date : false, + decodeURI : false, + decodeURIComponent : false, + encodeURI : false, + encodeURIComponent : false, + Error : false, + "eval" : false, + EvalError : false, + Function : false, + hasOwnProperty : false, + isFinite : false, + isNaN : false, + JSON : false, + Math : false, + Map : false, + Number : false, + Object : false, + parseInt : false, + parseFloat : false, + RangeError : false, + ReferenceError : false, + RegExp : false, + Set : false, + String : false, + SyntaxError : false, + TypeError : false, + URIError : false, + WeakMap : false +}; + +// Global variables commonly provided by a web browser environment. + +exports.browser = { + ArrayBuffer : false, + ArrayBufferView : false, + Audio : false, + Blob : false, + addEventListener : false, + applicationCache : false, + atob : false, + blur : false, + btoa : false, + clearInterval : false, + clearTimeout : false, + close : false, + closed : false, + DataView : false, + DOMParser : false, + defaultStatus : false, + document : false, + Element : false, + ElementTimeControl : false, + event : false, + FileReader : false, + Float32Array : false, + Float64Array : false, + FormData : false, + focus : false, + frames : false, + getComputedStyle : false, + HTMLElement : false, + HTMLAnchorElement : false, + HTMLBaseElement : false, + HTMLBlockquoteElement: false, + HTMLBodyElement : false, + HTMLBRElement : false, + HTMLButtonElement : false, + HTMLCanvasElement : false, + HTMLDirectoryElement : false, + HTMLDivElement : false, + HTMLDListElement : false, + HTMLFieldSetElement : false, + HTMLFontElement : false, + HTMLFormElement : false, + HTMLFrameElement : false, + HTMLFrameSetElement : false, + HTMLHeadElement : false, + HTMLHeadingElement : false, + HTMLHRElement : false, + HTMLHtmlElement : false, + HTMLIFrameElement : false, + HTMLImageElement : false, + HTMLInputElement : false, + HTMLIsIndexElement : false, + HTMLLabelElement : false, + HTMLLayerElement : false, + HTMLLegendElement : false, + HTMLLIElement : false, + HTMLLinkElement : false, + HTMLMapElement : false, + HTMLMenuElement : false, + HTMLMetaElement : false, + HTMLModElement : false, + HTMLObjectElement : false, + HTMLOListElement : false, + HTMLOptGroupElement : false, + HTMLOptionElement : false, + HTMLParagraphElement : false, + HTMLParamElement : false, + HTMLPreElement : false, + HTMLQuoteElement : false, + HTMLScriptElement : false, + HTMLSelectElement : false, + HTMLStyleElement : false, + HTMLTableCaptionElement: false, + HTMLTableCellElement : false, + HTMLTableColElement : false, + HTMLTableElement : false, + HTMLTableRowElement : false, + HTMLTableSectionElement: false, + HTMLTextAreaElement : false, + HTMLTitleElement : false, + HTMLUListElement : false, + HTMLVideoElement : false, + history : false, + Int16Array : false, + Int32Array : false, + Int8Array : false, + Image : false, + length : false, + localStorage : false, + location : false, + MessageChannel : false, + MessageEvent : false, + MessagePort : false, + moveBy : false, + moveTo : false, + MutationObserver : false, + name : false, + Node : false, + NodeFilter : false, + navigator : false, + onbeforeunload : true, + onblur : true, + onerror : true, + onfocus : true, + onload : true, + onresize : true, + onunload : true, + open : false, + openDatabase : false, + opener : false, + Option : false, + parent : false, + print : false, + removeEventListener : false, + resizeBy : false, + resizeTo : false, + screen : false, + scroll : false, + scrollBy : false, + scrollTo : false, + sessionStorage : false, + setInterval : false, + setTimeout : false, + SharedWorker : false, + status : false, + SVGAElement : false, + SVGAltGlyphDefElement: false, + SVGAltGlyphElement : false, + SVGAltGlyphItemElement: false, + SVGAngle : false, + SVGAnimateColorElement: false, + SVGAnimateElement : false, + SVGAnimateMotionElement: false, + SVGAnimateTransformElement: false, + SVGAnimatedAngle : false, + SVGAnimatedBoolean : false, + SVGAnimatedEnumeration: false, + SVGAnimatedInteger : false, + SVGAnimatedLength : false, + SVGAnimatedLengthList: false, + SVGAnimatedNumber : false, + SVGAnimatedNumberList: false, + SVGAnimatedPathData : false, + SVGAnimatedPoints : false, + SVGAnimatedPreserveAspectRatio: false, + SVGAnimatedRect : false, + SVGAnimatedString : false, + SVGAnimatedTransformList: false, + SVGAnimationElement : false, + SVGCSSRule : false, + SVGCircleElement : false, + SVGClipPathElement : false, + SVGColor : false, + SVGColorProfileElement: false, + SVGColorProfileRule : false, + SVGComponentTransferFunctionElement: false, + SVGCursorElement : false, + SVGDefsElement : false, + SVGDescElement : false, + SVGDocument : false, + SVGElement : false, + SVGElementInstance : false, + SVGElementInstanceList: false, + SVGEllipseElement : false, + SVGExternalResourcesRequired: false, + SVGFEBlendElement : false, + SVGFEColorMatrixElement: false, + SVGFEComponentTransferElement: false, + SVGFECompositeElement: false, + SVGFEConvolveMatrixElement: false, + SVGFEDiffuseLightingElement: false, + SVGFEDisplacementMapElement: false, + SVGFEDistantLightElement: false, + SVGFEDropShadowElement: false, + SVGFEFloodElement : false, + SVGFEFuncAElement : false, + SVGFEFuncBElement : false, + SVGFEFuncGElement : false, + SVGFEFuncRElement : false, + SVGFEGaussianBlurElement: false, + SVGFEImageElement : false, + SVGFEMergeElement : false, + SVGFEMergeNodeElement: false, + SVGFEMorphologyElement: false, + SVGFEOffsetElement : false, + SVGFEPointLightElement: false, + SVGFESpecularLightingElement: false, + SVGFESpotLightElement: false, + SVGFETileElement : false, + SVGFETurbulenceElement: false, + SVGFilterElement : false, + SVGFilterPrimitiveStandardAttributes: false, + SVGFitToViewBox : false, + SVGFontElement : false, + SVGFontFaceElement : false, + SVGFontFaceFormatElement: false, + SVGFontFaceNameElement: false, + SVGFontFaceSrcElement: false, + SVGFontFaceUriElement: false, + SVGForeignObjectElement: false, + SVGGElement : false, + SVGGlyphElement : false, + SVGGlyphRefElement : false, + SVGGradientElement : false, + SVGHKernElement : false, + SVGICCColor : false, + SVGImageElement : false, + SVGLangSpace : false, + SVGLength : false, + SVGLengthList : false, + SVGLineElement : false, + SVGLinearGradientElement: false, + SVGLocatable : false, + SVGMPathElement : false, + SVGMarkerElement : false, + SVGMaskElement : false, + SVGMatrix : false, + SVGMetadataElement : false, + SVGMissingGlyphElement: false, + SVGNumber : false, + SVGNumberList : false, + SVGPaint : false, + SVGPathElement : false, + SVGPathSeg : false, + SVGPathSegArcAbs : false, + SVGPathSegArcRel : false, + SVGPathSegClosePath : false, + SVGPathSegCurvetoCubicAbs: false, + SVGPathSegCurvetoCubicRel: false, + SVGPathSegCurvetoCubicSmoothAbs: false, + SVGPathSegCurvetoCubicSmoothRel: false, + SVGPathSegCurvetoQuadraticAbs: false, + SVGPathSegCurvetoQuadraticRel: false, + SVGPathSegCurvetoQuadraticSmoothAbs: false, + SVGPathSegCurvetoQuadraticSmoothRel: false, + SVGPathSegLinetoAbs : false, + SVGPathSegLinetoHorizontalAbs: false, + SVGPathSegLinetoHorizontalRel: false, + SVGPathSegLinetoRel : false, + SVGPathSegLinetoVerticalAbs: false, + SVGPathSegLinetoVerticalRel: false, + SVGPathSegList : false, + SVGPathSegMovetoAbs : false, + SVGPathSegMovetoRel : false, + SVGPatternElement : false, + SVGPoint : false, + SVGPointList : false, + SVGPolygonElement : false, + SVGPolylineElement : false, + SVGPreserveAspectRatio: false, + SVGRadialGradientElement: false, + SVGRect : false, + SVGRectElement : false, + SVGRenderingIntent : false, + SVGSVGElement : false, + SVGScriptElement : false, + SVGSetElement : false, + SVGStopElement : false, + SVGStringList : false, + SVGStylable : false, + SVGStyleElement : false, + SVGSwitchElement : false, + SVGSymbolElement : false, + SVGTRefElement : false, + SVGTSpanElement : false, + SVGTests : false, + SVGTextContentElement: false, + SVGTextElement : false, + SVGTextPathElement : false, + SVGTextPositioningElement: false, + SVGTitleElement : false, + SVGTransform : false, + SVGTransformList : false, + SVGTransformable : false, + SVGURIReference : false, + SVGUnitTypes : false, + SVGUseElement : false, + SVGVKernElement : false, + SVGViewElement : false, + SVGViewSpec : false, + SVGZoomAndPan : false, + TimeEvent : false, + top : false, + Uint16Array : false, + Uint32Array : false, + Uint8Array : false, + Uint8ClampedArray : false, + WebSocket : false, + window : false, + Worker : false, + XMLHttpRequest : false, + XMLSerializer : false, + XPathEvaluator : false, + XPathException : false, + XPathExpression : false, + XPathNSResolver : false, + XPathResult : false +}; + +exports.devel = { + alert : false, + confirm: false, + console: false, + Debug : false, + opera : false, + prompt : false +}; + +exports.worker = { + importScripts: true, + postMessage : true, + self : true +}; + +// Widely adopted global names that are not part of ECMAScript standard +exports.nonstandard = { + escape : false, + unescape: false +}; + +// Globals provided by popular JavaScript environments. + +exports.couch = { + "require" : false, + respond : false, + getRow : false, + emit : false, + send : false, + start : false, + sum : false, + log : false, + exports : false, + module : false, + provides : false +}; + +exports.node = { + __filename : false, + __dirname : false, + Buffer : false, + DataView : false, + console : false, + exports : true, // In Node it is ok to exports = module.exports = foo(); + GLOBAL : false, + global : false, + module : false, + process : false, + require : false, + setTimeout : false, + clearTimeout : false, + setInterval : false, + clearInterval : false, + setImmediate : false, // v0.9.1+ + clearImmediate: false // v0.9.1+ +}; + +exports.phantom = { + phantom : true, + require : true, + WebPage : true +}; + +exports.rhino = { + defineClass : false, + deserialize : false, + gc : false, + help : false, + importPackage: false, + "java" : false, + load : false, + loadClass : false, + print : false, + quit : false, + readFile : false, + readUrl : false, + runCommand : false, + seal : false, + serialize : false, + spawn : false, + sync : false, + toint32 : false, + version : false +}; + +exports.wsh = { + ActiveXObject : true, + Enumerator : true, + GetObject : true, + ScriptEngine : true, + ScriptEngineBuildVersion : true, + ScriptEngineMajorVersion : true, + ScriptEngineMinorVersion : true, + VBArray : true, + WSH : true, + WScript : true, + XDomainRequest : true +}; + +// Globals provided by popular JavaScript libraries. + +exports.dojo = { + dojo : false, + dijit : false, + dojox : false, + define : false, + "require": false +}; + +exports.jquery = { + "$" : false, + jQuery : false +}; + +exports.mootools = { + "$" : false, + "$$" : false, + Asset : false, + Browser : false, + Chain : false, + Class : false, + Color : false, + Cookie : false, + Core : false, + Document : false, + DomReady : false, + DOMEvent : false, + DOMReady : false, + Drag : false, + Element : false, + Elements : false, + Event : false, + Events : false, + Fx : false, + Group : false, + Hash : false, + HtmlTable : false, + Iframe : false, + IframeShim : false, + InputValidator: false, + instanceOf : false, + Keyboard : false, + Locale : false, + Mask : false, + MooTools : false, + Native : false, + Options : false, + OverText : false, + Request : false, + Scroller : false, + Slick : false, + Slider : false, + Sortables : false, + Spinner : false, + Swiff : false, + Tips : false, + Type : false, + typeOf : false, + URI : false, + Window : false +}; + +exports.prototypejs = { + "$" : false, + "$$" : false, + "$A" : false, + "$F" : false, + "$H" : false, + "$R" : false, + "$break" : false, + "$continue" : false, + "$w" : false, + Abstract : false, + Ajax : false, + Class : false, + Enumerable : false, + Element : false, + Event : false, + Field : false, + Form : false, + Hash : false, + Insertion : false, + ObjectRange : false, + PeriodicalExecuter: false, + Position : false, + Prototype : false, + Selector : false, + Template : false, + Toggle : false, + Try : false, + Autocompleter : false, + Builder : false, + Control : false, + Draggable : false, + Draggables : false, + Droppables : false, + Effect : false, + Sortable : false, + SortableObserver : false, + Sound : false, + Scriptaculous : false +}; + +exports.yui = { + YUI : false, + Y : false, + YUI_config: false +}; + + +})() +},{}],4:[function(require,module,exports){ +"use strict"; + +var state = { + syntax: {}, + + reset: function () { + this.tokens = { + prev: null, + next: null, + curr: null + }; + + this.option = {}; + this.ignored = {}; + this.directive = {}; + this.jsonMode = false; + this.jsonWarnings = []; + this.lines = []; + this.tab = ""; + this.cache = {}; // Node.JS doesn't have Map. Sniff. + } +}; + +exports.state = state; + +},{}],5:[function(require,module,exports){ +(function(){"use strict"; + +exports.register = function (linter) { + // Check for properties named __proto__. This special property was + // deprecated and then re-introduced for ES6. + + linter.on("Identifier", function style_scanProto(data) { + if (linter.getOption("proto")) { + return; + } + + if (data.name === "__proto__") { + linter.warn("W103", { + line: data.line, + char: data.char, + data: [ data.name ] + }); + } + }); + + // Check for properties named __iterator__. This is a special property + // available only in browsers with JavaScript 1.7 implementation. + + linter.on("Identifier", function style_scanIterator(data) { + if (linter.getOption("iterator")) { + return; + } + + if (data.name === "__iterator__") { + linter.warn("W104", { + line: data.line, + char: data.char, + data: [ data.name ] + }); + } + }); + + // Check for dangling underscores. + + linter.on("Identifier", function style_scanDangling(data) { + if (!linter.getOption("nomen")) { + return; + } + + // Underscore.js + if (data.name === "_") { + return; + } + + // In Node, __dirname and __filename should be ignored. + if (linter.getOption("node")) { + if (/^(__dirname|__filename)$/.test(data.name) && !data.isProperty) { + return; + } + } + + if (/^(_+.*|.*_+)$/.test(data.name)) { + linter.warn("W105", { + line: data.line, + char: data.from, + data: [ "dangling '_'", data.name ] + }); + } + }); + + // Check that all identifiers are using camelCase notation. + // Exceptions: names like MY_VAR and _myVar. + + linter.on("Identifier", function style_scanCamelCase(data) { + if (!linter.getOption("camelcase")) { + return; + } + + if (data.name.replace(/^_+/, "").indexOf("_") > -1 && !data.name.match(/^[A-Z0-9_]*$/)) { + linter.warn("W106", { + line: data.line, + char: data.from, + data: [ data.name ] + }); + } + }); + + // Enforce consistency in style of quoting. + + linter.on("String", function style_scanQuotes(data) { + var quotmark = linter.getOption("quotmark"); + var code; + + if (!quotmark) { + return; + } + + // If quotmark is set to 'single' warn about all double-quotes. + + if (quotmark === "single" && data.quote !== "'") { + code = "W109"; + } + + // If quotmark is set to 'double' warn about all single-quotes. + + if (quotmark === "double" && data.quote !== "\"") { + code = "W108"; + } + + // If quotmark is set to true, remember the first quotation style + // and then warn about all others. + + if (quotmark === true) { + if (!linter.getCache("quotmark")) { + linter.setCache("quotmark", data.quote); + } + + if (linter.getCache("quotmark") !== data.quote) { + code = "W110"; + } + } + + if (code) { + linter.warn(code, { + line: data.line, + char: data.char, + }); + } + }); + + linter.on("Number", function style_scanNumbers(data) { + if (data.value.charAt(0) === ".") { + // Warn about a leading decimal point. + linter.warn("W008", { + line: data.line, + char: data.char, + data: [ data.value ] + }); + } + + if (data.value.substr(data.value.length - 1) === ".") { + // Warn about a trailing decimal point. + linter.warn("W047", { + line: data.line, + char: data.char, + data: [ data.value ] + }); + } + + if (/^00+/.test(data.value)) { + // Multiple leading zeroes. + linter.warn("W046", { + line: data.line, + char: data.char, + data: [ data.value ] + }); + } + }); + + // Warn about script URLs. + + linter.on("String", function style_scanJavaScriptURLs(data) { + var re = /^(?:javascript|jscript|ecmascript|vbscript|mocha|livescript)\s*:/i; + + if (linter.getOption("scripturl")) { + return; + } + + if (re.test(data.value)) { + linter.warn("W107", { + line: data.line, + char: data.char + }); + } + }); +}; +})() +},{}],6:[function(require,module,exports){ +/* + * Regular expressions. Some of these are stupidly long. + */ + +/*jshint maxlen:1000 */ + +"use string"; + +// Unsafe comment or string (ax) +exports.unsafeString = + /@cc|<\/?|script|\]\s*\]|<\s*!|</i; + +// Unsafe characters that are silently deleted by one or more browsers (cx) +exports.unsafeChars = + /[\u0000-\u001f\u007f-\u009f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/; + +// Characters in strings that need escaping (nx and nxg) +exports.needEsc = + /[\u0000-\u001f&<"\/\\\u007f-\u009f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/; + +exports.needEscGlobal = + /[\u0000-\u001f&<"\/\\\u007f-\u009f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g; + +// Star slash (lx) +exports.starSlash = /\*\//; + +// Identifier (ix) +exports.identifier = /^([a-zA-Z_$][a-zA-Z0-9_$]*)$/; + +// JavaScript URL (jx) +exports.javascriptURL = /^(?:javascript|jscript|ecmascript|vbscript|mocha|livescript)\s*:/i; + +// Catches /* falls through */ comments (ft) +//exports.fallsThrough = /^\s*\/\*\s*falls?\sthrough\s*\*\/\s*$/; +exports.fallsThrough = /^\s*\/\/\s*Falls?\sthrough.*\s*$/; + +},{}],7:[function(require,module,exports){ +(function(global){/*global window, global*/ +var util = require("util") +var assert = require("assert") + +var slice = Array.prototype.slice +var console +var times = {} + +if (typeof global !== "undefined" && global.console) { + console = global.console +} else if (typeof window !== "undefined" && window.console) { + console = window.console +} else { + console = window.console = {} +} + +var functions = [ + [log, "log"] + , [info, "info"] + , [warn, "warn"] + , [error, "error"] + , [time, "time"] + , [timeEnd, "timeEnd"] + , [trace, "trace"] + , [dir, "dir"] + , [assert, "assert"] +] + +for (var i = 0; i < functions.length; i++) { + var tuple = functions[i] + var f = tuple[0] + var name = tuple[1] + + if (!console[name]) { + console[name] = f + } +} + +module.exports = console + +function log() {} + +function info() { + console.log.apply(console, arguments) +} + +function warn() { + console.log.apply(console, arguments) +} + +function error() { + console.warn.apply(console, arguments) +} + +function time(label) { + times[label] = Date.now() +} + +function timeEnd(label) { + var time = times[label] + if (!time) { + throw new Error("No such label: " + label) + } + + var duration = Date.now() - time + console.log(label + ": " + duration + "ms") +} + +function trace() { + var err = new Error() + err.name = "Trace" + err.message = util.format.apply(null, arguments) + console.error(err.stack) +} + +function dir(object) { + console.log(util.inspect(object) + "\n") +} + +function assert(expression) { + if (!expression) { + var arr = slice.call(arguments, 1) + assert.ok(false, util.format.apply(null, arr)) + } +} + +})(window) +},{"util":8,"assert":9}],10:[function(require,module,exports){ +(function(){/* + * Lexical analysis and token construction. + */ + +"use strict"; + +var _ = require("underscore"); +var events = require("events"); +var reg = require("./reg.js"); +var state = require("./state.js").state; + +// Some of these token types are from JavaScript Parser API +// while others are specific to JSHint parser. +// JS Parser API: https://developer.mozilla.org/en-US/docs/SpiderMonkey/Parser_API + +var Token = { + Identifier: 1, + Punctuator: 2, + NumericLiteral: 3, + StringLiteral: 4, + Comment: 5, + Keyword: 6, + NullLiteral: 7, + BooleanLiteral: 8, + RegExp: 9 +}; + +// This is auto generated from the unicode tables. +// The tables are at: +// http://www.fileformat.info/info/unicode/category/Lu/list.htm +// http://www.fileformat.info/info/unicode/category/Ll/list.htm +// http://www.fileformat.info/info/unicode/category/Lt/list.htm +// http://www.fileformat.info/info/unicode/category/Lm/list.htm +// http://www.fileformat.info/info/unicode/category/Lo/list.htm +// http://www.fileformat.info/info/unicode/category/Nl/list.htm + +var unicodeLetterTable = [ + 170, 170, 181, 181, 186, 186, 192, 214, + 216, 246, 248, 705, 710, 721, 736, 740, 748, 748, 750, 750, + 880, 884, 886, 887, 890, 893, 902, 902, 904, 906, 908, 908, + 910, 929, 931, 1013, 1015, 1153, 1162, 1319, 1329, 1366, + 1369, 1369, 1377, 1415, 1488, 1514, 1520, 1522, 1568, 1610, + 1646, 1647, 1649, 1747, 1749, 1749, 1765, 1766, 1774, 1775, + 1786, 1788, 1791, 1791, 1808, 1808, 1810, 1839, 1869, 1957, + 1969, 1969, 1994, 2026, 2036, 2037, 2042, 2042, 2048, 2069, + 2074, 2074, 2084, 2084, 2088, 2088, 2112, 2136, 2308, 2361, + 2365, 2365, 2384, 2384, 2392, 2401, 2417, 2423, 2425, 2431, + 2437, 2444, 2447, 2448, 2451, 2472, 2474, 2480, 2482, 2482, + 2486, 2489, 2493, 2493, 2510, 2510, 2524, 2525, 2527, 2529, + 2544, 2545, 2565, 2570, 2575, 2576, 2579, 2600, 2602, 2608, + 2610, 2611, 2613, 2614, 2616, 2617, 2649, 2652, 2654, 2654, + 2674, 2676, 2693, 2701, 2703, 2705, 2707, 2728, 2730, 2736, + 2738, 2739, 2741, 2745, 2749, 2749, 2768, 2768, 2784, 2785, + 2821, 2828, 2831, 2832, 2835, 2856, 2858, 2864, 2866, 2867, + 2869, 2873, 2877, 2877, 2908, 2909, 2911, 2913, 2929, 2929, + 2947, 2947, 2949, 2954, 2958, 2960, 2962, 2965, 2969, 2970, + 2972, 2972, 2974, 2975, 2979, 2980, 2984, 2986, 2990, 3001, + 3024, 3024, 3077, 3084, 3086, 3088, 3090, 3112, 3114, 3123, + 3125, 3129, 3133, 3133, 3160, 3161, 3168, 3169, 3205, 3212, + 3214, 3216, 3218, 3240, 3242, 3251, 3253, 3257, 3261, 3261, + 3294, 3294, 3296, 3297, 3313, 3314, 3333, 3340, 3342, 3344, + 3346, 3386, 3389, 3389, 3406, 3406, 3424, 3425, 3450, 3455, + 3461, 3478, 3482, 3505, 3507, 3515, 3517, 3517, 3520, 3526, + 3585, 3632, 3634, 3635, 3648, 3654, 3713, 3714, 3716, 3716, + 3719, 3720, 3722, 3722, 3725, 3725, 3732, 3735, 3737, 3743, + 3745, 3747, 3749, 3749, 3751, 3751, 3754, 3755, 3757, 3760, + 3762, 3763, 3773, 3773, 3776, 3780, 3782, 3782, 3804, 3805, + 3840, 3840, 3904, 3911, 3913, 3948, 3976, 3980, 4096, 4138, + 4159, 4159, 4176, 4181, 4186, 4189, 4193, 4193, 4197, 4198, + 4206, 4208, 4213, 4225, 4238, 4238, 4256, 4293, 4304, 4346, + 4348, 4348, 4352, 4680, 4682, 4685, 4688, 4694, 4696, 4696, + 4698, 4701, 4704, 4744, 4746, 4749, 4752, 4784, 4786, 4789, + 4792, 4798, 4800, 4800, 4802, 4805, 4808, 4822, 4824, 4880, + 4882, 4885, 4888, 4954, 4992, 5007, 5024, 5108, 5121, 5740, + 5743, 5759, 5761, 5786, 5792, 5866, 5870, 5872, 5888, 5900, + 5902, 5905, 5920, 5937, 5952, 5969, 5984, 5996, 5998, 6000, + 6016, 6067, 6103, 6103, 6108, 6108, 6176, 6263, 6272, 6312, + 6314, 6314, 6320, 6389, 6400, 6428, 6480, 6509, 6512, 6516, + 6528, 6571, 6593, 6599, 6656, 6678, 6688, 6740, 6823, 6823, + 6917, 6963, 6981, 6987, 7043, 7072, 7086, 7087, 7104, 7141, + 7168, 7203, 7245, 7247, 7258, 7293, 7401, 7404, 7406, 7409, + 7424, 7615, 7680, 7957, 7960, 7965, 7968, 8005, 8008, 8013, + 8016, 8023, 8025, 8025, 8027, 8027, 8029, 8029, 8031, 8061, + 8064, 8116, 8118, 8124, 8126, 8126, 8130, 8132, 8134, 8140, + 8144, 8147, 8150, 8155, 8160, 8172, 8178, 8180, 8182, 8188, + 8305, 8305, 8319, 8319, 8336, 8348, 8450, 8450, 8455, 8455, + 8458, 8467, 8469, 8469, 8473, 8477, 8484, 8484, 8486, 8486, + 8488, 8488, 8490, 8493, 8495, 8505, 8508, 8511, 8517, 8521, + 8526, 8526, 8544, 8584, 11264, 11310, 11312, 11358, + 11360, 11492, 11499, 11502, 11520, 11557, 11568, 11621, + 11631, 11631, 11648, 11670, 11680, 11686, 11688, 11694, + 11696, 11702, 11704, 11710, 11712, 11718, 11720, 11726, + 11728, 11734, 11736, 11742, 11823, 11823, 12293, 12295, + 12321, 12329, 12337, 12341, 12344, 12348, 12353, 12438, + 12445, 12447, 12449, 12538, 12540, 12543, 12549, 12589, + 12593, 12686, 12704, 12730, 12784, 12799, 13312, 13312, + 19893, 19893, 19968, 19968, 40907, 40907, 40960, 42124, + 42192, 42237, 42240, 42508, 42512, 42527, 42538, 42539, + 42560, 42606, 42623, 42647, 42656, 42735, 42775, 42783, + 42786, 42888, 42891, 42894, 42896, 42897, 42912, 42921, + 43002, 43009, 43011, 43013, 43015, 43018, 43020, 43042, + 43072, 43123, 43138, 43187, 43250, 43255, 43259, 43259, + 43274, 43301, 43312, 43334, 43360, 43388, 43396, 43442, + 43471, 43471, 43520, 43560, 43584, 43586, 43588, 43595, + 43616, 43638, 43642, 43642, 43648, 43695, 43697, 43697, + 43701, 43702, 43705, 43709, 43712, 43712, 43714, 43714, + 43739, 43741, 43777, 43782, 43785, 43790, 43793, 43798, + 43808, 43814, 43816, 43822, 43968, 44002, 44032, 44032, + 55203, 55203, 55216, 55238, 55243, 55291, 63744, 64045, + 64048, 64109, 64112, 64217, 64256, 64262, 64275, 64279, + 64285, 64285, 64287, 64296, 64298, 64310, 64312, 64316, + 64318, 64318, 64320, 64321, 64323, 64324, 64326, 64433, + 64467, 64829, 64848, 64911, 64914, 64967, 65008, 65019, + 65136, 65140, 65142, 65276, 65313, 65338, 65345, 65370, + 65382, 65470, 65474, 65479, 65482, 65487, 65490, 65495, + 65498, 65500, 65536, 65547, 65549, 65574, 65576, 65594, + 65596, 65597, 65599, 65613, 65616, 65629, 65664, 65786, + 65856, 65908, 66176, 66204, 66208, 66256, 66304, 66334, + 66352, 66378, 66432, 66461, 66464, 66499, 66504, 66511, + 66513, 66517, 66560, 66717, 67584, 67589, 67592, 67592, + 67594, 67637, 67639, 67640, 67644, 67644, 67647, 67669, + 67840, 67861, 67872, 67897, 68096, 68096, 68112, 68115, + 68117, 68119, 68121, 68147, 68192, 68220, 68352, 68405, + 68416, 68437, 68448, 68466, 68608, 68680, 69635, 69687, + 69763, 69807, 73728, 74606, 74752, 74850, 77824, 78894, + 92160, 92728, 110592, 110593, 119808, 119892, 119894, 119964, + 119966, 119967, 119970, 119970, 119973, 119974, 119977, 119980, + 119982, 119993, 119995, 119995, 119997, 120003, 120005, 120069, + 120071, 120074, 120077, 120084, 120086, 120092, 120094, 120121, + 120123, 120126, 120128, 120132, 120134, 120134, 120138, 120144, + 120146, 120485, 120488, 120512, 120514, 120538, 120540, 120570, + 120572, 120596, 120598, 120628, 120630, 120654, 120656, 120686, + 120688, 120712, 120714, 120744, 120746, 120770, 120772, 120779, + 131072, 131072, 173782, 173782, 173824, 173824, 177972, 177972, + 177984, 177984, 178205, 178205, 194560, 195101 +]; + +var identifierStartTable = []; + +for (var i = 0; i < 128; i++) { + identifierStartTable[i] = + i === 36 || // $ + i >= 65 && i <= 90 || // A-Z + i === 95 || // _ + i >= 97 && i <= 122; // a-z +} + +var identifierPartTable = []; + +for (var i = 0; i < 128; i++) { + identifierPartTable[i] = + identifierStartTable[i] || // $, _, A-Z, a-z + i >= 48 && i <= 57; // 0-9 +} + +// Object that handles postponed lexing verifications that checks the parsed +// environment state. + +function asyncTrigger() { + var _checks = []; + + return { + push: function (fn) { + _checks.push(fn); + }, + + check: function () { + for (var check in _checks) { + _checks[check](); + } + + _checks.splice(0, _checks.length); + } + }; +} + +/* + * Lexer for JSHint. + * + * This object does a char-by-char scan of the provided source code + * and produces a sequence of tokens. + * + * var lex = new Lexer("var i = 0;"); + * lex.start(); + * lex.token(); // returns the next token + * + * You have to use the token() method to move the lexer forward + * but you don't have to use its return value to get tokens. In addition + * to token() method returning the next token, the Lexer object also + * emits events. + * + * lex.on("Identifier", function (data) { + * if (data.name.indexOf("_") >= 0) { + * // Produce a warning. + * } + * }); + * + * Note that the token() method returns tokens in a JSLint-compatible + * format while the event emitter uses a slightly modified version of + * Mozilla's JavaScript Parser API. Eventually, we will move away from + * JSLint format. + */ +function Lexer(source) { + var lines = source; + + if (typeof lines === "string") { + lines = lines + .replace(/\r\n/g, "\n") + .replace(/\r/g, "\n") + .split("\n"); + } + + // If the first line is a shebang (#!), make it a blank and move on. + // Shebangs are used by Node scripts. + + if (lines[0] && lines[0].substr(0, 2) === "#!") { + lines[0] = ""; + } + + this.emitter = new events.EventEmitter(); + this.source = source; + this.lines = lines; + this.prereg = true; + + this.line = 0; + this.char = 1; + this.from = 1; + this.input = ""; + + for (var i = 0; i < state.option.indent; i += 1) { + state.tab += " "; + } +} + +Lexer.prototype = { + _lines: [], + + get lines() { + this._lines = state.lines; + return this._lines; + }, + + set lines(val) { + this._lines = val; + state.lines = this._lines; + }, + + /* + * Return the next i character without actually moving the + * char pointer. + */ + peek: function (i) { + return this.input.charAt(i || 0); + }, + + /* + * Move the char pointer forward i times. + */ + skip: function (i) { + i = i || 1; + this.char += i; + this.input = this.input.slice(i); + }, + + /* + * Subscribe to a token event. The API for this method is similar + * Underscore.js i.e. you can subscribe to multiple events with + * one call: + * + * lex.on("Identifier Number", function (data) { + * // ... + * }); + */ + on: function (names, listener) { + names.split(" ").forEach(function (name) { + this.emitter.on(name, listener); + }.bind(this)); + }, + + /* + * Trigger a token event. All arguments will be passed to each + * listener. + */ + trigger: function () { + this.emitter.emit.apply(this.emitter, Array.prototype.slice.call(arguments)); + }, + + /* + * Postpone a token event. the checking condition is set as + * last parameter, and the trigger function is called in a + * stored callback. To be later called using the check() function + * by the parser. This avoids parser's peek() to give the lexer + * a false context. + */ + triggerAsync: function (type, args, checks, fn) { + checks.push(function () { + if (fn()) { + this.trigger(type, args); + } + }.bind(this)); + }, + + /* + * Extract a punctuator out of the next sequence of characters + * or return 'null' if its not possible. + * + * This method's implementation was heavily influenced by the + * scanPunctuator function in the Esprima parser's source code. + */ + scanPunctuator: function () { + var ch1 = this.peek(); + var ch2, ch3, ch4; + + switch (ch1) { + // Most common single-character punctuators + case ".": + if ((/^[0-9]$/).test(this.peek(1))) { + return null; + } + if (this.peek(1) === "." && this.peek(2) === ".") { + return { + type: Token.Punctuator, + value: "..." + }; + } + /* falls through */ + case "(": + case ")": + case ";": + case ",": + case "{": + case "}": + case "[": + case "]": + case ":": + case "~": + case "?": + return { + type: Token.Punctuator, + value: ch1 + }; + + // A pound sign (for Node shebangs) + case "#": + return { + type: Token.Punctuator, + value: ch1 + }; + + // We're at the end of input + case "": + return null; + } + + // Peek more characters + + ch2 = this.peek(1); + ch3 = this.peek(2); + ch4 = this.peek(3); + + // 4-character punctuator: >>>= + + if (ch1 === ">" && ch2 === ">" && ch3 === ">" && ch4 === "=") { + return { + type: Token.Punctuator, + value: ">>>=" + }; + } + + // 3-character punctuators: === !== >>> <<= >>= + + if (ch1 === "=" && ch2 === "=" && ch3 === "=") { + return { + type: Token.Punctuator, + value: "===" + }; + } + + if (ch1 === "!" && ch2 === "=" && ch3 === "=") { + return { + type: Token.Punctuator, + value: "!==" + }; + } + + if (ch1 === ">" && ch2 === ">" && ch3 === ">") { + return { + type: Token.Punctuator, + value: ">>>" + }; + } + + if (ch1 === "<" && ch2 === "<" && ch3 === "=") { + return { + type: Token.Punctuator, + value: "<<=" + }; + } + + if (ch1 === ">" && ch2 === ">" && ch3 === "=") { + return { + type: Token.Punctuator, + value: ">>=" + }; + } + + // Fat arrow punctuator + if (ch1 === "=" && ch2 === ">") { + return { + type: Token.Punctuator, + value: ch1 + ch2 + }; + } + + // 2-character punctuators: <= >= == != ++ -- << >> && || + // += -= *= %= &= |= ^= (but not /=, see below) + if (ch1 === ch2 && ("+-<>&|".indexOf(ch1) >= 0)) { + return { + type: Token.Punctuator, + value: ch1 + ch2 + }; + } + + if ("<>=!+-*%&|^".indexOf(ch1) >= 0) { + if (ch2 === "=") { + return { + type: Token.Punctuator, + value: ch1 + ch2 + }; + } + + return { + type: Token.Punctuator, + value: ch1 + }; + } + + // Special case: /=. We need to make sure that this is an + // operator and not a regular expression. + + if (ch1 === "/") { + if (ch2 === "=" && /\/=(?!(\S*\/[gim]?))/.test(this.input)) { + // /= is not a part of a regular expression, return it as a + // punctuator. + return { + type: Token.Punctuator, + value: "/=" + }; + } + + return { + type: Token.Punctuator, + value: "/" + }; + } + + return null; + }, + + /* + * Extract a comment out of the next sequence of characters and/or + * lines or return 'null' if its not possible. Since comments can + * span across multiple lines this method has to move the char + * pointer. + * + * In addition to normal JavaScript comments (// and /*) this method + * also recognizes JSHint- and JSLint-specific comments such as + * /*jshint, /*jslint, /*globals and so on. + */ + scanComments: function () { + var ch1 = this.peek(); + var ch2 = this.peek(1); + var rest = this.input.substr(2); + var startLine = this.line; + var startChar = this.char; + + // Create a comment token object and make sure it + // has all the data JSHint needs to work with special + // comments. + + function commentToken(label, body, opt) { + var special = ["jshint", "jslint", "members", "member", "globals", "global", "exported"]; + var isSpecial = false; + var value = label + body; + var commentType = "plain"; + opt = opt || {}; + + if (opt.isMultiline) { + value += "*/"; + } + + special.forEach(function (str) { + if (isSpecial) { + return; + } + + // Don't recognize any special comments other than jshint for single-line + // comments. This introduced many problems with legit comments. + if (label === "//" && str !== "jshint") { + return; + } + + if (body.substr(0, str.length) === str) { + isSpecial = true; + label = label + str; + body = body.substr(str.length); + } + + if (!isSpecial && body.charAt(0) === " " && body.substr(1, str.length) === str) { + isSpecial = true; + label = label + " " + str; + body = body.substr(str.length + 1); + } + + if (!isSpecial) { + return; + } + + switch (str) { + case "member": + commentType = "members"; + break; + case "global": + commentType = "globals"; + break; + default: + commentType = str; + } + }); + + return { + type: Token.Comment, + commentType: commentType, + value: value, + body: body, + isSpecial: isSpecial, + isMultiline: opt.isMultiline || false, + isMalformed: opt.isMalformed || false + }; + } + + // End of unbegun comment. Raise an error and skip that input. + if (ch1 === "*" && ch2 === "/") { + this.trigger("error", { + code: "E018", + line: startLine, + character: startChar + }); + + this.skip(2); + return null; + } + + // Comments must start either with // or /* + if (ch1 !== "/" || (ch2 !== "*" && ch2 !== "/")) { + return null; + } + + // One-line comment + if (ch2 === "/") { + this.skip(this.input.length); // Skip to the EOL. + return commentToken("//", rest); + } + + var body = ""; + + /* Multi-line comment */ + if (ch2 === "*") { + this.skip(2); + + while (this.peek() !== "*" || this.peek(1) !== "/") { + if (this.peek() === "") { // End of Line + body += "\n"; + + // If we hit EOF and our comment is still unclosed, + // trigger an error and end the comment implicitly. + if (!this.nextLine()) { + this.trigger("error", { + code: "E017", + line: startLine, + character: startChar + }); + + return commentToken("/*", body, { + isMultiline: true, + isMalformed: true + }); + } + } else { + body += this.peek(); + this.skip(); + } + } + + this.skip(2); + return commentToken("/*", body, { isMultiline: true }); + } + }, + + /* + * Extract a keyword out of the next sequence of characters or + * return 'null' if its not possible. + */ + scanKeyword: function () { + var result = /^[a-zA-Z_$][a-zA-Z0-9_$]*/.exec(this.input); + var keywords = [ + "if", "in", "do", "var", "for", "new", + "try", "let", "this", "else", "case", + "void", "with", "enum", "while", "break", + "catch", "throw", "const", "yield", "class", + "super", "return", "typeof", "delete", + "switch", "export", "import", "default", + "finally", "extends", "function", "continue", + "debugger", "instanceof" + ]; + + if (result && keywords.indexOf(result[0]) >= 0) { + return { + type: Token.Keyword, + value: result[0] + }; + } + + return null; + }, + + /* + * Extract a JavaScript identifier out of the next sequence of + * characters or return 'null' if its not possible. In addition, + * to Identifier this method can also produce BooleanLiteral + * (true/false) and NullLiteral (null). + */ + scanIdentifier: function () { + var id = ""; + var index = 0; + var type, char; + + // Detects any character in the Unicode categories "Uppercase + // letter (Lu)", "Lowercase letter (Ll)", "Titlecase letter + // (Lt)", "Modifier letter (Lm)", "Other letter (Lo)", or + // "Letter number (Nl)". + // + // Both approach and unicodeLetterTable were borrowed from + // Google's Traceur. + + function isUnicodeLetter(code) { + for (var i = 0; i < unicodeLetterTable.length;) { + if (code < unicodeLetterTable[i++]) { + return false; + } + + if (code <= unicodeLetterTable[i++]) { + return true; + } + } + + return false; + } + + function isHexDigit(str) { + return (/^[0-9a-fA-F]$/).test(str); + } + + var readUnicodeEscapeSequence = function () { + /*jshint validthis:true */ + index += 1; + + if (this.peek(index) !== "u") { + return null; + } + + var ch1 = this.peek(index + 1); + var ch2 = this.peek(index + 2); + var ch3 = this.peek(index + 3); + var ch4 = this.peek(index + 4); + var code; + + if (isHexDigit(ch1) && isHexDigit(ch2) && isHexDigit(ch3) && isHexDigit(ch4)) { + code = parseInt(ch1 + ch2 + ch3 + ch4, 16); + + if (isUnicodeLetter(code)) { + index += 5; + return "\\u" + ch1 + ch2 + ch3 + ch4; + } + + return null; + } + + return null; + }.bind(this); + + var getIdentifierStart = function () { + /*jshint validthis:true */ + var chr = this.peek(index); + var code = chr.charCodeAt(0); + + if (code === 92) { + return readUnicodeEscapeSequence(); + } + + if (code < 128) { + if (identifierStartTable[code]) { + index += 1; + return chr; + } + + return null; + } + + if (isUnicodeLetter(code)) { + index += 1; + return chr; + } + + return null; + }.bind(this); + + var getIdentifierPart = function () { + /*jshint validthis:true */ + var chr = this.peek(index); + var code = chr.charCodeAt(0); + + if (code === 92) { + return readUnicodeEscapeSequence(); + } + + if (code < 128) { + if (identifierPartTable[code]) { + index += 1; + return chr; + } + + return null; + } + + if (isUnicodeLetter(code)) { + index += 1; + return chr; + } + + return null; + }.bind(this); + + char = getIdentifierStart(); + if (char === null) { + return null; + } + + id = char; + for (;;) { + char = getIdentifierPart(); + + if (char === null) { + break; + } + + id += char; + } + + switch (id) { + case "true": + case "false": + type = Token.BooleanLiteral; + break; + case "null": + type = Token.NullLiteral; + break; + default: + type = Token.Identifier; + } + + return { + type: type, + value: id + }; + }, + + /* + * Extract a numeric literal out of the next sequence of + * characters or return 'null' if its not possible. This method + * supports all numeric literals described in section 7.8.3 + * of the EcmaScript 5 specification. + * + * This method's implementation was heavily influenced by the + * scanNumericLiteral function in the Esprima parser's source code. + */ + scanNumericLiteral: function () { + var index = 0; + var value = ""; + var length = this.input.length; + var char = this.peek(index); + var bad; + + function isDecimalDigit(str) { + return (/^[0-9]$/).test(str); + } + + function isOctalDigit(str) { + return (/^[0-7]$/).test(str); + } + + function isHexDigit(str) { + return (/^[0-9a-fA-F]$/).test(str); + } + + function isIdentifierStart(ch) { + return (ch === "$") || (ch === "_") || (ch === "\\") || + (ch >= "a" && ch <= "z") || (ch >= "A" && ch <= "Z"); + } + + // Numbers must start either with a decimal digit or a point. + + if (char !== "." && !isDecimalDigit(char)) { + return null; + } + + if (char !== ".") { + value = this.peek(index); + index += 1; + char = this.peek(index); + + if (value === "0") { + // Base-16 numbers. + if (char === "x" || char === "X") { + index += 1; + value += char; + + while (index < length) { + char = this.peek(index); + if (!isHexDigit(char)) { + break; + } + value += char; + index += 1; + } + + if (value.length <= 2) { // 0x + return { + type: Token.NumericLiteral, + value: value, + isMalformed: true + }; + } + + if (index < length) { + char = this.peek(index); + if (isIdentifierStart(char)) { + return null; + } + } + + return { + type: Token.NumericLiteral, + value: value, + base: 16, + isMalformed: false + }; + } + + // Base-8 numbers. + if (isOctalDigit(char)) { + index += 1; + value += char; + bad = false; + + while (index < length) { + char = this.peek(index); + + // Numbers like '019' (note the 9) are not valid octals + // but we still parse them and mark as malformed. + + if (isDecimalDigit(char)) { + bad = true; + } else if (!isOctalDigit(char)) { + break; + } + value += char; + index += 1; + } + + if (index < length) { + char = this.peek(index); + if (isIdentifierStart(char)) { + return null; + } + } + + return { + type: Token.NumericLiteral, + value: value, + base: 8, + isMalformed: false + }; + } + + // Decimal numbers that start with '0' such as '09' are illegal + // but we still parse them and return as malformed. + + if (isDecimalDigit(char)) { + index += 1; + value += char; + } + } + + while (index < length) { + char = this.peek(index); + if (!isDecimalDigit(char)) { + break; + } + value += char; + index += 1; + } + } + + // Decimal digits. + + if (char === ".") { + value += char; + index += 1; + + while (index < length) { + char = this.peek(index); + if (!isDecimalDigit(char)) { + break; + } + value += char; + index += 1; + } + } + + // Exponent part. + + if (char === "e" || char === "E") { + value += char; + index += 1; + char = this.peek(index); + + if (char === "+" || char === "-") { + value += this.peek(index); + index += 1; + } + + char = this.peek(index); + if (isDecimalDigit(char)) { + value += char; + index += 1; + + while (index < length) { + char = this.peek(index); + if (!isDecimalDigit(char)) { + break; + } + value += char; + index += 1; + } + } else { + return null; + } + } + + if (index < length) { + char = this.peek(index); + if (isIdentifierStart(char)) { + return null; + } + } + + return { + type: Token.NumericLiteral, + value: value, + base: 10, + isMalformed: !isFinite(value) + }; + }, + + /* + * Extract a string out of the next sequence of characters and/or + * lines or return 'null' if its not possible. Since strings can + * span across multiple lines this method has to move the char + * pointer. + * + * This method recognizes pseudo-multiline JavaScript strings: + * + * var str = "hello\ + * world"; + */ + scanStringLiteral: function (checks) { + /*jshint loopfunc:true */ + var quote = this.peek(); + + // String must start with a quote. + if (quote !== "\"" && quote !== "'") { + return null; + } + + // In JSON strings must always use double quotes. + this.triggerAsync("warning", { + code: "W108", + line: this.line, + character: this.char // +1? + }, checks, function () { return state.jsonMode && quote !== "\""; }); + + var value = ""; + var startLine = this.line; + var startChar = this.char; + var allowNewLine = false; + + this.skip(); + + while (this.peek() !== quote) { + while (this.peek() === "") { // End Of Line + + // If an EOL is not preceded by a backslash, show a warning + // and proceed like it was a legit multi-line string where + // author simply forgot to escape the newline symbol. + // + // Another approach is to implicitly close a string on EOL + // but it generates too many false positives. + + if (!allowNewLine) { + this.trigger("warning", { + code: "W112", + line: this.line, + character: this.char + }); + } else { + allowNewLine = false; + + // Otherwise show a warning if multistr option was not set. + // For JSON, show warning no matter what. + + this.triggerAsync("warning", { + code: "W043", + line: this.line, + character: this.char + }, checks, function () { return !state.option.multistr; }); + + this.triggerAsync("warning", { + code: "W042", + line: this.line, + character: this.char + }, checks, function () { return state.jsonMode && state.option.multistr; }); + } + + // If we get an EOF inside of an unclosed string, show an + // error and implicitly close it at the EOF point. + + if (!this.nextLine()) { + this.trigger("error", { + code: "E029", + line: startLine, + character: startChar + }); + + return { + type: Token.StringLiteral, + value: value, + isUnclosed: true, + quote: quote + }; + } + } + + allowNewLine = false; + var char = this.peek(); + var jump = 1; // A length of a jump, after we're done + // parsing this character. + + if (char < " ") { + // Warn about a control character in a string. + this.trigger("warning", { + code: "W113", + line: this.line, + character: this.char, + data: [ "<non-printable>" ] + }); + } + + // Special treatment for some escaped characters. + + if (char === "\\") { + this.skip(); + char = this.peek(); + + switch (char) { + case "'": + this.triggerAsync("warning", { + code: "W114", + line: this.line, + character: this.char, + data: [ "\\'" ] + }, checks, function () {return state.jsonMode; }); + break; + case "b": + char = "\b"; + break; + case "f": + char = "\f"; + break; + case "n": + char = "\n"; + break; + case "r": + char = "\r"; + break; + case "t": + char = "\t"; + break; + case "0": + char = "\0"; + + // Octal literals fail in strict mode. + // Check if the number is between 00 and 07. + var n = parseInt(this.peek(1), 10); + this.triggerAsync("warning", { + code: "W115", + line: this.line, + character: this.char + }, checks, + function () { return n >= 0 && n <= 7 && state.directive["use strict"]; }); + break; + case "u": + char = String.fromCharCode(parseInt(this.input.substr(1, 4), 16)); + jump = 5; + break; + case "v": + this.triggerAsync("warning", { + code: "W114", + line: this.line, + character: this.char, + data: [ "\\v" ] + }, checks, function () { return state.jsonMode; }); + + char = "\v"; + break; + case "x": + var x = parseInt(this.input.substr(1, 2), 16); + + this.triggerAsync("warning", { + code: "W114", + line: this.line, + character: this.char, + data: [ "\\x-" ] + }, checks, function () { return state.jsonMode; }); + + char = String.fromCharCode(x); + jump = 3; + break; + case "\\": + case "\"": + case "/": + break; + case "": + allowNewLine = true; + char = ""; + break; + case "!": + if (value.slice(value.length - 2) === "<") { + break; + } + + /*falls through */ + default: + // Weird escaping. + this.trigger("warning", { + code: "W044", + line: this.line, + character: this.char + }); + } + } + + value += char; + this.skip(jump); + } + + this.skip(); + return { + type: Token.StringLiteral, + value: value, + isUnclosed: false, + quote: quote + }; + }, + + /* + * Extract a regular expression out of the next sequence of + * characters and/or lines or return 'null' if its not possible. + * + * This method is platform dependent: it accepts almost any + * regular expression values but then tries to compile and run + * them using system's RegExp object. This means that there are + * rare edge cases where one JavaScript engine complains about + * your regular expression while others don't. + */ + scanRegExp: function () { + var index = 0; + var length = this.input.length; + var char = this.peek(); + var value = char; + var body = ""; + var flags = []; + var malformed = false; + var isCharSet = false; + var terminated; + + var scanUnexpectedChars = function () { + // Unexpected control character + if (char < " ") { + malformed = true; + this.trigger("warning", { + code: "W048", + line: this.line, + character: this.char + }); + } + + // Unexpected escaped character + if (char === "<") { + malformed = true; + this.trigger("warning", { + code: "W049", + line: this.line, + character: this.char, + data: [ char ] + }); + } + }.bind(this); + + // Regular expressions must start with '/' + if (!this.prereg || char !== "/") { + return null; + } + + index += 1; + terminated = false; + + // Try to get everything in between slashes. A couple of + // cases aside (see scanUnexpectedChars) we don't really + // care whether the resulting expression is valid or not. + // We will check that later using the RegExp object. + + while (index < length) { + char = this.peek(index); + value += char; + body += char; + + if (isCharSet) { + if (char === "]") { + if (this.peek(index - 1) !== "\\" || this.peek(index - 2) === "\\") { + isCharSet = false; + } + } + + if (char === "\\") { + index += 1; + char = this.peek(index); + body += char; + value += char; + + scanUnexpectedChars(); + } + + index += 1; + continue; + } + + if (char === "\\") { + index += 1; + char = this.peek(index); + body += char; + value += char; + + scanUnexpectedChars(); + + if (char === "/") { + index += 1; + continue; + } + + if (char === "[") { + index += 1; + continue; + } + } + + if (char === "[") { + isCharSet = true; + index += 1; + continue; + } + + if (char === "/") { + body = body.substr(0, body.length - 1); + terminated = true; + index += 1; + break; + } + + index += 1; + } + + // A regular expression that was never closed is an + // error from which we cannot recover. + + if (!terminated) { + this.trigger("error", { + code: "E015", + line: this.line, + character: this.from + }); + + return void this.trigger("fatal", { + line: this.line, + from: this.from + }); + } + + // Parse flags (if any). + + while (index < length) { + char = this.peek(index); + if (!/[gim]/.test(char)) { + break; + } + flags.push(char); + value += char; + index += 1; + } + + // Check regular expression for correctness. + + try { + new RegExp(body, flags.join("")); + } catch (err) { + malformed = true; + this.trigger("error", { + code: "E016", + line: this.line, + character: this.char, + data: [ err.message ] // Platform dependent! + }); + } + + return { + type: Token.RegExp, + value: value, + flags: flags, + isMalformed: malformed + }; + }, + + /* + * Scan for any occurence of mixed tabs and spaces. If smarttabs option + * is on, ignore tabs followed by spaces. + * + * Tabs followed by one space followed by a block comment are allowed. + */ + scanMixedSpacesAndTabs: function () { + var at, match; + + if (state.option.smarttabs) { + // Negative look-behind for "//" + match = this.input.match(/(\/\/|^\s?\*)? \t/); + at = match && !match[1] ? 0 : -1; + } else { + at = this.input.search(/ \t|\t [^\*]/); + } + + return at; + }, + + /* + * Scan for characters that get silently deleted by one or more browsers. + */ + scanUnsafeChars: function () { + return this.input.search(reg.unsafeChars); + }, + + /* + * Produce the next raw token or return 'null' if no tokens can be matched. + * This method skips over all space characters. + */ + next: function (checks) { + this.from = this.char; + + // Move to the next non-space character. + var start; + if (/\s/.test(this.peek())) { + start = this.char; + + while (/\s/.test(this.peek())) { + this.from += 1; + this.skip(); + } + + if (this.peek() === "") { // EOL + if (!/^\s*$/.test(this.lines[this.line - 1]) && state.option.trailing) { + this.trigger("warning", { code: "W102", line: this.line, character: start }); + } + } + } + + // Methods that work with multi-line structures and move the + // character pointer. + + var match = this.scanComments() || + this.scanStringLiteral(checks); + + if (match) { + return match; + } + + // Methods that don't move the character pointer. + + match = + this.scanRegExp() || + this.scanPunctuator() || + this.scanKeyword() || + this.scanIdentifier() || + this.scanNumericLiteral(); + + if (match) { + this.skip(match.value.length); + return match; + } + + // No token could be matched, give up. + + return null; + }, + + /* + * Switch to the next line and reset all char pointers. Once + * switched, this method also checks for mixed spaces and tabs + * and other minor warnings. + */ + nextLine: function () { + var char; + + if (this.line >= this.lines.length) { + return false; + } + + this.input = this.lines[this.line]; + this.line += 1; + this.char = 1; + this.from = 1; + + char = this.scanMixedSpacesAndTabs(); + if (char >= 0) { + this.trigger("warning", { code: "W099", line: this.line, character: char + 1 }); + } + + this.input = this.input.replace(/\t/g, state.tab); + char = this.scanUnsafeChars(); + + if (char >= 0) { + this.trigger("warning", { code: "W100", line: this.line, character: char }); + } + + // If there is a limit on line length, warn when lines get too + // long. + + if (state.option.maxlen && state.option.maxlen < this.input.length) { + this.trigger("warning", { code: "W101", line: this.line, character: this.input.length }); + } + + return true; + }, + + /* + * This is simply a synonym for nextLine() method with a friendlier + * public name. + */ + start: function () { + this.nextLine(); + }, + + /* + * Produce the next token. This function is called by advance() to get + * the next token. It retuns a token in a JSLint-compatible format. + */ + token: function () { + /*jshint loopfunc:true */ + var checks = asyncTrigger(); + var token; + + + function isReserved(token, isProperty) { + if (!token.reserved) { + return false; + } + + if (token.meta && token.meta.isFutureReservedWord) { + // ES3 FutureReservedWord in an ES5 environment. + if (state.option.inES5(true) && !token.meta.es5) { + return false; + } + + // Some ES5 FutureReservedWord identifiers are active only + // within a strict mode environment. + if (token.meta.strictOnly) { + if (!state.option.strict && !state.directive["use strict"]) { + return false; + } + } + + if (isProperty) { + return false; + } + } + + return true; + } + + // Produce a token object. + var create = function (type, value, isProperty) { + /*jshint validthis:true */ + var obj; + + if (type !== "(endline)" && type !== "(end)") { + this.prereg = false; + } + + if (type === "(punctuator)") { + switch (value) { + case ".": + case ")": + case "~": + case "#": + case "]": + this.prereg = false; + break; + default: + this.prereg = true; + } + + obj = Object.create(state.syntax[value] || state.syntax["(error)"]); + } + + if (type === "(identifier)") { + if (value === "return" || value === "case" || value === "typeof") { + this.prereg = true; + } + + if (_.has(state.syntax, value)) { + obj = Object.create(state.syntax[value] || state.syntax["(error)"]); + + // If this can't be a reserved keyword, reset the object. + if (!isReserved(obj, isProperty && type === "(identifier)")) { + obj = null; + } + } + } + + if (!obj) { + obj = Object.create(state.syntax[type]); + } + + obj.identifier = (type === "(identifier)"); + obj.type = obj.type || type; + obj.value = value; + obj.line = this.line; + obj.character = this.char; + obj.from = this.from; + + if (isProperty && obj.identifier) { + obj.isProperty = isProperty; + } + + obj.check = checks.check; + + return obj; + }.bind(this); + + for (;;) { + if (!this.input.length) { + return create(this.nextLine() ? "(endline)" : "(end)", ""); + } + + token = this.next(checks); + + if (!token) { + if (this.input.length) { + // Unexpected character. + this.trigger("error", { + code: "E024", + line: this.line, + character: this.char, + data: [ this.peek() ] + }); + + this.input = ""; + } + + continue; + } + + switch (token.type) { + case Token.StringLiteral: + this.triggerAsync("String", { + line: this.line, + char: this.char, + from: this.from, + value: token.value, + quote: token.quote + }, checks, function () { return true; }); + + return create("(string)", token.value); + case Token.Identifier: + this.trigger("Identifier", { + line: this.line, + char: this.char, + from: this.form, + name: token.value, + isProperty: state.tokens.curr.id === "." + }); + + /* falls through */ + case Token.Keyword: + case Token.NullLiteral: + case Token.BooleanLiteral: + return create("(identifier)", token.value, state.tokens.curr.id === "."); + + case Token.NumericLiteral: + if (token.isMalformed) { + this.trigger("warning", { + code: "W045", + line: this.line, + character: this.char, + data: [ token.value ] + }); + } + + this.triggerAsync("warning", { + code: "W114", + line: this.line, + character: this.char, + data: [ "0x-" ] + }, checks, function () { return token.base === 16 && state.jsonMode; }); + + this.triggerAsync("warning", { + code: "W115", + line: this.line, + character: this.char + }, checks, function () { + return state.directive["use strict"] && token.base === 8; + }); + + this.trigger("Number", { + line: this.line, + char: this.char, + from: this.from, + value: token.value, + base: token.base, + isMalformed: token.malformed + }); + + return create("(number)", token.value); + + case Token.RegExp: + return create("(regexp)", token.value); + + case Token.Comment: + state.tokens.curr.comment = true; + + if (token.isSpecial) { + return { + value: token.value, + body: token.body, + type: token.commentType, + isSpecial: token.isSpecial, + line: this.line, + character: this.char, + from: this.from + }; + } + + break; + + case "": + break; + + default: + return create("(punctuator)", token.value); + } + } + } +}; + +exports.Lexer = Lexer; + +})() +},{"events":2,"./reg.js":6,"./state.js":4,"underscore":11}],"jshint":[function(require,module,exports){ +module.exports=require('E/GbHF'); +},{}],"E/GbHF":[function(require,module,exports){ +(function(){/*! + * JSHint, by JSHint Community. + * + * This file (and this file only) is licensed under the same slightly modified + * MIT license that JSLint is. It stops evil-doers everywhere: + * + * Copyright (c) 2002 Douglas Crockford (www.JSLint.com) + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom + * the Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * The Software shall be used for Good, not Evil. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + * + */ + +/*jshint quotmark:double */ +/*global console:true */ +/*exported console */ + +var _ = require("underscore"); +var events = require("events"); +var vars = require("../shared/vars.js"); +var messages = require("../shared/messages.js"); +var Lexer = require("./lex.js").Lexer; +var reg = require("./reg.js"); +var state = require("./state.js").state; +var style = require("./style.js"); + +// We need this module here because environments such as IE and Rhino +// don't necessarilly expose the 'console' API and browserify uses +// it to log things. It's a sad state of affair, really. +var console = require("console-browserify"); + +// We build the application inside a function so that we produce only a singleton +// variable. That function will be invoked immediately, and its return value is +// the JSHINT function itself. + +var JSHINT = (function () { + "use strict"; + + var anonname, // The guessed name for anonymous functions. + api, // Extension API + + // These are operators that should not be used with the ! operator. + bang = { + "<" : true, + "<=" : true, + "==" : true, + "===": true, + "!==": true, + "!=" : true, + ">" : true, + ">=" : true, + "+" : true, + "-" : true, + "*" : true, + "/" : true, + "%" : true + }, + + // These are the JSHint boolean options. + boolOptions = { + asi : true, // if automatic semicolon insertion should be tolerated + bitwise : true, // if bitwise operators should not be allowed + boss : true, // if advanced usage of assignments should be allowed + browser : true, // if the standard browser globals should be predefined + camelcase : true, // if identifiers should be required in camel case + couch : true, // if CouchDB globals should be predefined + curly : true, // if curly braces around all blocks should be required + debug : true, // if debugger statements should be allowed + devel : true, // if logging globals should be predefined (console, alert, etc.) + dojo : true, // if Dojo Toolkit globals should be predefined + eqeqeq : true, // if === should be required + eqnull : true, // if == null comparisons should be tolerated + es3 : true, // if ES3 syntax should be allowed + es5 : true, // if ES5 syntax should be allowed (is now set per default) + esnext : true, // if es.next specific syntax should be allowed + moz : true, // if mozilla specific syntax should be allowed + evil : true, // if eval should be allowed + expr : true, // if ExpressionStatement should be allowed as Programs + forin : true, // if for in statements must filter + funcscope : true, // if only function scope should be used for scope tests + gcl : true, // if JSHint should be compatible with Google Closure Linter + globalstrict: true, // if global "use strict"; should be allowed (also enables 'strict') + immed : true, // if immediate invocations must be wrapped in parens + iterator : true, // if the `__iterator__` property should be allowed + jquery : true, // if jQuery globals should be predefined + lastsemic : true, // if semicolons may be ommitted for the trailing + // statements inside of a one-line blocks. + laxbreak : true, // if line breaks should not be checked + laxcomma : true, // if line breaks should not be checked around commas + loopfunc : true, // if functions should be allowed to be defined within + // loops + mootools : true, // if MooTools globals should be predefined + multistr : true, // allow multiline strings + newcap : true, // if constructor names must be capitalized + noarg : true, // if arguments.caller and arguments.callee should be + // disallowed + node : true, // if the Node.js environment globals should be + // predefined + noempty : true, // if empty blocks should be disallowed + nonew : true, // if using `new` for side-effects should be disallowed + nonstandard : true, // if non-standard (but widely adopted) globals should + // be predefined + nomen : true, // if names should be checked + onevar : true, // if only one var statement per function should be + // allowed + passfail : true, // if the scan should stop on first error + phantom : true, // if PhantomJS symbols should be allowed + plusplus : true, // if increment/decrement should not be allowed + proto : true, // if the `__proto__` property should be allowed + prototypejs : true, // if Prototype and Scriptaculous globals should be + // predefined + rhino : true, // if the Rhino environment globals should be predefined + undef : true, // if variables should be declared before used + scripturl : true, // if script-targeted URLs should be tolerated + shadow : true, // if variable shadowing should be tolerated + smarttabs : true, // if smarttabs should be tolerated + // (http://www.emacswiki.org/emacs/SmartTabs) + strict : true, // require the "use strict"; pragma + sub : true, // if all forms of subscript notation are tolerated + supernew : true, // if `new function () { ... };` and `new Object;` + // should be tolerated + trailing : true, // if trailing whitespace rules apply + validthis : true, // if 'this' inside a non-constructor function is valid. + // This is a function scoped option only. + withstmt : true, // if with statements should be allowed + white : true, // if strict whitespace rules apply + worker : true, // if Web Worker script symbols should be allowed + wsh : true, // if the Windows Scripting Host environment globals + // should be predefined + yui : true, // YUI variables should be predefined + + // Obsolete options + onecase : true, // if one case switch statements should be allowed + regexp : true, // if the . should not be allowed in regexp literals + regexdash : true // if unescaped first/last dash (-) inside brackets + // should be tolerated + }, + + // These are the JSHint options that can take any value + // (we use this object to detect invalid options) + valOptions = { + maxlen : false, + indent : false, + maxerr : false, + predef : false, + quotmark : false, //'single'|'double'|true + scope : false, + maxstatements: false, // {int} max statements per function + maxdepth : false, // {int} max nested block depth per function + maxparams : false, // {int} max params per function + maxcomplexity: false, // {int} max cyclomatic complexity per function + unused : true, // warn if variables are unused. Available options: + // false - don't check for unused variables + // true - "vars" + check last function param + // "vars" - skip checking unused function params + // "strict" - "vars" + check all function params + latedef : false // warn if the variable is used before its definition + // false - don't emit any warnings + // true - warn if any variable is used before its definition + // "nofunc" - warn for any variable but function declarations + }, + + // These are JSHint boolean options which are shared with JSLint + // where the definition in JSHint is opposite JSLint + invertedOptions = { + bitwise : true, + forin : true, + newcap : true, + nomen : true, + plusplus: true, + regexp : true, + undef : true, + white : true, + + // Inverted and renamed, use JSHint name here + eqeqeq : true, + onevar : true, + strict : true + }, + + // These are JSHint boolean options which are shared with JSLint + // where the name has been changed but the effect is unchanged + renamedOptions = { + eqeq : "eqeqeq", + vars : "onevar", + windows: "wsh", + sloppy : "strict" + }, + + declared, // Globals that were declared using /*global ... */ syntax. + exported, // Variables that are used outside of the current file. + + functionicity = [ + "closure", "exception", "global", "label", + "outer", "unused", "var" + ], + + funct, // The current function + functions, // All of the functions + + global, // The global scope + implied, // Implied globals + inblock, + indent, + lookahead, + lex, + member, + membersOnly, + noreach, + predefined, // Global variables defined by option + + scope, // The current scope + stack, + unuseds, + urls, + warnings, + + extraModules = [], + emitter = new events.EventEmitter(); + + function checkOption(name, t) { + name = name.trim(); + + if (/^[+-]W\d{3}$/g.test(name)) { + return true; + } + + if (valOptions[name] === undefined && boolOptions[name] === undefined) { + if (t.type !== "jslint") { + error("E001", t, name); + return false; + } + } + + return true; + } + + function isString(obj) { + return Object.prototype.toString.call(obj) === "[object String]"; + } + + function isIdentifier(tkn, value) { + if (!tkn) + return false; + + if (!tkn.identifier || tkn.value !== value) + return false; + + return true; + } + + function isReserved(token) { + if (!token.reserved) { + return false; + } + + if (token.meta && token.meta.isFutureReservedWord) { + // ES3 FutureReservedWord in an ES5 environment. + if (state.option.inES5(true) && !token.meta.es5) { + return false; + } + + // Some ES5 FutureReservedWord identifiers are active only + // within a strict mode environment. + if (token.meta.strictOnly) { + if (!state.option.strict && !state.directive["use strict"]) { + return false; + } + } + + if (token.isProperty) { + return false; + } + } + + return true; + } + + function supplant(str, data) { + return str.replace(/\{([^{}]*)\}/g, function (a, b) { + var r = data[b]; + return typeof r === "string" || typeof r === "number" ? r : a; + }); + } + + function combine(t, o) { + var n; + for (n in o) { + if (_.has(o, n) && !_.has(JSHINT.blacklist, n)) { + t[n] = o[n]; + } + } + } + + function updatePredefined() { + Object.keys(JSHINT.blacklist).forEach(function (key) { + delete predefined[key]; + }); + } + + function assume() { + if (state.option.es5) { + warning("I003"); + } + if (state.option.couch) { + combine(predefined, vars.couch); + } + + if (state.option.rhino) { + combine(predefined, vars.rhino); + } + + if (state.option.phantom) { + combine(predefined, vars.phantom); + } + + if (state.option.prototypejs) { + combine(predefined, vars.prototypejs); + } + + if (state.option.node) { + combine(predefined, vars.node); + } + + if (state.option.devel) { + combine(predefined, vars.devel); + } + + if (state.option.dojo) { + combine(predefined, vars.dojo); + } + + if (state.option.browser) { + combine(predefined, vars.browser); + } + + if (state.option.nonstandard) { + combine(predefined, vars.nonstandard); + } + + if (state.option.jquery) { + combine(predefined, vars.jquery); + } + + if (state.option.mootools) { + combine(predefined, vars.mootools); + } + + if (state.option.worker) { + combine(predefined, vars.worker); + } + + if (state.option.wsh) { + combine(predefined, vars.wsh); + } + + if (state.option.globalstrict && state.option.strict !== false) { + state.option.strict = true; + } + + if (state.option.yui) { + combine(predefined, vars.yui); + } + + // Let's assume that chronologically ES3 < ES5 < ES6/ESNext < Moz + + state.option.inMoz = function (strict) { + if (strict) { + return state.option.moz && !state.option.esnext; + } + return state.option.moz; + }; + + state.option.inESNext = function (strict) { + if (strict) { + return !state.option.moz && state.option.esnext; + } + return state.option.moz || state.option.esnext; + }; + + state.option.inES5 = function (/* strict */) { + return !state.option.es3; + }; + + state.option.inES3 = function (strict) { + if (strict) { + return !state.option.moz && !state.option.esnext && state.option.es3; + } + return state.option.es3; + }; + } + + // Produce an error warning. + function quit(code, line, chr) { + var percentage = Math.floor((line / state.lines.length) * 100); + var message = messages.errors[code].desc; + + throw { + name: "JSHintError", + line: line, + character: chr, + message: message + " (" + percentage + "% scanned).", + raw: message + }; + } + + function isundef(scope, code, token, a) { + return JSHINT.undefs.push([scope, code, token, a]); + } + + function warning(code, t, a, b, c, d) { + var ch, l, w, msg; + + if (/^W\d{3}$/.test(code)) { + if (state.ignored[code]) + return; + + msg = messages.warnings[code]; + } else if (/E\d{3}/.test(code)) { + msg = messages.errors[code]; + } else if (/I\d{3}/.test(code)) { + msg = messages.info[code]; + } + + t = t || state.tokens.next; + if (t.id === "(end)") { // `~ + t = state.tokens.curr; + } + + l = t.line || 0; + ch = t.from || 0; + + w = { + id: "(error)", + raw: msg.desc, + code: msg.code, + evidence: state.lines[l - 1] || "", + line: l, + character: ch, + scope: JSHINT.scope, + a: a, + b: b, + c: c, + d: d + }; + + w.reason = supplant(msg.desc, w); + JSHINT.errors.push(w); + + if (state.option.passfail) { + quit("E042", l, ch); + } + + warnings += 1; + if (warnings >= state.option.maxerr) { + quit("E043", l, ch); + } + + return w; + } + + function warningAt(m, l, ch, a, b, c, d) { + return warning(m, { + line: l, + from: ch + }, a, b, c, d); + } + + function error(m, t, a, b, c, d) { + warning(m, t, a, b, c, d); + } + + function errorAt(m, l, ch, a, b, c, d) { + return error(m, { + line: l, + from: ch + }, a, b, c, d); + } + + // Tracking of "internal" scripts, like eval containing a static string + function addInternalSrc(elem, src) { + var i; + i = { + id: "(internal)", + elem: elem, + value: src + }; + JSHINT.internals.push(i); + return i; + } + + function addlabel(t, type, tkn, islet) { + // Define t in the current function in the current scope. + if (type === "exception") { + if (_.has(funct["(context)"], t)) { + if (funct[t] !== true && !state.option.node) { + warning("W002", state.tokens.next, t); + } + } + } + + if (_.has(funct, t) && !funct["(global)"]) { + if (funct[t] === true) { + if (state.option.latedef) { + if ((state.option.latedef === true && _.contains([funct[t], type], "unction")) || + !_.contains([funct[t], type], "unction")) { + warning("W003", state.tokens.next, t); + } + } + } else { + if (!state.option.shadow && type !== "exception" || + (funct["(blockscope)"].getlabel(t))) { + warning("W004", state.tokens.next, t); + } + } + } + + // a double definition of a let variable in same block throws a TypeError + //if (funct["(blockscope)"] && funct["(blockscope)"].current.has(t)) { + // error("E044", state.tokens.next, t); + //} + + // if the identifier is from a let, adds it only to the current blockscope + if (islet) { + funct["(blockscope)"].current.add(t, type, state.tokens.curr); + } else { + + funct[t] = type; + + if (tkn) { + funct["(tokens)"][t] = tkn; + } + + if (funct["(global)"]) { + global[t] = funct; + if (_.has(implied, t)) { + if (state.option.latedef) { + if ((state.option.latedef === true && _.contains([funct[t], type], "unction")) || + !_.contains([funct[t], type], "unction")) { + warning("W003", state.tokens.next, t); + } + } + + delete implied[t]; + } + } else { + scope[t] = funct; + } + } + } + + function doOption() { + var nt = state.tokens.next; + var body = nt.body.split(",").map(function (s) { return s.trim(); }); + var predef = {}; + + if (nt.type === "globals") { + body.forEach(function (g) { + g = g.split(":"); + var key = g[0]; + var val = g[1]; + + if (key.charAt(0) === "-") { + key = key.slice(1); + val = false; + + JSHINT.blacklist[key] = key; + updatePredefined(); + } else { + predef[key] = (val === "true"); + } + }); + + combine(predefined, predef); + + for (var key in predef) { + if (_.has(predef, key)) { + declared[key] = nt; + } + } + } + + if (nt.type === "exported") { + body.forEach(function (e) { + exported[e] = true; + }); + } + + if (nt.type === "members") { + membersOnly = membersOnly || {}; + + body.forEach(function (m) { + var ch1 = m.charAt(0); + var ch2 = m.charAt(m.length - 1); + + if (ch1 === ch2 && (ch1 === "\"" || ch1 === "'")) { + m = m + .substr(1, m.length - 2) + .replace("\\b", "\b") + .replace("\\t", "\t") + .replace("\\n", "\n") + .replace("\\v", "\v") + .replace("\\f", "\f") + .replace("\\r", "\r") + .replace("\\\\", "\\") + .replace("\\\"", "\""); + } + + membersOnly[m] = false; + }); + } + + var numvals = [ + "maxstatements", + "maxparams", + "maxdepth", + "maxcomplexity", + "maxerr", + "maxlen", + "indent" + ]; + + if (nt.type === "jshint" || nt.type === "jslint") { + body.forEach(function (g) { + g = g.split(":"); + var key = (g[0] || "").trim(); + var val = (g[1] || "").trim(); + + if (!checkOption(key, nt)) { + return; + } + + if (numvals.indexOf(key) >= 0) { + + // GH988 - numeric options can be disabled by setting them to `false` + if (val !== "false") { + val = +val; + + if (typeof val !== "number" || !isFinite(val) || val <= 0 || Math.floor(val) !== val) { + error("E032", nt, g[1].trim()); + return; + } + + if (key === "indent") { + state.option["(explicitIndent)"] = true; + } + state.option[key] = val; + } else { + if (key === "indent") { + state.option["(explicitIndent)"] = false; + } else { + state.option[key] = false; + } + } + + return; + } + + if (key === "validthis") { + // `validthis` is valid only within a function scope. + if (funct["(global)"]) { + error("E009"); + } else { + if (val === "true" || val === "false") { + state.option.validthis = (val === "true"); + } else { + error("E002", nt); + } + } + return; + } + + if (key === "quotmark") { + switch (val) { + case "true": + case "false": + state.option.quotmark = (val === "true"); + break; + case "double": + case "single": + state.option.quotmark = val; + break; + default: + error("E002", nt); + } + return; + } + + if (key === "unused") { + switch (val) { + case "true": + state.option.unused = true; + break; + case "false": + state.option.unused = false; + break; + case "vars": + case "strict": + state.option.unused = val; + break; + default: + error("E002", nt); + } + return; + } + + if (key === "latedef") { + switch (val) { + case "true": + state.option.latedef = true; + break; + case "false": + state.option.latedef = false; + break; + case "nofunc": + state.option.latedef = "nofunc"; + break; + default: + error("E002", nt); + } + return; + } + + var match = /^([+-])(W\d{3})$/g.exec(key); + if (match) { + // ignore for -W..., unignore for +W... + state.ignored[match[2]] = (match[1] === "-"); + return; + } + + var tn; + if (val === "true" || val === "false") { + if (nt.type === "jslint") { + tn = renamedOptions[key] || key; + state.option[tn] = (val === "true"); + + if (invertedOptions[tn] !== undefined) { + state.option[tn] = !state.option[tn]; + } + } else { + state.option[key] = (val === "true"); + } + + if (key === "newcap") { + state.option["(explicitNewcap)"] = true; + } + return; + } + + error("E002", nt); + }); + + assume(); + } + } + + // We need a peek function. If it has an argument, it peeks that much farther + // ahead. It is used to distinguish + // for ( var i in ... + // from + // for ( var i = ... + + function peek(p) { + var i = p || 0, j = 0, t; + + while (j <= i) { + t = lookahead[j]; + if (!t) { + t = lookahead[j] = lex.token(); + } + j += 1; + } + return t; + } + + // Produce the next token. It looks for programming errors. + + function advance(id, t) { + switch (state.tokens.curr.id) { + case "(number)": + if (state.tokens.next.id === ".") { + warning("W005", state.tokens.curr); + } + break; + case "-": + if (state.tokens.next.id === "-" || state.tokens.next.id === "--") { + warning("W006"); + } + break; + case "+": + if (state.tokens.next.id === "+" || state.tokens.next.id === "++") { + warning("W007"); + } + break; + } + + if (state.tokens.curr.type === "(string)" || state.tokens.curr.identifier) { + anonname = state.tokens.curr.value; + } + + if (id && state.tokens.next.id !== id) { + if (t) { + if (state.tokens.next.id === "(end)") { + error("E019", t, t.id); + } else { + error("E020", state.tokens.next, id, t.id, t.line, state.tokens.next.value); + } + } else if (state.tokens.next.type !== "(identifier)" || state.tokens.next.value !== id) { + warning("W116", state.tokens.next, id, state.tokens.next.value); + } + } + + state.tokens.prev = state.tokens.curr; + state.tokens.curr = state.tokens.next; + for (;;) { + state.tokens.next = lookahead.shift() || lex.token(); + + if (!state.tokens.next) { // No more tokens left, give up + quit("E041", state.tokens.curr.line); + } + + if (state.tokens.next.id === "(end)" || state.tokens.next.id === "(error)") { + return; + } + + if (state.tokens.next.check) { + state.tokens.next.check(); + } + + if (state.tokens.next.isSpecial) { + doOption(); + } else { + if (state.tokens.next.id !== "(endline)") { + break; + } + } + } + } + + // This is the heart of JSHINT, the Pratt parser. In addition to parsing, it + // is looking for ad hoc lint patterns. We add .fud to Pratt's model, which is + // like .nud except that it is only used on the first token of a statement. + // Having .fud makes it much easier to define statement-oriented languages like + // JavaScript. I retained Pratt's nomenclature. + + // .nud Null denotation + // .fud First null denotation + // .led Left denotation + // lbp Left binding power + // rbp Right binding power + + // They are elements of the parsing method called Top Down Operator Precedence. + + function expression(rbp, initial) { + var left, isArray = false, isObject = false, isLetExpr = false; + + // if current expression is a let expression + if (!initial && state.tokens.next.value === "let" && peek(0).value === "(") { + if (!state.option.inMoz(true)) { + warning("W118", state.tokens.next, "let expressions"); + } + isLetExpr = true; + // create a new block scope we use only for the current expression + funct["(blockscope)"].stack(); + advance("let"); + advance("("); + state.syntax["let"].fud.call(state.syntax["let"].fud, false); + advance(")"); + } + + if (state.tokens.next.id === "(end)") + error("E006", state.tokens.curr); + + advance(); + + if (initial) { + anonname = "anonymous"; + funct["(verb)"] = state.tokens.curr.value; + } + + if (initial === true && state.tokens.curr.fud) { + left = state.tokens.curr.fud(); + } else { + if (state.tokens.curr.nud) { + left = state.tokens.curr.nud(); + } else { + error("E030", state.tokens.curr, state.tokens.curr.id); + } + + var end_of_expr = state.tokens.next.identifier && + !state.tokens.curr.led && + state.tokens.curr.line !== state.tokens.next.line; + while (rbp < state.tokens.next.lbp && !end_of_expr) { + isArray = state.tokens.curr.value === "Array"; + isObject = state.tokens.curr.value === "Object"; + + // #527, new Foo.Array(), Foo.Array(), new Foo.Object(), Foo.Object() + // Line breaks in IfStatement heads exist to satisfy the checkJSHint + // "Line too long." error. + if (left && (left.value || (left.first && left.first.value))) { + // If the left.value is not "new", or the left.first.value is a "." + // then safely assume that this is not "new Array()" and possibly + // not "new Object()"... + if (left.value !== "new" || + (left.first && left.first.value && left.first.value === ".")) { + isArray = false; + // ...In the case of Object, if the left.value and state.tokens.curr.value + // are not equal, then safely assume that this not "new Object()" + if (left.value !== state.tokens.curr.value) { + isObject = false; + } + } + } + + advance(); + + if (isArray && state.tokens.curr.id === "(" && state.tokens.next.id === ")") { + warning("W009", state.tokens.curr); + } + + if (isObject && state.tokens.curr.id === "(" && state.tokens.next.id === ")") { + warning("W010", state.tokens.curr); + } + + if (left && state.tokens.curr.led) { + left = state.tokens.curr.led(left); + } else { + error("E033", state.tokens.curr, state.tokens.curr.id); + } + } + } + if (isLetExpr) { + funct["(blockscope)"].unstack(); + } + return left; + } + + +// Functions for conformance of style. + + function adjacent(left, right) { + left = left || state.tokens.curr; + right = right || state.tokens.next; + if (state.option.white) { + if (left.character !== right.from && left.line === right.line) { + left.from += (left.character - left.from); + warning("W011", left, left.value); + } + } + } + + function nobreak(left, right) { + left = left || state.tokens.curr; + right = right || state.tokens.next; + if (state.option.white && (left.character !== right.from || left.line !== right.line)) { + warning("W012", right, right.value); + } + } + + function nospace(left, right) { + left = left || state.tokens.curr; + right = right || state.tokens.next; + if (state.option.white && !left.comment) { + if (left.line === right.line) { + adjacent(left, right); + } + } + } + + function nonadjacent(left, right) { + if (state.option.white) { + left = left || state.tokens.curr; + right = right || state.tokens.next; + + if (left.value === ";" && right.value === ";") { + return; + } + + if (left.line === right.line && left.character === right.from) { + left.from += (left.character - left.from); + warning("W013", left, left.value); + } + } + } + + function nobreaknonadjacent(left, right) { + left = left || state.tokens.curr; + right = right || state.tokens.next; + if (!state.option.laxbreak && left.line !== right.line) { + warning("W014", right, right.id); + } else if (state.option.white) { + left = left || state.tokens.curr; + right = right || state.tokens.next; + if (left.character === right.from) { + left.from += (left.character - left.from); + warning("W013", left, left.value); + } + } + } + + function indentation(bias) { + if (!state.option.white && !state.option["(explicitIndent)"]) { + return; + } + + if (state.tokens.next.id === "(end)") { + return; + } + + var i = indent + (bias || 0); + if (state.tokens.next.from !== i) { + warning("W015", state.tokens.next, state.tokens.next.value, i, state.tokens.next.from); + } + } + + function nolinebreak(t) { + t = t || state.tokens.curr; + if (t.line !== state.tokens.next.line) { + warning("E022", t, t.value); + } + } + + + function comma(opts) { + opts = opts || {}; + + if (!opts.peek) { + if (state.tokens.curr.line !== state.tokens.next.line) { + if (!state.option.laxcomma) { + if (comma.first) { + warning("I001"); + comma.first = false; + } + warning("W014", state.tokens.curr, state.tokens.next.value); + } + } else if (!state.tokens.curr.comment && + state.tokens.curr.character !== state.tokens.next.from && state.option.white) { + state.tokens.curr.from += (state.tokens.curr.character - state.tokens.curr.from); + warning("W011", state.tokens.curr, state.tokens.curr.value); + } + + advance(","); + } + + // TODO: This is a temporary solution to fight against false-positives in + // arrays and objects with trailing commas (see GH-363). The best solution + // would be to extract all whitespace rules out of parser. + + if (state.tokens.next.value !== "]" && state.tokens.next.value !== "}") { + nonadjacent(state.tokens.curr, state.tokens.next); + } + + if (state.tokens.next.identifier && !(opts.property && state.option.inES5())) { + // Keywords that cannot follow a comma operator. + switch (state.tokens.next.value) { + case "break": + case "case": + case "catch": + case "continue": + case "default": + case "do": + case "else": + case "finally": + case "for": + case "if": + case "in": + case "instanceof": + case "return": + case "yield": + case "switch": + case "throw": + case "try": + case "var": + case "let": + case "while": + case "with": + error("E024", state.tokens.next, state.tokens.next.value); + return false; + } + } + + if (state.tokens.next.type === "(punctuator)") { + switch (state.tokens.next.value) { + case "}": + case "]": + case ",": + if (opts.allowTrailing) { + return true; + } + + /* falls through */ + case ")": + error("E024", state.tokens.next, state.tokens.next.value); + return false; + } + } + return true; + } + + // Functional constructors for making the symbols that will be inherited by + // tokens. + + function symbol(s, p) { + var x = state.syntax[s]; + if (!x || typeof x !== "object") { + state.syntax[s] = x = { + id: s, + lbp: p, + value: s + }; + } + return x; + } + + function delim(s) { + return symbol(s, 0); + } + + function stmt(s, f) { + var x = delim(s); + x.identifier = x.reserved = true; + x.fud = f; + return x; + } + + function blockstmt(s, f) { + var x = stmt(s, f); + x.block = true; + return x; + } + + function reserveName(x) { + var c = x.id.charAt(0); + if ((c >= "a" && c <= "z") || (c >= "A" && c <= "Z")) { + x.identifier = x.reserved = true; + } + return x; + } + + function prefix(s, f) { + var x = symbol(s, 150); + reserveName(x); + x.nud = (typeof f === "function") ? f : function () { + this.right = expression(150); + this.arity = "unary"; + if (this.id === "++" || this.id === "--") { + if (state.option.plusplus) { + warning("W016", this, this.id); + } else if ((!this.right.identifier || isReserved(this.right)) && + this.right.id !== "." && this.right.id !== "[") { + warning("W017", this); + } + } + return this; + }; + return x; + } + + function type(s, f) { + var x = delim(s); + x.type = s; + x.nud = f; + return x; + } + + function reserve(name, func) { + var x = type(name, func); + x.identifier = true; + x.reserved = true; + return x; + } + + function FutureReservedWord(name, meta) { + var x = type(name, (meta && meta.nud) || function () { + return this; + }); + + meta = meta || {}; + meta.isFutureReservedWord = true; + + x.value = name; + x.identifier = true; + x.reserved = true; + x.meta = meta; + + return x; + } + + function reservevar(s, v) { + return reserve(s, function () { + if (typeof v === "function") { + v(this); + } + return this; + }); + } + + function infix(s, f, p, w) { + var x = symbol(s, p); + reserveName(x); + x.led = function (left) { + if (!w) { + nobreaknonadjacent(state.tokens.prev, state.tokens.curr); + nonadjacent(state.tokens.curr, state.tokens.next); + } + if (s === "in" && left.id === "!") { + warning("W018", left, "!"); + } + if (typeof f === "function") { + return f(left, this); + } else { + this.left = left; + this.right = expression(p); + return this; + } + }; + return x; + } + + + function application(s) { + var x = symbol(s, 42); + + x.led = function (left) { + if (!state.option.inESNext()) { + warning("W104", state.tokens.curr, "arrow function syntax (=>)"); + } + + nobreaknonadjacent(state.tokens.prev, state.tokens.curr); + nonadjacent(state.tokens.curr, state.tokens.next); + + this.left = left; + this.right = doFunction(undefined, undefined, false, left); + return this; + }; + return x; + } + + function relation(s, f) { + var x = symbol(s, 100); + + x.led = function (left) { + nobreaknonadjacent(state.tokens.prev, state.tokens.curr); + nonadjacent(state.tokens.curr, state.tokens.next); + var right = expression(100); + + if (isIdentifier(left, "NaN") || isIdentifier(right, "NaN")) { + warning("W019", this); + } else if (f) { + f.apply(this, [left, right]); + } + + if (!left || !right) { + quit("E041", state.tokens.curr.line); + } + + if (left.id === "!") { + warning("W018", left, "!"); + } + + if (right.id === "!") { + warning("W018", right, "!"); + } + + this.left = left; + this.right = right; + return this; + }; + return x; + } + + function isPoorRelation(node) { + return node && + ((node.type === "(number)" && +node.value === 0) || + (node.type === "(string)" && node.value === "") || + (node.type === "null" && !state.option.eqnull) || + node.type === "true" || + node.type === "false" || + node.type === "undefined"); + } + + function assignop(s) { + symbol(s, 20).exps = true; + + return infix(s, function (left, that) { + that.left = left; + + if (left) { + if (predefined[left.value] === false && + scope[left.value]["(global)"] === true) { + warning("W020", left); + } else if (left["function"]) { + warning("W021", left, left.value); + } + + if (funct[left.value] === "const") { + error("E013", left, left.value); + } + + if (left.id === ".") { + if (!left.left) { + warning("E031", that); + } else if (left.left.value === "arguments" && !state.directive["use strict"]) { + warning("E031", that); + } + + that.right = expression(19); + return that; + } else if (left.id === "[") { + if (state.tokens.curr.left.first) { + state.tokens.curr.left.first.forEach(function (t) { + if (funct[t.value] === "const") { + error("E013", t, t.value); + } + }); + } else if (!left.left) { + warning("E031", that); + } else if (left.left.value === "arguments" && !state.directive["use strict"]) { + warning("E031", that); + } + that.right = expression(19); + return that; + } else if (left.identifier && !isReserved(left)) { + if (funct[left.value] === "exception") { + warning("W022", left); + } + that.right = expression(19); + return that; + } + + if (left === state.syntax["function"]) { + warning("W023", state.tokens.curr); + } + } + + error("E031", that); + }, 20); + } + + + function bitwise(s, f, p) { + var x = symbol(s, p); + reserveName(x); + x.led = (typeof f === "function") ? f : function (left) { + if (state.option.bitwise) { + warning("W016", this, this.id); + } + this.left = left; + this.right = expression(p); + return this; + }; + return x; + } + + + function bitwiseassignop(s) { + symbol(s, 20).exps = true; + return infix(s, function (left, that) { + if (state.option.bitwise) { + warning("W016", that, that.id); + } + nonadjacent(state.tokens.prev, state.tokens.curr); + nonadjacent(state.tokens.curr, state.tokens.next); + if (left) { + if (left.id === "." || left.id === "[" || + (left.identifier && !isReserved(left))) { + expression(19); + return that; + } + if (left === state.syntax["function"]) { + warning("W023", state.tokens.curr); + } + return that; + } + error("E031", that); + }, 20); + } + + + function suffix(s) { + var x = symbol(s, 150); + + x.led = function (left) { + if (state.option.plusplus) { + warning("W016", this, this.id); + } else if ((!left.identifier || isReserved(left)) && left.id !== "." && left.id !== "[") { + warning("W017", this); + } + + this.left = left; + return this; + }; + return x; + } + + // fnparam means that this identifier is being defined as a function + // argument (see identifier()) + // prop means that this identifier is that of an object property + + function optionalidentifier(fnparam, prop) { + if (!state.tokens.next.identifier) { + return; + } + + advance(); + + var curr = state.tokens.curr; + var meta = curr.meta || {}; + var val = state.tokens.curr.value; + + if (!isReserved(curr)) { + return val; + } + + if (prop) { + if (state.option.inES5() || meta.isFutureReservedWord) { + return val; + } + } + + if (fnparam && val === "undefined") { + return val; + } + + // Display an info message about reserved words as properties + // and ES5 but do it only once. + if (prop && !api.getCache("displayed:I002")) { + api.setCache("displayed:I002", true); + warning("I002"); + } + + warning("W024", state.tokens.curr, state.tokens.curr.id); + return val; + } + + // fnparam means that this identifier is being defined as a function + // argument + // prop means that this identifier is that of an object property + function identifier(fnparam, prop) { + var i = optionalidentifier(fnparam, prop); + if (i) { + return i; + } + if (state.tokens.curr.id === "function" && state.tokens.next.id === "(") { + warning("W025"); + } else { + error("E030", state.tokens.next, state.tokens.next.value); + } + } + + + function reachable(s) { + var i = 0, t; + if (state.tokens.next.id !== ";" || noreach) { + return; + } + for (;;) { + t = peek(i); + if (t.reach) { + return; + } + if (t.id !== "(endline)") { + if (t.id === "function") { + if (!state.option.latedef) { + break; + } + + warning("W026", t); + break; + } + + warning("W027", t, t.value, s); + break; + } + i += 1; + } + } + + + function statement(noindent) { + var values; + var i = indent, r, s = scope, t = state.tokens.next; + + if (t.id === ";") { + advance(";"); + return; + } + + // Is this a labelled statement? + var res = isReserved(t); + + // We're being more tolerant here: if someone uses + // a FutureReservedWord as a label, we warn but proceed + // anyway. + + if (res && t.meta && t.meta.isFutureReservedWord && peek().id === ":") { + warning("W024", t, t.id); + res = false; + } + + // detect a destructuring assignment + if (_.has(["[", "{"], t.value)) { + if (lookupBlockType().isDestAssign) { + if (!state.option.inESNext()) { + warning("W104", state.tokens.curr, "destructuring expression"); + } + values = destructuringExpression(); + values.forEach(function (tok) { + isundef(funct, "W117", tok.token, tok.id); + }); + advance("="); + destructuringExpressionMatch(values, expression(5, true)); + advance(";"); + return; + } + } + if (t.identifier && !res && peek().id === ":") { + advance(); + advance(":"); + scope = Object.create(s); + addlabel(t.value, "label"); + + if (!state.tokens.next.labelled && state.tokens.next.value !== "{") { + warning("W028", state.tokens.next, t.value, state.tokens.next.value); + } + + state.tokens.next.label = t.value; + t = state.tokens.next; + } + + // Is it a lonely block? + + if (t.id === "{") { + // Is it a switch case block? + // + // switch (foo) { + // case bar: { <= here. + // ... + // } + // } + var iscase = (funct["(verb)"] === "case" && state.tokens.curr.value === ":"); + block(true, true, false, false, iscase); + return; + } + + // Parse the statement. + + if (!noindent) { + indentation(); + } + r = expression(0, true); + + // Look for the final semicolon. + + if (!t.block) { + if (!state.option.expr && (!r || !r.exps)) { + warning("W030", state.tokens.curr); + } else if (state.option.nonew && r && r.left && r.id === "(" && r.left.id === "new") { + warning("W031", t); + } + + if (state.tokens.next.id !== ";") { + if (!state.option.asi) { + // If this is the last statement in a block that ends on + // the same line *and* option lastsemic is on, ignore the warning. + // Otherwise, complain about missing semicolon. + if (!state.option.lastsemic || state.tokens.next.id !== "}" || + state.tokens.next.line !== state.tokens.curr.line) { + warningAt("W033", state.tokens.curr.line, state.tokens.curr.character); + } + } + } else { + adjacent(state.tokens.curr, state.tokens.next); + advance(";"); + nonadjacent(state.tokens.curr, state.tokens.next); + } + } + + // Restore the indentation. + + indent = i; + scope = s; + return r; + } + + + function statements(startLine) { + var a = [], p; + + while (!state.tokens.next.reach && state.tokens.next.id !== "(end)") { + if (state.tokens.next.id === ";") { + p = peek(); + + if (!p || (p.id !== "(" && p.id !== "[")) { + warning("W032"); + } + + advance(";"); + } else { + a.push(statement(startLine === state.tokens.next.line)); + } + } + return a; + } + + + /* + * read all directives + * recognizes a simple form of asi, but always + * warns, if it is used + */ + function directives() { + var i, p, pn; + + for (;;) { + if (state.tokens.next.id === "(string)") { + p = peek(0); + if (p.id === "(endline)") { + i = 1; + do { + pn = peek(i); + i = i + 1; + } while (pn.id === "(endline)"); + + if (pn.id !== ";") { + if (pn.id !== "(string)" && pn.id !== "(number)" && + pn.id !== "(regexp)" && pn.identifier !== true && + pn.id !== "}") { + break; + } + warning("W033", state.tokens.next); + } else { + p = pn; + } + } else if (p.id === "}") { + // Directive with no other statements, warn about missing semicolon + warning("W033", p); + } else if (p.id !== ";") { + break; + } + + indentation(); + advance(); + if (state.directive[state.tokens.curr.value]) { + warning("W034", state.tokens.curr, state.tokens.curr.value); + } + + if (state.tokens.curr.value === "use strict") { + if (!state.option["(explicitNewcap)"]) + state.option.newcap = true; + state.option.undef = true; + } + + // there's no directive negation, so always set to true + state.directive[state.tokens.curr.value] = true; + + if (p.id === ";") { + advance(";"); + } + continue; + } + break; + } + } + + + /* + * Parses a single block. A block is a sequence of statements wrapped in + * braces. + * + * ordinary - true for everything but function bodies and try blocks. + * stmt - true if block can be a single statement (e.g. in if/for/while). + * isfunc - true if block is a function body + * isfatarrow - + * iscase - true if block is a switch case block + */ + function block(ordinary, stmt, isfunc, isfatarrow, iscase) { + var a, + b = inblock, + old_indent = indent, + m, + s = scope, + t, + line, + d; + + inblock = ordinary; + + if (!ordinary || !state.option.funcscope) + scope = Object.create(scope); + + nonadjacent(state.tokens.curr, state.tokens.next); + t = state.tokens.next; + + var metrics = funct["(metrics)"]; + metrics.nestedBlockDepth += 1; + metrics.verifyMaxNestedBlockDepthPerFunction(); + + if (state.tokens.next.id === "{") { + advance("{"); + + // create a new block scope + funct["(blockscope)"].stack(); + + line = state.tokens.curr.line; + if (state.tokens.next.id !== "}") { + indent += state.option.indent; + while (!ordinary && state.tokens.next.from > indent) { + indent += state.option.indent; + } + + if (isfunc) { + m = {}; + for (d in state.directive) { + if (_.has(state.directive, d)) { + m[d] = state.directive[d]; + } + } + directives(); + + if (state.option.strict && funct["(context)"]["(global)"]) { + if (!m["use strict"] && !state.directive["use strict"]) { + warning("E007"); + } + } + } + + a = statements(line); + + metrics.statementCount += a.length; + + if (isfunc) { + state.directive = m; + } + + indent -= state.option.indent; + if (line !== state.tokens.next.line) { + indentation(); + } + } else if (line !== state.tokens.next.line) { + indentation(); + } + advance("}", t); + + funct["(blockscope)"].unstack(); + + indent = old_indent; + } else if (!ordinary) { + if (isfunc) { + m = {}; + if (stmt && !isfatarrow && !state.option.inMoz(true)) { + error("W118", state.tokens.curr, "function closure expressions"); + } + + if (!stmt) { + for (d in state.directive) { + if (_.has(state.directive, d)) { + m[d] = state.directive[d]; + } + } + } + expression(5); + + if (state.option.strict && funct["(context)"]["(global)"]) { + if (!m["use strict"] && !state.directive["use strict"]) { + warning("E007"); + } + } + } else { + error("E021", state.tokens.next, "{", state.tokens.next.value); + } + } else { + + // check to avoid let declaration not within a block + funct["(nolet)"] = true; + + if (!stmt || state.option.curly) { + warning("W116", state.tokens.next, "{", state.tokens.next.value); + } + + noreach = true; + indent += state.option.indent; + // test indentation only if statement is in new line + a = [statement(state.tokens.next.line === state.tokens.curr.line)]; + indent -= state.option.indent; + noreach = false; + + delete funct["(nolet)"]; + } + // If it is a "break" in switch case, don't clear and let it propagate out. + if (!(iscase && funct["(verb)"] === "break")) funct["(verb)"] = null; + + if (!ordinary || !state.option.funcscope) scope = s; + inblock = b; + if (ordinary && state.option.noempty && (!a || a.length === 0)) { + warning("W035"); + } + metrics.nestedBlockDepth -= 1; + return a; + } + + + function countMember(m) { + if (membersOnly && typeof membersOnly[m] !== "boolean") { + warning("W036", state.tokens.curr, m); + } + if (typeof member[m] === "number") { + member[m] += 1; + } else { + member[m] = 1; + } + } + + + function note_implied(tkn) { + var name = tkn.value, line = tkn.line, a = implied[name]; + if (typeof a === "function") { + a = false; + } + + if (!a) { + a = [line]; + implied[name] = a; + } else if (a[a.length - 1] !== line) { + a.push(line); + } + } + + + // Build the syntax table by declaring the syntactic elements of the language. + + type("(number)", function () { + return this; + }); + + type("(string)", function () { + return this; + }); + + state.syntax["(identifier)"] = { + type: "(identifier)", + lbp: 0, + identifier: true, + nud: function () { + var v = this.value, + s = scope[v], + f; + + if (typeof s === "function") { + // Protection against accidental inheritance. + s = undefined; + } else if (typeof s === "boolean") { + f = funct; + funct = functions[0]; + addlabel(v, "var"); + s = funct; + funct = f; + } + var block; + if (_.has(funct, "(blockscope)")) { + block = funct["(blockscope)"].getlabel(v); + } + + // The name is in scope and defined in the current function. + if (funct === s || block) { + // Change 'unused' to 'var', and reject labels. + // the name is in a block scope + switch (block ? block[v]["(type)"] : funct[v]) { + case "unused": + if (block) block[v]["(type)"] = "var"; + else funct[v] = "var"; + break; + case "unction": + if (block) block[v]["(type)"] = "function"; + else funct[v] = "function"; + this["function"] = true; + break; + case "function": + this["function"] = true; + break; + case "label": + warning("W037", state.tokens.curr, v); + break; + } + } else if (funct["(global)"]) { + // The name is not defined in the function. If we are in the global + // scope, then we have an undefined variable. + // + // Operators typeof and delete do not raise runtime errors even if + // the base object of a reference is null so no need to display warning + // if we're inside of typeof or delete. + + if (typeof predefined[v] !== "boolean") { + // Attempting to subscript a null reference will throw an + // error, even within the typeof and delete operators + if (!(anonname === "typeof" || anonname === "delete") || + (state.tokens.next && (state.tokens.next.value === "." || + state.tokens.next.value === "["))) { + + // if we're in a list comprehension, variables are declared + // locally and used before being defined. So we check + // the presence of the given variable in the comp array + // before declaring it undefined. + + if (!funct["(comparray)"].check(v)) { + isundef(funct, "W117", state.tokens.curr, v); + } + } + } + + note_implied(state.tokens.curr); + } else { + // If the name is already defined in the current + // function, but not as outer, then there is a scope error. + + switch (funct[v]) { + case "closure": + case "function": + case "var": + case "unused": + warning("W038", state.tokens.curr, v); + break; + case "label": + warning("W037", state.tokens.curr, v); + break; + case "outer": + case "global": + break; + default: + // If the name is defined in an outer function, make an outer entry, + // and if it was unused, make it var. + if (s === true) { + funct[v] = true; + } else if (s === null) { + warning("W039", state.tokens.curr, v); + note_implied(state.tokens.curr); + } else if (typeof s !== "object") { + // Operators typeof and delete do not raise runtime errors even + // if the base object of a reference is null so no need to + // + // display warning if we're inside of typeof or delete. + // Attempting to subscript a null reference will throw an + // error, even within the typeof and delete operators + if (!(anonname === "typeof" || anonname === "delete") || + (state.tokens.next && + (state.tokens.next.value === "." || state.tokens.next.value === "["))) { + + isundef(funct, "W117", state.tokens.curr, v); + } + funct[v] = true; + note_implied(state.tokens.curr); + } else { + switch (s[v]) { + case "function": + case "unction": + this["function"] = true; + s[v] = "closure"; + funct[v] = s["(global)"] ? "global" : "outer"; + break; + case "var": + case "unused": + s[v] = "closure"; + funct[v] = s["(global)"] ? "global" : "outer"; + break; + case "closure": + funct[v] = s["(global)"] ? "global" : "outer"; + break; + case "label": + warning("W037", state.tokens.curr, v); + } + } + } + } + return this; + }, + led: function () { + error("E033", state.tokens.next, state.tokens.next.value); + } + }; + + type("(regexp)", function () { + return this; + }); + + // ECMAScript parser + + delim("(endline)"); + delim("(begin)"); + delim("(end)").reach = true; + delim("(error)").reach = true; + delim("}").reach = true; + delim(")"); + delim("]"); + delim("\"").reach = true; + delim("'").reach = true; + delim(";"); + delim(":").reach = true; + delim("#"); + + reserve("else"); + reserve("case").reach = true; + reserve("catch"); + reserve("default").reach = true; + reserve("finally"); + reservevar("arguments", function (x) { + if (state.directive["use strict"] && funct["(global)"]) { + warning("E008", x); + } + }); + reservevar("eval"); + reservevar("false"); + reservevar("Infinity"); + reservevar("null"); + reservevar("this", function (x) { + if (state.directive["use strict"] && !state.option.validthis && ((funct["(statement)"] && + funct["(name)"].charAt(0) > "Z") || funct["(global)"])) { + warning("W040", x); + } + }); + reservevar("true"); + reservevar("undefined"); + + assignop("=", "assign", 20); + assignop("+=", "assignadd", 20); + assignop("-=", "assignsub", 20); + assignop("*=", "assignmult", 20); + assignop("/=", "assigndiv", 20).nud = function () { + error("E014"); + }; + assignop("%=", "assignmod", 20); + + bitwiseassignop("&=", "assignbitand", 20); + bitwiseassignop("|=", "assignbitor", 20); + bitwiseassignop("^=", "assignbitxor", 20); + bitwiseassignop("<<=", "assignshiftleft", 20); + bitwiseassignop(">>=", "assignshiftright", 20); + bitwiseassignop(">>>=", "assignshiftrightunsigned", 20); + infix(",", function (left, that) { + var expr; + that.exprs = [left]; + if (!comma({peek: true})) { + return that; + } + while (true) { + if (!(expr = expression(5))) { + break; + } + that.exprs.push(expr); + if (state.tokens.next.value !== "," || !comma()) { + break; + } + } + return that; + }, 5, true); + infix("?", function (left, that) { + that.left = left; + that.right = expression(10); + advance(":"); + that["else"] = expression(10); + return that; + }, 30); + + infix("||", "or", 40); + infix("&&", "and", 50); + bitwise("|", "bitor", 70); + bitwise("^", "bitxor", 80); + bitwise("&", "bitand", 90); + relation("==", function (left, right) { + var eqnull = state.option.eqnull && (left.value === "null" || right.value === "null"); + + if (!eqnull && state.option.eqeqeq) + warning("W116", this, "===", "=="); + else if (isPoorRelation(left)) + warning("W041", this, "===", left.value); + else if (isPoorRelation(right)) + warning("W041", this, "===", right.value); + + return this; + }); + relation("==="); + relation("!=", function (left, right) { + var eqnull = state.option.eqnull && + (left.value === "null" || right.value === "null"); + + if (!eqnull && state.option.eqeqeq) { + warning("W116", this, "!==", "!="); + } else if (isPoorRelation(left)) { + warning("W041", this, "!==", left.value); + } else if (isPoorRelation(right)) { + warning("W041", this, "!==", right.value); + } + return this; + }); + relation("!=="); + relation("<"); + relation(">"); + relation("<="); + relation(">="); + bitwise("<<", "shiftleft", 120); + bitwise(">>", "shiftright", 120); + bitwise(">>>", "shiftrightunsigned", 120); + infix("in", "in", 120); + infix("instanceof", "instanceof", 120); + infix("+", function (left, that) { + var right = expression(130); + if (left && right && left.id === "(string)" && right.id === "(string)") { + left.value += right.value; + left.character = right.character; + if (!state.option.scripturl && reg.javascriptURL.test(left.value)) { + warning("W050", left); + } + return left; + } + that.left = left; + that.right = right; + return that; + }, 130); + prefix("+", "num"); + prefix("+++", function () { + warning("W007"); + this.right = expression(150); + this.arity = "unary"; + return this; + }); + infix("+++", function (left) { + warning("W007"); + this.left = left; + this.right = expression(130); + return this; + }, 130); + infix("-", "sub", 130); + prefix("-", "neg"); + prefix("---", function () { + warning("W006"); + this.right = expression(150); + this.arity = "unary"; + return this; + }); + infix("---", function (left) { + warning("W006"); + this.left = left; + this.right = expression(130); + return this; + }, 130); + infix("*", "mult", 140); + infix("/", "div", 140); + infix("%", "mod", 140); + + suffix("++", "postinc"); + prefix("++", "preinc"); + state.syntax["++"].exps = true; + + suffix("--", "postdec"); + prefix("--", "predec"); + state.syntax["--"].exps = true; + prefix("delete", function () { + var p = expression(5); + if (!p || (p.id !== "." && p.id !== "[")) { + warning("W051"); + } + this.first = p; + return this; + }).exps = true; + + prefix("~", function () { + if (state.option.bitwise) { + warning("W052", this, "~"); + } + expression(150); + return this; + }); + + prefix("...", function () { + if (!state.option.inESNext()) { + warning("W104", this, "spread/rest operator"); + } + if (!state.tokens.next.identifier) { + error("E030", state.tokens.next, state.tokens.next.value); + } + expression(150); + return this; + }); + + prefix("!", function () { + this.right = expression(150); + this.arity = "unary"; + + if (!this.right) { // '!' followed by nothing? Give up. + quit("E041", this.line || 0); + } + + if (bang[this.right.id] === true) { + warning("W018", this, "!"); + } + return this; + }); + + prefix("typeof", "typeof"); + prefix("new", function () { + var c = expression(155), i; + if (c && c.id !== "function") { + if (c.identifier) { + c["new"] = true; + switch (c.value) { + case "Number": + case "String": + case "Boolean": + case "Math": + case "JSON": + warning("W053", state.tokens.prev, c.value); + break; + case "Function": + if (!state.option.evil) { + warning("W054"); + } + break; + case "Date": + case "RegExp": + break; + default: + if (c.id !== "function") { + i = c.value.substr(0, 1); + if (state.option.newcap && (i < "A" || i > "Z") && !_.has(global, c.value)) { + warning("W055", state.tokens.curr); + } + } + } + } else { + if (c.id !== "." && c.id !== "[" && c.id !== "(") { + warning("W056", state.tokens.curr); + } + } + } else { + if (!state.option.supernew) + warning("W057", this); + } + adjacent(state.tokens.curr, state.tokens.next); + if (state.tokens.next.id !== "(" && !state.option.supernew) { + warning("W058", state.tokens.curr, state.tokens.curr.value); + } + this.first = c; + return this; + }); + state.syntax["new"].exps = true; + + prefix("void").exps = true; + + infix(".", function (left, that) { + adjacent(state.tokens.prev, state.tokens.curr); + nobreak(); + var m = identifier(false, true); + + if (typeof m === "string") { + countMember(m); + } + + that.left = left; + that.right = m; + + if (m && m === "hasOwnProperty" && state.tokens.next.value === "=") { + warning("W001"); + } + + if (left && left.value === "arguments" && (m === "callee" || m === "caller")) { + if (state.option.noarg) + warning("W059", left, m); + else if (state.directive["use strict"]) + error("E008"); + } else if (!state.option.evil && left && left.value === "document" && + (m === "write" || m === "writeln")) { + warning("W060", left); + } + + if (!state.option.evil && (m === "eval" || m === "execScript")) { + warning("W061"); + } + + return that; + }, 160, true); + + infix("(", function (left, that) { + if (state.tokens.prev.id !== "}" && state.tokens.prev.id !== ")") { + nobreak(state.tokens.prev, state.tokens.curr); + } + + nospace(); + if (state.option.immed && left && !left.immed && left.id === "function") { + warning("W062"); + } + + var n = 0; + var p = []; + + if (left) { + if (left.type === "(identifier)") { + if (left.value.match(/^[A-Z]([A-Z0-9_$]*[a-z][A-Za-z0-9_$]*)?$/)) { + if ("Number String Boolean Date Object".indexOf(left.value) === -1) { + if (left.value === "Math") { + warning("W063", left); + } else if (state.option.newcap) { + warning("W064", left); + } + } + } + } + } + + if (state.tokens.next.id !== ")") { + for (;;) { + p[p.length] = expression(10); + n += 1; + if (state.tokens.next.id !== ",") { + break; + } + comma(); + } + } + + advance(")"); + nospace(state.tokens.prev, state.tokens.curr); + + if (typeof left === "object") { + if (left.value === "parseInt" && n === 1) { + warning("W065", state.tokens.curr); + } + if (!state.option.evil) { + if (left.value === "eval" || left.value === "Function" || + left.value === "execScript") { + warning("W061", left); + + if (p[0] && [0].id === "(string)") { + addInternalSrc(left, p[0].value); + } + } else if (p[0] && p[0].id === "(string)" && + (left.value === "setTimeout" || + left.value === "setInterval")) { + warning("W066", left); + addInternalSrc(left, p[0].value); + + // window.setTimeout/setInterval + } else if (p[0] && p[0].id === "(string)" && + left.value === "." && + left.left.value === "window" && + (left.right === "setTimeout" || + left.right === "setInterval")) { + warning("W066", left); + addInternalSrc(left, p[0].value); + } + } + if (!left.identifier && left.id !== "." && left.id !== "[" && + left.id !== "(" && left.id !== "&&" && left.id !== "||" && + left.id !== "?") { + warning("W067", left); + } + } + + that.left = left; + return that; + }, 155, true).exps = true; + + prefix("(", function () { + nospace(); + var bracket, brackets = []; + var pn, pn1, i = 0; + + do { + pn = peek(i); + i += 1; + pn1 = peek(i); + i += 1; + } while (pn.value !== ")" && pn1.value !== "=>" && pn1.value !== ";" && pn1.type !== "(end)"); + + if (state.tokens.next.id === "function") { + state.tokens.next.immed = true; + } + + var exprs = []; + + if (state.tokens.next.id !== ")") { + for (;;) { + if (pn1.value === "=>" && state.tokens.next.value === "{") { + bracket = state.tokens.next; + bracket.left = destructuringExpression(); + brackets.push(bracket); + for (var t in bracket.left) { + exprs.push(bracket.left[t].token); + } + } else { + exprs.push(expression(5)); + } + if (state.tokens.next.id !== ",") { + break; + } + comma(); + } + } + + advance(")", this); + nospace(state.tokens.prev, state.tokens.curr); + if (state.option.immed && exprs[0] && exprs[0].id === "function") { + if (state.tokens.next.id !== "(" && + (state.tokens.next.id !== "." || (peek().value !== "call" && peek().value !== "apply"))) { + warning("W068", this); + } + } + + if (state.tokens.next.value === "=>") { + return exprs; + } + if (!exprs.length) { + return; + } + exprs[exprs.length - 1].paren = true; + if (exprs.length > 1) { + return Object.create(state.syntax[","], { exprs: { value: exprs } }); + } + return exprs[0]; + }); + + application("=>"); + + infix("[", function (left, that) { + nobreak(state.tokens.prev, state.tokens.curr); + nospace(); + var e = expression(5), s; + if (e && e.type === "(string)") { + if (!state.option.evil && (e.value === "eval" || e.value === "execScript")) { + warning("W061", that); + } + + countMember(e.value); + if (!state.option.sub && reg.identifier.test(e.value)) { + s = state.syntax[e.value]; + if (!s || !isReserved(s)) { + warning("W069", state.tokens.prev, e.value); + } + } + } + advance("]", that); + + if (e && e.value === "hasOwnProperty" && state.tokens.next.value === "=") { + warning("W001"); + } + + nospace(state.tokens.prev, state.tokens.curr); + that.left = left; + that.right = e; + return that; + }, 160, true); + + function comprehensiveArrayExpression() { + var res = {}; + res.exps = true; + funct["(comparray)"].stack(); + + res.right = expression(5); + advance("for"); + if (state.tokens.next.value === "each") { + advance("each"); + if (!state.option.inMoz(true)) { + warning("W118", state.tokens.curr, "for each"); + } + } + advance("("); + funct["(comparray)"].setState("define"); + res.left = expression(5); + advance(")"); + if (state.tokens.next.value === "if") { + advance("if"); + advance("("); + funct["(comparray)"].setState("filter"); + res.filter = expression(5); + advance(")"); + } + advance("]"); + funct["(comparray)"].unstack(); + return res; + } + + prefix("[", function () { + var blocktype = lookupBlockType(true); + if (blocktype.isCompArray) { + if (!state.option.inMoz(true)) { + warning("W118", state.tokens.curr, "array comprehension"); + } + return comprehensiveArrayExpression(); + } else if (blocktype.isDestAssign && !state.option.inESNext()) { + warning("W104", state.tokens.curr, "destructuring assignment"); + } + var b = state.tokens.curr.line !== state.tokens.next.line; + this.first = []; + if (b) { + indent += state.option.indent; + if (state.tokens.next.from === indent + state.option.indent) { + indent += state.option.indent; + } + } + while (state.tokens.next.id !== "(end)") { + while (state.tokens.next.id === ",") { + if (!state.option.inES5()) + warning("W070"); + advance(","); + } + if (state.tokens.next.id === "]") { + break; + } + if (b && state.tokens.curr.line !== state.tokens.next.line) { + indentation(); + } + this.first.push(expression(10)); + if (state.tokens.next.id === ",") { + comma({ allowTrailing: true }); + if (state.tokens.next.id === "]" && !state.option.inES5(true)) { + warning("W070", state.tokens.curr); + break; + } + } else { + break; + } + } + if (b) { + indent -= state.option.indent; + indentation(); + } + advance("]", this); + return this; + }, 160); + + + function property_name() { + var id = optionalidentifier(false, true); + + if (!id) { + if (state.tokens.next.id === "(string)") { + id = state.tokens.next.value; + advance(); + } else if (state.tokens.next.id === "(number)") { + id = state.tokens.next.value.toString(); + advance(); + } + } + + if (id === "hasOwnProperty") { + warning("W001"); + } + + return id; + } + + + function functionparams(parsed) { + var curr, next; + var params = []; + var ident; + var tokens = []; + var t; + + if (parsed) { + if (parsed instanceof Array) { + for (var i in parsed) { + curr = parsed[i]; + if (_.contains(["{", "["], curr.id)) { + for (t in curr.left) { + t = tokens[t]; + if (t.id) { + params.push(t.id); + addlabel(t.id, "unused", t.token); + } + } + } else if (curr.value === "...") { + if (!state.option.inESNext()) { + warning("W104", curr, "spread/rest operator"); + } + continue; + } else { + addlabel(curr.value, "unused", curr); + } + } + return params; + } else { + if (parsed.identifier === true) { + addlabel(parsed.value, "unused", parsed); + return [parsed]; + } + } + } + + next = state.tokens.next; + + advance("("); + nospace(); + + if (state.tokens.next.id === ")") { + advance(")"); + return; + } + + for (;;) { + if (_.contains(["{", "["], state.tokens.next.id)) { + tokens = destructuringExpression(); + for (t in tokens) { + t = tokens[t]; + if (t.id) { + params.push(t.id); + addlabel(t.id, "unused", t.token); + } + } + } else if (state.tokens.next.value === "...") { + if (!state.option.inESNext()) { + warning("W104", state.tokens.next, "spread/rest operator"); + } + advance("..."); + nospace(); + ident = identifier(true); + params.push(ident); + addlabel(ident, "unused", state.tokens.curr); + } else { + ident = identifier(true); + params.push(ident); + addlabel(ident, "unused", state.tokens.curr); + } + if (state.tokens.next.id === ",") { + comma(); + } else { + advance(")", next); + nospace(state.tokens.prev, state.tokens.curr); + return params; + } + } + } + + + function doFunction(name, statement, generator, fatarrowparams) { + var f; + var oldOption = state.option; + var oldIgnored = state.ignored; + var oldScope = scope; + + state.option = Object.create(state.option); + state.ignored = Object.create(state.ignored); + scope = Object.create(scope); + + funct = { + "(name)" : name || "\"" + anonname + "\"", + "(line)" : state.tokens.next.line, + "(character)" : state.tokens.next.character, + "(context)" : funct, + "(breakage)" : 0, + "(loopage)" : 0, + "(metrics)" : createMetrics(state.tokens.next), + "(scope)" : scope, + "(statement)" : statement, + "(tokens)" : {}, + "(blockscope)": funct["(blockscope)"], + "(comparray)" : funct["(comparray)"] + }; + + if (generator) { + funct["(generator)"] = true; + } + + f = funct; + state.tokens.curr.funct = funct; + + functions.push(funct); + + if (name) { + addlabel(name, "function"); + } + + funct["(params)"] = functionparams(fatarrowparams); + + funct["(metrics)"].verifyMaxParametersPerFunction(funct["(params)"]); + + block(false, true, true, fatarrowparams ? true:false); + + if (generator && funct["(generator)"] !== "yielded") { + error("E047", state.tokens.curr); + } + + funct["(metrics)"].verifyMaxStatementsPerFunction(); + funct["(metrics)"].verifyMaxComplexityPerFunction(); + funct["(unusedOption)"] = state.option.unused; + + scope = oldScope; + state.option = oldOption; + state.ignored = oldIgnored; + funct["(last)"] = state.tokens.curr.line; + funct["(lastcharacter)"] = state.tokens.curr.character; + funct = funct["(context)"]; + + return f; + } + + function createMetrics(functionStartToken) { + return { + statementCount: 0, + nestedBlockDepth: -1, + ComplexityCount: 1, + verifyMaxStatementsPerFunction: function () { + if (state.option.maxstatements && + this.statementCount > state.option.maxstatements) { + warning("W071", functionStartToken, this.statementCount); + } + }, + + verifyMaxParametersPerFunction: function (params) { + params = params || []; + + if (state.option.maxparams && params.length > state.option.maxparams) { + warning("W072", functionStartToken, params.length); + } + }, + + verifyMaxNestedBlockDepthPerFunction: function () { + if (state.option.maxdepth && + this.nestedBlockDepth > 0 && + this.nestedBlockDepth === state.option.maxdepth + 1) { + warning("W073", null, this.nestedBlockDepth); + } + }, + + verifyMaxComplexityPerFunction: function () { + var max = state.option.maxcomplexity; + var cc = this.ComplexityCount; + if (max && cc > max) { + warning("W074", functionStartToken, cc); + } + } + }; + } + + function increaseComplexityCount() { + funct["(metrics)"].ComplexityCount += 1; + } + + // Parse assignments that were found instead of conditionals. + // For example: if (a = 1) { ... } + + function checkCondAssignment(expr) { + var id = expr.id; + if (id === ",") { + expr = expr.exprs[expr.exprs.length - 1]; + id = expr.id; + } + switch (id) { + case "=": + case "+=": + case "-=": + case "*=": + case "%=": + case "&=": + case "|=": + case "^=": + case "/=": + if (!expr.paren && !state.option.boss) { + warning("W084"); + } + } + } + + + (function (x) { + x.nud = function (isclassdef) { + var b, f, i, p, t, g; + var props = {}; // All properties, including accessors + var tag = ""; + + function saveProperty(name, tkn) { + if (props[name] && _.has(props, name)) + warning("W075", state.tokens.next, i); + else + props[name] = {}; + + props[name].basic = true; + props[name].basictkn = tkn; + } + + function saveSetter(name, tkn) { + if (props[name] && _.has(props, name)) { + if (props[name].basic || props[name].setter) + warning("W075", state.tokens.next, i); + } else { + props[name] = {}; + } + + props[name].setter = true; + props[name].setterToken = tkn; + } + + function saveGetter(name) { + if (props[name] && _.has(props, name)) { + if (props[name].basic || props[name].getter) + warning("W075", state.tokens.next, i); + } else { + props[name] = {}; + } + + props[name].getter = true; + props[name].getterToken = state.tokens.curr; + } + + b = state.tokens.curr.line !== state.tokens.next.line; + if (b) { + indent += state.option.indent; + if (state.tokens.next.from === indent + state.option.indent) { + indent += state.option.indent; + } + } + + for (;;) { + if (state.tokens.next.id === "}") { + break; + } + + if (b) { + indentation(); + } + + if (isclassdef && state.tokens.next.value === "static") { + advance("static"); + tag = "static "; + } + + if (state.tokens.next.value === "get" && peek().id !== ":") { + advance("get"); + + if (!state.option.inES5(!isclassdef)) { + error("E034"); + } + + i = property_name(); + if (!i) { + error("E035"); + } + + // It is a Syntax Error if PropName of MethodDefinition is + // "constructor" and SpecialMethod of MethodDefinition is true. + if (isclassdef && i === "constructor") { + error("E049", state.tokens.next, "class getter method", i); + } + + saveGetter(tag + i); + t = state.tokens.next; + adjacent(state.tokens.curr, state.tokens.next); + f = doFunction(); + p = f["(params)"]; + + if (p) { + warning("W076", t, p[0], i); + } + + adjacent(state.tokens.curr, state.tokens.next); + } else if (state.tokens.next.value === "set" && peek().id !== ":") { + advance("set"); + + if (!state.option.inES5(!isclassdef)) { + error("E034"); + } + + i = property_name(); + if (!i) { + error("E035"); + } + + // It is a Syntax Error if PropName of MethodDefinition is + // "constructor" and SpecialMethod of MethodDefinition is true. + if (isclassdef && i === "constructor") { + error("E049", state.tokens.next, "class setter method", i); + } + + saveSetter(tag + i, state.tokens.next); + t = state.tokens.next; + adjacent(state.tokens.curr, state.tokens.next); + f = doFunction(); + p = f["(params)"]; + + if (!p || p.length !== 1) { + warning("W077", t, i); + } + } else { + g = false; + if (state.tokens.next.value === "*" && state.tokens.next.type === "(punctuator)") { + if (!state.option.inESNext()) { + warning("W104", state.tokens.next, "generator functions"); + } + advance("*"); + g = true; + } + i = property_name(); + saveProperty(tag + i, state.tokens.next); + + if (typeof i !== "string") { + break; + } + + if (state.tokens.next.value === "(") { + if (!state.option.inESNext()) { + warning("W104", state.tokens.curr, "concise methods"); + } + doFunction(i, undefined, g); + } else if (!isclassdef) { + advance(":"); + nonadjacent(state.tokens.curr, state.tokens.next); + expression(10); + } + } + // It is a Syntax Error if PropName of MethodDefinition is "prototype". + if (isclassdef && i === "prototype") { + error("E049", state.tokens.next, "class method", i); + } + + countMember(i); + if (isclassdef) { + tag = ""; + continue; + } + if (state.tokens.next.id === ",") { + comma({ allowTrailing: true, property: true }); + if (state.tokens.next.id === ",") { + warning("W070", state.tokens.curr); + } else if (state.tokens.next.id === "}" && !state.option.inES5(true)) { + warning("W070", state.tokens.curr); + } + } else { + break; + } + } + if (b) { + indent -= state.option.indent; + indentation(); + } + advance("}", this); + + // Check for lonely setters if in the ES5 mode. + if (state.option.inES5()) { + for (var name in props) { + if (_.has(props, name) && props[name].setter && !props[name].getter) { + warning("W078", props[name].setterToken); + } + } + } + return this; + }; + x.fud = function () { + error("E036", state.tokens.curr); + }; + }(delim("{"))); + + function destructuringExpression() { + var id, ids; + var identifiers = []; + if (!state.option.inESNext()) { + warning("W104", state.tokens.curr, "destructuring expression"); + } + var nextInnerDE = function () { + var ident; + if (_.contains(["[", "{"], state.tokens.next.value)) { + ids = destructuringExpression(); + for (var id in ids) { + id = ids[id]; + identifiers.push({ id: id.id, token: id.token }); + } + } else if (state.tokens.next.value === ",") { + identifiers.push({ id: null, token: state.tokens.curr }); + } else { + ident = identifier(); + if (ident) + identifiers.push({ id: ident, token: state.tokens.curr }); + } + }; + if (state.tokens.next.value === "[") { + advance("["); + nextInnerDE(); + while (state.tokens.next.value !== "]") { + advance(","); + nextInnerDE(); + } + advance("]"); + } else if (state.tokens.next.value === "{") { + advance("{"); + id = identifier(); + if (state.tokens.next.value === ":") { + advance(":"); + nextInnerDE(); + } else { + identifiers.push({ id: id, token: state.tokens.curr }); + } + while (state.tokens.next.value !== "}") { + advance(","); + id = identifier(); + if (state.tokens.next.value === ":") { + advance(":"); + nextInnerDE(); + } else { + identifiers.push({ id: id, token: state.tokens.curr }); + } + } + advance("}"); + } + return identifiers; + } + function destructuringExpressionMatch(tokens, value) { + if (value.first) { + _.zip(tokens, value.first).forEach(function (val) { + var token = val[0]; + var value = val[1]; + if (token && value) { + token.first = value; + } else if (token && token.first && !value) { + warning("W080", token.first, token.first.value); + } /* else { + XXX value is discarded: wouldn't it need a warning ? + } */ + }); + } + } + + var conststatement = stmt("const", function (prefix) { + var tokens, value; + // state variable to know if it is a lone identifier, or a destructuring statement. + var lone; + + if (!state.option.inESNext()) { + warning("W104", state.tokens.curr, "const"); + } + + this.first = []; + for (;;) { + var names = []; + nonadjacent(state.tokens.curr, state.tokens.next); + if (_.contains(["{", "["], state.tokens.next.value)) { + tokens = destructuringExpression(); + lone = false; + } else { + tokens = [ { id: identifier(), token: state.tokens.curr } ]; + lone = true; + } + for (var t in tokens) { + t = tokens[t]; + if (funct[t.id] === "const") { + warning("E011", null, t.id); + } + if (funct["(global)"] && predefined[t.id] === false) { + warning("W079", t.token, t.id); + } + if (t.id) { + addlabel(t.id, "const"); + names.push(t.token); + } + } + if (prefix) { + break; + } + + this.first = this.first.concat(names); + + if (state.tokens.next.id !== "=") { + warning("E012", state.tokens.curr, state.tokens.curr.value); + } + + if (state.tokens.next.id === "=") { + nonadjacent(state.tokens.curr, state.tokens.next); + advance("="); + nonadjacent(state.tokens.curr, state.tokens.next); + if (state.tokens.next.id === "undefined") { + warning("W080", state.tokens.prev, state.tokens.prev.value); + } + if (peek(0).id === "=" && state.tokens.next.identifier) { + error("E037", state.tokens.next, state.tokens.next.value); + } + value = expression(5); + if (lone) { + tokens[0].first = value; + } else { + destructuringExpressionMatch(names, value); + } + } + + if (state.tokens.next.id !== ",") { + break; + } + comma(); + } + return this; + }); + conststatement.exps = true; + var varstatement = stmt("var", function (prefix) { + // JavaScript does not have block scope. It only has function scope. So, + // declaring a variable in a block can have unexpected consequences. + var tokens, lone, value; + + if (funct["(onevar)"] && state.option.onevar) { + warning("W081"); + } else if (!funct["(global)"]) { + funct["(onevar)"] = true; + } + + this.first = []; + for (;;) { + var names = []; + nonadjacent(state.tokens.curr, state.tokens.next); + if (_.contains(["{", "["], state.tokens.next.value)) { + tokens = destructuringExpression(); + lone = false; + } else { + tokens = [ { id: identifier(), token: state.tokens.curr } ]; + lone = true; + } + for (var t in tokens) { + t = tokens[t]; + if (state.option.inESNext() && funct[t.id] === "const") { + warning("E011", null, t.id); + } + if (funct["(global)"] && predefined[t.id] === false) { + warning("W079", t.token, t.id); + } + if (t.id) { + addlabel(t.id, "unused", t.token); + names.push(t.token); + } + } + if (prefix) { + break; + } + + this.first = this.first.concat(names); + + if (state.tokens.next.id === "=") { + nonadjacent(state.tokens.curr, state.tokens.next); + advance("="); + nonadjacent(state.tokens.curr, state.tokens.next); + if (state.tokens.next.id === "undefined") { + warning("W080", state.tokens.prev, state.tokens.prev.value); + } + if (peek(0).id === "=" && state.tokens.next.identifier) { + error("E038", state.tokens.next, state.tokens.next.value); + } + value = expression(5); + if (lone) { + tokens[0].first = value; + } else { + destructuringExpressionMatch(names, value); + } + } + + if (state.tokens.next.id !== ",") { + break; + } + comma(); + } + return this; + }); + varstatement.exps = true; + var letstatement = stmt("let", function (prefix) { + var tokens, lone, value, letblock; + + if (!state.option.inESNext()) { + warning("W104", state.tokens.curr, "let"); + } + + if (state.tokens.next.value === "(") { + if (!state.option.inMoz(true)) { + warning("W118", state.tokens.next, "let block"); + } + advance("("); + funct["(blockscope)"].stack(); + letblock = true; + } else if (funct["(nolet)"]) { + error("E048", state.tokens.curr); + } + + if (funct["(onevar)"] && state.option.onevar) { + warning("W081"); + } else if (!funct["(global)"]) { + funct["(onevar)"] = true; + } + + this.first = []; + for (;;) { + var names = []; + nonadjacent(state.tokens.curr, state.tokens.next); + if (_.contains(["{", "["], state.tokens.next.value)) { + tokens = destructuringExpression(); + lone = false; + } else { + tokens = [ { id: identifier(), token: state.tokens.curr.value } ]; + lone = true; + } + for (var t in tokens) { + t = tokens[t]; + if (state.option.inESNext() && funct[t.id] === "const") { + warning("E011", null, t.id); + } + if (funct["(global)"] && predefined[t.id] === false) { + warning("W079", t.token, t.id); + } + if (t.id && !funct["(nolet)"]) { + addlabel(t.id, "unused", t.token, true); + names.push(t.token); + } + } + if (prefix) { + break; + } + + this.first = this.first.concat(names); + + if (state.tokens.next.id === "=") { + nonadjacent(state.tokens.curr, state.tokens.next); + advance("="); + nonadjacent(state.tokens.curr, state.tokens.next); + if (state.tokens.next.id === "undefined") { + warning("W080", state.tokens.prev, state.tokens.prev.value); + } + if (peek(0).id === "=" && state.tokens.next.identifier) { + error("E037", state.tokens.next, state.tokens.next.value); + } + value = expression(5); + if (lone) { + tokens[0].first = value; + } else { + destructuringExpressionMatch(names, value); + } + } + + if (state.tokens.next.id !== ",") { + break; + } + comma(); + } + if (letblock) { + advance(")"); + block(true, true); + this.block = true; + funct["(blockscope)"].unstack(); + } + + return this; + }); + letstatement.exps = true; + + blockstmt("class", function () { + return classdef.call(this, true); + }); + + function classdef(stmt) { + /*jshint validthis:true */ + if (!state.option.inESNext()) { + warning("W104", state.tokens.curr, "class"); + } + if (stmt) { + // BindingIdentifier + this.name = identifier(); + addlabel(this.name, "unused", state.tokens.curr); + } else if (state.tokens.next.identifier && state.tokens.next.value !== "extends") { + // BindingIdentifier(opt) + this.name = identifier(); + } + classtail(this); + return this; + } + + function classtail(c) { + var strictness = state.directive["use strict"]; + + // ClassHeritage(opt) + if (state.tokens.next.value === "extends") { + advance("extends"); + c.heritage = expression(10); + } + + // A ClassBody is always strict code. + state.directive["use strict"] = true; + advance("{"); + // ClassBody(opt) + c.body = state.syntax["{"].nud(true); + state.directive["use strict"] = strictness; + } + + blockstmt("function", function () { + var generator = false; + if (state.tokens.next.value === "*") { + advance("*"); + if (state.option.inESNext(true)) { + generator = true; + } else { + warning("W119", state.tokens.curr, "function*"); + } + } + if (inblock) { + warning("W082", state.tokens.curr); + + } + var i = identifier(); + if (funct[i] === "const") { + warning("E011", null, i); + } + adjacent(state.tokens.curr, state.tokens.next); + addlabel(i, "unction", state.tokens.curr); + + doFunction(i, { statement: true }, generator); + if (state.tokens.next.id === "(" && state.tokens.next.line === state.tokens.curr.line) { + error("E039"); + } + return this; + }); + + prefix("function", function () { + var generator = false; + if (state.tokens.next.value === "*") { + if (!state.option.inESNext()) { + warning("W119", state.tokens.curr, "function*"); + } + advance("*"); + generator = true; + } + var i = optionalidentifier(); + if (i || state.option.gcl) { + adjacent(state.tokens.curr, state.tokens.next); + } else { + nonadjacent(state.tokens.curr, state.tokens.next); + } + doFunction(i, undefined, generator); + if (!state.option.loopfunc && funct["(loopage)"]) { + warning("W083"); + } + return this; + }); + + blockstmt("if", function () { + var t = state.tokens.next; + increaseComplexityCount(); + state.condition = true; + advance("("); + nonadjacent(this, t); + nospace(); + checkCondAssignment(expression(0)); + advance(")", t); + state.condition = false; + nospace(state.tokens.prev, state.tokens.curr); + block(true, true); + if (state.tokens.next.id === "else") { + nonadjacent(state.tokens.curr, state.tokens.next); + advance("else"); + if (state.tokens.next.id === "if" || state.tokens.next.id === "switch") { + statement(true); + } else { + block(true, true); + } + } + return this; + }); + + blockstmt("try", function () { + var b; + + function doCatch() { + var oldScope = scope; + var e; + + advance("catch"); + nonadjacent(state.tokens.curr, state.tokens.next); + advance("("); + + scope = Object.create(oldScope); + + e = state.tokens.next.value; + if (state.tokens.next.type !== "(identifier)") { + e = null; + warning("E030", state.tokens.next, e); + } + + advance(); + + funct = { + "(name)" : "(catch)", + "(line)" : state.tokens.next.line, + "(character)": state.tokens.next.character, + "(context)" : funct, + "(breakage)" : funct["(breakage)"], + "(loopage)" : funct["(loopage)"], + "(scope)" : scope, + "(statement)": false, + "(metrics)" : createMetrics(state.tokens.next), + "(catch)" : true, + "(tokens)" : {}, + "(blockscope)": funct["(blockscope)"], + "(comparray)": funct["(comparray)"] + }; + + if (e) { + addlabel(e, "exception"); + } + + if (state.tokens.next.value === "if") { + if (!state.option.inMoz(true)) { + warning("W118", state.tokens.curr, "catch filter"); + } + advance("if"); + expression(0); + } + + advance(")"); + + state.tokens.curr.funct = funct; + functions.push(funct); + + block(false); + + scope = oldScope; + + funct["(last)"] = state.tokens.curr.line; + funct["(lastcharacter)"] = state.tokens.curr.character; + funct = funct["(context)"]; + } + + block(false); + + while (state.tokens.next.id === "catch") { + increaseComplexityCount(); + if (b && (!state.option.inMoz(true))) { + warning("W118", state.tokens.next, "multiple catch blocks"); + } + doCatch(); + b = true; + } + + if (state.tokens.next.id === "finally") { + advance("finally"); + block(false); + return; + } + + if (!b) { + error("E021", state.tokens.next, "catch", state.tokens.next.value); + } + + return this; + }); + + blockstmt("while", function () { + var t = state.tokens.next; + funct["(breakage)"] += 1; + funct["(loopage)"] += 1; + increaseComplexityCount(); + advance("("); + nonadjacent(this, t); + nospace(); + checkCondAssignment(expression(0)); + advance(")", t); + nospace(state.tokens.prev, state.tokens.curr); + block(true, true); + funct["(breakage)"] -= 1; + funct["(loopage)"] -= 1; + return this; + }).labelled = true; + + blockstmt("with", function () { + var t = state.tokens.next; + if (state.directive["use strict"]) { + error("E010", state.tokens.curr); + } else if (!state.option.withstmt) { + warning("W085", state.tokens.curr); + } + + advance("("); + nonadjacent(this, t); + nospace(); + expression(0); + advance(")", t); + nospace(state.tokens.prev, state.tokens.curr); + block(true, true); + + return this; + }); + + blockstmt("switch", function () { + var t = state.tokens.next, + g = false; + funct["(breakage)"] += 1; + advance("("); + nonadjacent(this, t); + nospace(); + checkCondAssignment(expression(0)); + advance(")", t); + nospace(state.tokens.prev, state.tokens.curr); + nonadjacent(state.tokens.curr, state.tokens.next); + t = state.tokens.next; + advance("{"); + nonadjacent(state.tokens.curr, state.tokens.next); + indent += state.option.indent; + this.cases = []; + + for (;;) { + switch (state.tokens.next.id) { + case "case": + switch (funct["(verb)"]) { + case "yield": + case "break": + case "case": + case "continue": + case "return": + case "switch": + case "throw": + break; + default: + // You can tell JSHint that you don't use break intentionally by + // adding a comment /* falls through */ on a line just before + // the next `case`. + if (!reg.fallsThrough.test(state.lines[state.tokens.next.line - 2])) { + warning("W086", state.tokens.curr, "case"); + } + } + indentation(-state.option.indent); + advance("case"); + this.cases.push(expression(20)); + increaseComplexityCount(); + g = true; + advance(":"); + funct["(verb)"] = "case"; + break; + case "default": + switch (funct["(verb)"]) { + case "yield": + case "break": + case "continue": + case "return": + case "throw": + break; + default: + // Do not display a warning if 'default' is the first statement or if + // there is a special /* falls through */ comment. + if (this.cases.length) { + if (!reg.fallsThrough.test(state.lines[state.tokens.next.line - 2])) { + warning("W086", state.tokens.curr, "default"); + } + } + } + indentation(-state.option.indent); + advance("default"); + g = true; + advance(":"); + break; + case "}": + indent -= state.option.indent; + indentation(); + advance("}", t); + funct["(breakage)"] -= 1; + funct["(verb)"] = undefined; + return; + case "(end)": + error("E023", state.tokens.next, "}"); + return; + default: + if (g) { + switch (state.tokens.curr.id) { + case ",": + error("E040"); + return; + case ":": + g = false; + statements(); + break; + default: + error("E025", state.tokens.curr); + return; + } + } else { + if (state.tokens.curr.id === ":") { + advance(":"); + error("E024", state.tokens.curr, ":"); + statements(); + } else { + error("E021", state.tokens.next, "case", state.tokens.next.value); + return; + } + } + } + } + }).labelled = true; + + stmt("debugger", function () { + if (!state.option.debug) { + warning("W087"); + } + return this; + }).exps = true; + + (function () { + var x = stmt("do", function () { + funct["(breakage)"] += 1; + funct["(loopage)"] += 1; + increaseComplexityCount(); + + this.first = block(true, true); + advance("while"); + var t = state.tokens.next; + nonadjacent(state.tokens.curr, t); + advance("("); + nospace(); + checkCondAssignment(expression(0)); + advance(")", t); + nospace(state.tokens.prev, state.tokens.curr); + funct["(breakage)"] -= 1; + funct["(loopage)"] -= 1; + return this; + }); + x.labelled = true; + x.exps = true; + }()); + + blockstmt("for", function () { + var s, t = state.tokens.next; + var letscope = false; + var foreachtok = null; + + if (t.value === "each") { + foreachtok = t; + advance("each"); + if (!state.option.inMoz(true)) { + warning("W118", state.tokens.curr, "for each"); + } + } + + funct["(breakage)"] += 1; + funct["(loopage)"] += 1; + increaseComplexityCount(); + advance("("); + nonadjacent(this, t); + nospace(); + + // what kind of for(…) statement it is? for(…of…)? for(…in…)? for(…;…;…)? + var nextop; // contains the token of the "in" or "of" operator + var i = 0; + var inof = ["in", "of"]; + do { + nextop = peek(i); + ++i; + } while (!_.contains(inof, nextop.value) && nextop.value !== ";" && + nextop.type !== "(end)"); + + // if we're in a for (… in|of …) statement + if (_.contains(inof, nextop.value)) { + if (!state.option.inESNext() && nextop.value === "of") { + error("W104", nextop, "for of"); + } + if (state.tokens.next.id === "var") { + advance("var"); + state.syntax["var"].fud.call(state.syntax["var"].fud, true); + } else if (state.tokens.next.id === "let") { + advance("let"); + // create a new block scope + letscope = true; + funct["(blockscope)"].stack(); + state.syntax["let"].fud.call(state.syntax["let"].fud, true); + } else { + switch (funct[state.tokens.next.value]) { + case "unused": + funct[state.tokens.next.value] = "var"; + break; + case "var": + break; + default: + if (!funct["(blockscope)"].getlabel(state.tokens.next.value)) + warning("W088", state.tokens.next, state.tokens.next.value); + } + advance(); + } + advance(nextop.value); + expression(20); + advance(")", t); + s = block(true, true); + if (state.option.forin && s && (s.length > 1 || typeof s[0] !== "object" || + s[0].value !== "if")) { + warning("W089", this); + } + funct["(breakage)"] -= 1; + funct["(loopage)"] -= 1; + } else { + if (foreachtok) { + error("E045", foreachtok); + } + if (state.tokens.next.id !== ";") { + if (state.tokens.next.id === "var") { + advance("var"); + state.syntax["var"].fud.call(state.syntax["var"].fud); + } else if (state.tokens.next.id === "let") { + advance("let"); + // create a new block scope + letscope = true; + funct["(blockscope)"].stack(); + state.syntax["let"].fud.call(state.syntax["let"].fud); + } else { + for (;;) { + expression(0, "for"); + if (state.tokens.next.id !== ",") { + break; + } + comma(); + } + } + } + nolinebreak(state.tokens.curr); + advance(";"); + if (state.tokens.next.id !== ";") { + checkCondAssignment(expression(0)); + } + nolinebreak(state.tokens.curr); + advance(";"); + if (state.tokens.next.id === ";") { + error("E021", state.tokens.next, ")", ";"); + } + if (state.tokens.next.id !== ")") { + for (;;) { + expression(0, "for"); + if (state.tokens.next.id !== ",") { + break; + } + comma(); + } + } + advance(")", t); + nospace(state.tokens.prev, state.tokens.curr); + block(true, true); + funct["(breakage)"] -= 1; + funct["(loopage)"] -= 1; + + } + // unstack loop blockscope + if (letscope) { + funct["(blockscope)"].unstack(); + } + return this; + }).labelled = true; + + + stmt("break", function () { + var v = state.tokens.next.value; + + if (funct["(breakage)"] === 0) + warning("W052", state.tokens.next, this.value); + + if (!state.option.asi) + nolinebreak(this); + + if (state.tokens.next.id !== ";") { + if (state.tokens.curr.line === state.tokens.next.line) { + if (funct[v] !== "label") { + warning("W090", state.tokens.next, v); + } else if (scope[v] !== funct) { + warning("W091", state.tokens.next, v); + } + this.first = state.tokens.next; + advance(); + } + } + reachable("break"); + return this; + }).exps = true; + + + stmt("continue", function () { + var v = state.tokens.next.value; + + if (funct["(breakage)"] === 0) + warning("W052", state.tokens.next, this.value); + + if (!state.option.asi) + nolinebreak(this); + + if (state.tokens.next.id !== ";") { + if (state.tokens.curr.line === state.tokens.next.line) { + if (funct[v] !== "label") { + warning("W090", state.tokens.next, v); + } else if (scope[v] !== funct) { + warning("W091", state.tokens.next, v); + } + this.first = state.tokens.next; + advance(); + } + } else if (!funct["(loopage)"]) { + warning("W052", state.tokens.next, this.value); + } + reachable("continue"); + return this; + }).exps = true; + + + stmt("return", function () { + if (this.line === state.tokens.next.line) { + if (state.tokens.next.id === "(regexp)") + warning("W092"); + + if (state.tokens.next.id !== ";" && !state.tokens.next.reach) { + nonadjacent(state.tokens.curr, state.tokens.next); + this.first = expression(0); + + if (this.first && + this.first.type === "(punctuator)" && this.first.value === "=" && !state.option.boss) { + warningAt("W093", this.first.line, this.first.character); + } + } + } else { + if (state.tokens.next.type === "(punctuator)" && + ["[", "{", "+", "-"].indexOf(state.tokens.next.value) > -1) { + nolinebreak(this); // always warn (Line breaking error) + } + } + reachable("return"); + return this; + }).exps = true; + + stmt("yield", function () { + if (state.option.inESNext(true) && funct["(generator)"] !== true) { + error("E046", state.tokens.curr, "yield"); + } else if (!state.option.inESNext()) { + warning("W104", state.tokens.curr, "yield"); + } + funct["(generator)"] = "yielded"; + if (this.line === state.tokens.next.line) { + if (state.tokens.next.id === "(regexp)") + warning("W092"); + + if (state.tokens.next.id !== ";" && !state.tokens.next.reach) { + nonadjacent(state.tokens.curr, state.tokens.next); + this.first = expression(0); + + if (this.first.type === "(punctuator)" && this.first.value === "=" && !state.option.boss) { + warningAt("W093", this.first.line, this.first.character); + } + } + } else if (!state.option.asi) { + nolinebreak(this); // always warn (Line breaking error) + } + return this; + }).exps = true; + + + stmt("throw", function () { + nolinebreak(this); + nonadjacent(state.tokens.curr, state.tokens.next); + this.first = expression(20); + reachable("throw"); + return this; + }).exps = true; + + // Future Reserved Words + + FutureReservedWord("abstract"); + FutureReservedWord("boolean"); + FutureReservedWord("byte"); + FutureReservedWord("char"); + FutureReservedWord("class", { es5: true, nud: classdef }); + FutureReservedWord("double"); + FutureReservedWord("enum", { es5: true }); + FutureReservedWord("export", { es5: true }); + FutureReservedWord("extends", { es5: true }); + FutureReservedWord("final"); + FutureReservedWord("float"); + FutureReservedWord("goto"); + FutureReservedWord("implements", { es5: true, strictOnly: true }); + FutureReservedWord("import", { es5: true }); + FutureReservedWord("int"); + FutureReservedWord("interface", { es5: true, strictOnly: true }); + FutureReservedWord("long"); + FutureReservedWord("native"); + FutureReservedWord("package", { es5: true, strictOnly: true }); + FutureReservedWord("private", { es5: true, strictOnly: true }); + FutureReservedWord("protected", { es5: true, strictOnly: true }); + FutureReservedWord("public", { es5: true, strictOnly: true }); + FutureReservedWord("short"); + FutureReservedWord("static", { es5: true, strictOnly: true }); + FutureReservedWord("super", { es5: true }); + FutureReservedWord("synchronized"); + FutureReservedWord("throws"); + FutureReservedWord("transient"); + FutureReservedWord("volatile"); + + // this function is used to determine wether a squarebracket or a curlybracket + // expression is a comprehension array, destructuring assignment or a json value. + + var lookupBlockType = function () { + var pn, pn1; + var i = 0; + var bracketStack = 0; + var ret = {}; + if (_.contains(["[", "{"], state.tokens.curr.value)) + bracketStack += 1; + if (_.contains(["[", "{"], state.tokens.next.value)) + bracketStack += 1; + if (_.contains(["]", "}"], state.tokens.next.value)) + bracketStack -= 1; + do { + pn = peek(i); + pn1 = peek(i + 1); + i = i + 1; + if (_.contains(["[", "{"], pn.value)) { + bracketStack += 1; + } else if (_.contains(["]", "}"], pn.value)) { + bracketStack -= 1; + } + if (pn.identifier && pn.value === "for" && bracketStack === 1) { + ret.isCompArray = true; + ret.notJson = true; + break; + } + if (_.contains(["}", "]"], pn.value) && pn1.value === "=") { + ret.isDestAssign = true; + ret.notJson = true; + break; + } + if (pn.value === ";") { + ret.isBlock = true; + ret.notJson = true; + } + } while (bracketStack > 0 && pn.id !== "(end)" && i < 15); + return ret; + }; + + // Check whether this function has been reached for a destructuring assign with undeclared values + function destructuringAssignOrJsonValue() { + // lookup for the assignment (esnext only) + // if it has semicolons, it is a block, so go parse it as a block + // or it's not a block, but there are assignments, check for undeclared variables + + var block = lookupBlockType(); + if (block.notJson) { + if (!state.option.inESNext() && block.isDestAssign) { + warning("W104", state.tokens.curr, "destructuring assignment"); + } + statements(); + // otherwise parse json value + } else { + state.option.laxbreak = true; + state.jsonMode = true; + jsonValue(); + } + } + + // array comprehension parsing function + // parses and defines the three states of the list comprehension in order + // to avoid defining global variables, but keeping them to the list comprehension scope + // only. The order of the states are as follows: + // * "use" which will be the returned iterative part of the list comprehension + // * "define" which will define the variables local to the list comprehension + // * "filter" which will help filter out values + + var arrayComprehension = function () { + var CompArray = function () { + this.mode = "use"; + this.variables = []; + }; + var _carrays = []; + var _current; + function declare(v) { + var l = _current.variables.filter(function (elt) { + // if it has, change its undef state + if (elt.value === v) { + elt.undef = false; + return v; + } + }).length; + return l !== 0; + } + function use(v) { + var l = _current.variables.filter(function (elt) { + // and if it has been defined + if (elt.value === v && !elt.undef) { + if (elt.unused === true) { + elt.unused = false; + } + return v; + } + }).length; + // otherwise we warn about it + return (l === 0); + } + return {stack: function () { + _current = new CompArray(); + _carrays.push(_current); + }, + unstack: function () { + _current.variables.filter(function (v) { + if (v.unused) + warning("W098", v.token, v.value); + if (v.undef) + isundef(v.funct, "W117", v.token, v.value); + }); + _carrays.splice(_carrays[_carrays.length - 1], 1); + _current = _carrays[_carrays.length - 1]; + }, + setState: function (s) { + if (_.contains(["use", "define", "filter"], s)) + _current.mode = s; + }, + check: function (v) { + // When we are in "use" state of the list comp, we enqueue that var + if (_current && _current.mode === "use") { + _current.variables.push({funct: funct, + token: state.tokens.curr, + value: v, + undef: true, + unused: false}); + return true; + // When we are in "define" state of the list comp, + } else if (_current && _current.mode === "define") { + // check if the variable has been used previously + if (!declare(v)) { + _current.variables.push({funct: funct, + token: state.tokens.curr, + value: v, + undef: false, + unused: true}); + } + return true; + // When we are in "filter" state, + } else if (_current && _current.mode === "filter") { + // we check whether current variable has been declared + if (use(v)) { + // if not we warn about it + isundef(funct, "W117", state.tokens.curr, v); + } + return true; + } + return false; + } + }; + }; + + + // Parse JSON + + function jsonValue() { + + function jsonObject() { + var o = {}, t = state.tokens.next; + advance("{"); + if (state.tokens.next.id !== "}") { + for (;;) { + if (state.tokens.next.id === "(end)") { + error("E026", state.tokens.next, t.line); + } else if (state.tokens.next.id === "}") { + warning("W094", state.tokens.curr); + break; + } else if (state.tokens.next.id === ",") { + error("E028", state.tokens.next); + } else if (state.tokens.next.id !== "(string)") { + warning("W095", state.tokens.next, state.tokens.next.value); + } + if (o[state.tokens.next.value] === true) { + warning("W075", state.tokens.next, state.tokens.next.value); + } else if ((state.tokens.next.value === "__proto__" && + !state.option.proto) || (state.tokens.next.value === "__iterator__" && + !state.option.iterator)) { + warning("W096", state.tokens.next, state.tokens.next.value); + } else { + o[state.tokens.next.value] = true; + } + advance(); + advance(":"); + jsonValue(); + if (state.tokens.next.id !== ",") { + break; + } + advance(","); + } + } + advance("}"); + } + + function jsonArray() { + var t = state.tokens.next; + advance("["); + if (state.tokens.next.id !== "]") { + for (;;) { + if (state.tokens.next.id === "(end)") { + error("E027", state.tokens.next, t.line); + } else if (state.tokens.next.id === "]") { + warning("W094", state.tokens.curr); + break; + } else if (state.tokens.next.id === ",") { + error("E028", state.tokens.next); + } + jsonValue(); + if (state.tokens.next.id !== ",") { + break; + } + advance(","); + } + } + advance("]"); + } + + switch (state.tokens.next.id) { + case "{": + jsonObject(); + break; + case "[": + jsonArray(); + break; + case "true": + case "false": + case "null": + case "(number)": + case "(string)": + advance(); + break; + case "-": + advance("-"); + if (state.tokens.curr.character !== state.tokens.next.from) { + warning("W011", state.tokens.curr); + } + adjacent(state.tokens.curr, state.tokens.next); + advance("(number)"); + break; + default: + error("E003", state.tokens.next); + } + } + + var blockScope = function () { + var _current = {}; + var _variables = [_current]; + + function _checkBlockLabels() { + for (var t in _current) { + if (_current[t]["(type)"] === "unused") { + if (state.option.unused) { + var tkn = _current[t]["(token)"]; + var line = tkn.line; + var chr = tkn.character; + warningAt("W098", line, chr, t); + } + } + } + } + + return { + stack: function () { + _current = {}; + _variables.push(_current); + }, + + unstack: function () { + _checkBlockLabels(); + _variables.splice(_variables.length - 1, 1); + _current = _.last(_variables); + }, + + getlabel: function (l) { + for (var i = _variables.length - 1 ; i >= 0; --i) { + if (_.has(_variables[i], l)) { + return _variables[i]; + } + } + }, + + current: { + has: function (t) { + return _.has(_current, t); + }, + add: function (t, type, tok) { + _current[t] = { "(type)" : type, + "(token)": tok }; + } + } + }; + }; + + // The actual JSHINT function itself. + var itself = function (s, o, g) { + var a, i, k, x; + var optionKeys; + var newOptionObj = {}; + var newIgnoredObj = {}; + + state.reset(); + + if (o && o.scope) { + JSHINT.scope = o.scope; + } else { + JSHINT.errors = []; + JSHINT.undefs = []; + JSHINT.internals = []; + JSHINT.blacklist = {}; + JSHINT.scope = "(main)"; + } + + predefined = Object.create(null); + combine(predefined, vars.ecmaIdentifiers); + combine(predefined, vars.reservedVars); + + combine(predefined, g || {}); + + declared = Object.create(null); + exported = Object.create(null); + + if (o) { + a = o.predef; + if (a) { + if (!Array.isArray(a) && typeof a === "object") { + a = Object.keys(a); + } + + a.forEach(function (item) { + var slice, prop; + + if (item[0] === "-") { + slice = item.slice(1); + JSHINT.blacklist[slice] = slice; + } else { + prop = Object.getOwnPropertyDescriptor(o.predef, item); + predefined[item] = prop ? prop.value : false; + } + }); + } + + optionKeys = Object.keys(o); + for (x = 0; x < optionKeys.length; x++) { + if (/^-W\d{3}$/g.test(optionKeys[x])) { + newIgnoredObj[optionKeys[x].slice(1)] = true; + } else { + newOptionObj[optionKeys[x]] = o[optionKeys[x]]; + + if (optionKeys[x] === "newcap" && o[optionKeys[x]] === false) + newOptionObj["(explicitNewcap)"] = true; + + if (optionKeys[x] === "indent") + newOptionObj["(explicitIndent)"] = o[optionKeys[x]] === false ? false : true; + } + } + } + + state.option = newOptionObj; + state.ignored = newIgnoredObj; + + state.option.indent = state.option.indent || 4; + state.option.maxerr = state.option.maxerr || 50; + + indent = 1; + global = Object.create(predefined); + scope = global; + funct = { + "(global)": true, + "(name)": "(global)", + "(scope)": scope, + "(breakage)": 0, + "(loopage)": 0, + "(tokens)": {}, + "(metrics)": createMetrics(state.tokens.next), + "(blockscope)": blockScope(), + "(comparray)": arrayComprehension() + }; + functions = [funct]; + urls = []; + stack = null; + member = {}; + membersOnly = null; + implied = {}; + inblock = false; + lookahead = []; + warnings = 0; + unuseds = []; + + if (!isString(s) && !Array.isArray(s)) { + errorAt("E004", 0); + return false; + } + + api = { + get isJSON() { + return state.jsonMode; + }, + + getOption: function (name) { + return state.option[name] || null; + }, + + getCache: function (name) { + return state.cache[name]; + }, + + setCache: function (name, value) { + state.cache[name] = value; + }, + + warn: function (code, data) { + warningAt.apply(null, [ code, data.line, data.char ].concat(data.data)); + }, + + on: function (names, listener) { + names.split(" ").forEach(function (name) { + emitter.on(name, listener); + }.bind(this)); + } + }; + + emitter.removeAllListeners(); + (extraModules || []).forEach(function (func) { + func(api); + }); + + state.tokens.prev = state.tokens.curr = state.tokens.next = state.syntax["(begin)"]; + + lex = new Lexer(s); + + lex.on("warning", function (ev) { + warningAt.apply(null, [ ev.code, ev.line, ev.character].concat(ev.data)); + }); + + lex.on("error", function (ev) { + errorAt.apply(null, [ ev.code, ev.line, ev.character ].concat(ev.data)); + }); + + lex.on("fatal", function (ev) { + quit("E041", ev.line, ev.from); + }); + + lex.on("Identifier", function (ev) { + emitter.emit("Identifier", ev); + }); + + lex.on("String", function (ev) { + emitter.emit("String", ev); + }); + + lex.on("Number", function (ev) { + emitter.emit("Number", ev); + }); + + lex.start(); + + // Check options + for (var name in o) { + if (_.has(o, name)) { + checkOption(name, state.tokens.curr); + } + } + + assume(); + + // combine the passed globals after we've assumed all our options + combine(predefined, g || {}); + + //reset values + comma.first = true; + + try { + advance(); + switch (state.tokens.next.id) { + case "{": + case "[": + destructuringAssignOrJsonValue(); + break; + default: + directives(); + + if (state.directive["use strict"]) { + if (!state.option.globalstrict && !state.option.node) { + warning("W097", state.tokens.prev); + } + } + + statements(); + } + advance((state.tokens.next && state.tokens.next.value !== ".") ? "(end)" : undefined); + funct["(blockscope)"].unstack(); + + var markDefined = function (name, context) { + do { + if (typeof context[name] === "string") { + // JSHINT marks unused variables as 'unused' and + // unused function declaration as 'unction'. This + // code changes such instances back 'var' and + // 'closure' so that the code in JSHINT.data() + // doesn't think they're unused. + + if (context[name] === "unused") + context[name] = "var"; + else if (context[name] === "unction") + context[name] = "closure"; + + return true; + } + + context = context["(context)"]; + } while (context); + + return false; + }; + + var clearImplied = function (name, line) { + if (!implied[name]) + return; + + var newImplied = []; + for (var i = 0; i < implied[name].length; i += 1) { + if (implied[name][i] !== line) + newImplied.push(implied[name][i]); + } + + if (newImplied.length === 0) + delete implied[name]; + else + implied[name] = newImplied; + }; + + var warnUnused = function (name, tkn, type, unused_opt) { + var line = tkn.line; + var chr = tkn.character; + + if (unused_opt === undefined) { + unused_opt = state.option.unused; + } + + if (unused_opt === true) { + unused_opt = "last-param"; + } + + var warnable_types = { + "vars": ["var"], + "last-param": ["var", "param"], + "strict": ["var", "param", "last-param"] + }; + + if (unused_opt) { + if (warnable_types[unused_opt] && warnable_types[unused_opt].indexOf(type) !== -1) { + warningAt("W098", line, chr, name); + } + } + + unuseds.push({ + name: name, + line: line, + character: chr + }); + }; + + var checkUnused = function (func, key) { + var type = func[key]; + var tkn = func["(tokens)"][key]; + + if (key.charAt(0) === "(") + return; + + if (type !== "unused" && type !== "unction") + return; + + // Params are checked separately from other variables. + if (func["(params)"] && func["(params)"].indexOf(key) !== -1) + return; + + // Variable is in global scope and defined as exported. + if (func["(global)"] && _.has(exported, key)) { + return; + } + + warnUnused(key, tkn, "var"); + }; + + // Check queued 'x is not defined' instances to see if they're still undefined. + for (i = 0; i < JSHINT.undefs.length; i += 1) { + k = JSHINT.undefs[i].slice(0); + + if (markDefined(k[2].value, k[0])) { + clearImplied(k[2].value, k[2].line); + } else if (state.option.undef) { + warning.apply(warning, k.slice(1)); + } + } + + functions.forEach(function (func) { + if (func["(unusedOption)"] === false) { + return; + } + + for (var key in func) { + if (_.has(func, key)) { + checkUnused(func, key); + } + } + + if (!func["(params)"]) + return; + + var params = func["(params)"].slice(); + var param = params.pop(); + var type, unused_opt; + + while (param) { + type = func[param]; + unused_opt = func["(unusedOption)"] || state.option.unused; + unused_opt = unused_opt === true ? "last-param" : unused_opt; + + // 'undefined' is a special case for (function (window, undefined) { ... })(); + // patterns. + + if (param === "undefined") + return; + + if (type === "unused" || type === "unction") { + warnUnused(param, func["(tokens)"][param], "param", func["(unusedOption)"]); + } else if (unused_opt === "last-param") { + return; + } + + param = params.pop(); + } + }); + + for (var key in declared) { + if (_.has(declared, key) && !_.has(global, key)) { + warnUnused(key, declared[key], "var"); + } + } + + } catch (err) { + if (err && err.name === "JSHintError") { + var nt = state.tokens.next || {}; + JSHINT.errors.push({ + scope : "(main)", + raw : err.raw, + reason : err.message, + line : err.line || nt.line, + character : err.character || nt.from + }, null); + } else { + throw err; + } + } + + // Loop over the listed "internals", and check them as well. + + if (JSHINT.scope === "(main)") { + o = o || {}; + + for (i = 0; i < JSHINT.internals.length; i += 1) { + k = JSHINT.internals[i]; + o.scope = k.elem; + itself(k.value, o, g); + } + } + + return JSHINT.errors.length === 0; + }; + + // Modules. + itself.addModule = function (func) { + extraModules.push(func); + }; + + itself.addModule(style.register); + + // Data summary. + itself.data = function () { + var data = { + functions: [], + options: state.option + }; + var implieds = []; + var members = []; + var fu, f, i, j, n, globals; + + if (itself.errors.length) { + data.errors = itself.errors; + } + + if (state.jsonMode) { + data.json = true; + } + + for (n in implied) { + if (_.has(implied, n)) { + implieds.push({ + name: n, + line: implied[n] + }); + } + } + + if (implieds.length > 0) { + data.implieds = implieds; + } + + if (urls.length > 0) { + data.urls = urls; + } + + globals = Object.keys(scope); + if (globals.length > 0) { + data.globals = globals; + } + + for (i = 1; i < functions.length; i += 1) { + f = functions[i]; + fu = {}; + + for (j = 0; j < functionicity.length; j += 1) { + fu[functionicity[j]] = []; + } + + for (j = 0; j < functionicity.length; j += 1) { + if (fu[functionicity[j]].length === 0) { + delete fu[functionicity[j]]; + } + } + + fu.name = f["(name)"]; + fu.param = f["(params)"]; + fu.line = f["(line)"]; + fu.character = f["(character)"]; + fu.last = f["(last)"]; + fu.lastcharacter = f["(lastcharacter)"]; + data.functions.push(fu); + } + + if (unuseds.length > 0) { + data.unused = unuseds; + } + + members = []; + for (n in member) { + if (typeof member[n] === "number") { + data.member = member; + break; + } + } + + return data; + }; + + itself.jshint = itself; + + return itself; +}()); + +// Make JSHINT a Node module, if possible. +if (typeof exports === "object" && exports) { + exports.JSHINT = JSHINT; +} + +})() +},{"events":2,"../shared/vars.js":3,"./lex.js":10,"./reg.js":6,"./state.js":4,"../shared/messages.js":12,"./style.js":5,"console-browserify":7,"underscore":11}],12:[function(require,module,exports){ +(function(){"use strict"; + +var _ = require("underscore"); + +var errors = { + // JSHint options + E001: "Bad option: '{a}'.", + E002: "Bad option value.", + + // JSHint input + E003: "Expected a JSON value.", + E004: "Input is neither a string nor an array of strings.", + E005: "Input is empty.", + E006: "Unexpected early end of program.", + + // Strict mode + E007: "Missing \"use strict\" statement.", + E008: "Strict violation.", + E009: "Option 'validthis' can't be used in a global scope.", + E010: "'with' is not allowed in strict mode.", + + // Constants + E011: "const '{a}' has already been declared.", + E012: "const '{a}' is initialized to 'undefined'.", + E013: "Attempting to override '{a}' which is a constant.", + + // Regular expressions + E014: "A regular expression literal can be confused with '/='.", + E015: "Unclosed regular expression.", + E016: "Invalid regular expression.", + + // Tokens + E017: "Unclosed comment.", + E018: "Unbegun comment.", + E019: "Unmatched '{a}'.", + E020: "Expected '{a}' to match '{b}' from line {c} and instead saw '{d}'.", + E021: "Expected '{a}' and instead saw '{b}'.", + E022: "Line breaking error '{a}'.", + E023: "Missing '{a}'.", + E024: "Unexpected '{a}'.", + E025: "Missing ':' on a case clause.", + E026: "Missing '}' to match '{' from line {a}.", + E027: "Missing ']' to match '[' form line {a}.", + E028: "Illegal comma.", + E029: "Unclosed string.", + + // Everything else + E030: "Expected an identifier and instead saw '{a}'.", + E031: "Bad assignment.", // FIXME: Rephrase + E032: "Expected a small integer or 'false' and instead saw '{a}'.", + E033: "Expected an operator and instead saw '{a}'.", + E034: "get/set are ES5 features.", + E035: "Missing property name.", + E036: "Expected to see a statement and instead saw a block.", + E037: "Constant {a} was not declared correctly.", + E038: "Variable {a} was not declared correctly.", + E039: "Function declarations are not invocable. Wrap the whole function invocation in parens.", + E040: "Each value should have its own case label.", + E041: "Unrecoverable syntax error.", + E042: "Stopping.", + E043: "Too many errors.", + E044: "'{a}' is already defined and can't be redefined.", + E045: "Invalid for each loop.", + E046: "A yield statement shall be within a generator function (with syntax: `function*`)", + E047: "A generator function shall contain a yield statement.", + E048: "Let declaration not directly within block.", + E049: "A {a} cannot be named '{b}'." +}; + +var warnings = { + W001: "'hasOwnProperty' is a really bad name.", + W002: "Value of '{a}' may be overwritten in IE.", + W003: "'{a}' was used before it was defined.", + W004: "'{a}' is already defined.", + W005: "A dot following a number can be confused with a decimal point.", + W006: "Confusing minuses.", + W007: "Confusing pluses.", + W008: "A leading decimal point can be confused with a dot: '{a}'.", + W009: "The array literal notation [] is preferrable.", + W010: "The object literal notation {} is preferrable.", + W011: "Unexpected space after '{a}'.", + W012: "Unexpected space before '{a}'.", + W013: "Missing space after '{a}'.", + W014: "Bad line breaking before '{a}'.", + W015: "Expected '{a}' to have an indentation at {b} instead at {c}.", + W016: "Unexpected use of '{a}'.", + W017: "Bad operand.", + W018: "Confusing use of '{a}'.", + W019: "Use the isNaN function to compare with NaN.", + W020: "Read only.", + W021: "'{a}' is a function.", + W022: "Do not assign to the exception parameter.", + W023: "Expected an identifier in an assignment and instead saw a function invocation.", + W024: "Expected an identifier and instead saw '{a}' (a reserved word).", + W025: "Missing name in function declaration.", + W026: "Inner functions should be listed at the top of the outer function.", + W027: "Unreachable '{a}' after '{b}'.", + W028: "Label '{a}' on {b} statement.", + W030: "Expected an assignment or function call and instead saw an expression.", + W031: "Do not use 'new' for side effects.", + W032: "Unnecessary semicolon.", + W033: "Missing semicolon.", + W034: "Unnecessary directive \"{a}\".", + W035: "Empty block.", + W036: "Unexpected /*member '{a}'.", + W037: "'{a}' is a statement label.", + W038: "'{a}' used out of scope.", + W039: "'{a}' is not allowed.", + W040: "Possible strict violation.", + W041: "Use '{a}' to compare with '{b}'.", + W042: "Avoid EOL escaping.", + W043: "Bad escaping of EOL. Use option multistr if needed.", + W044: "Bad or unnecessary escaping.", + W045: "Bad number '{a}'.", + W046: "Don't use extra leading zeros '{a}'.", + W047: "A trailing decimal point can be confused with a dot: '{a}'.", + W048: "Unexpected control character in regular expression.", + W049: "Unexpected escaped character '{a}' in regular expression.", + W050: "JavaScript URL.", + W051: "Variables should not be deleted.", + W052: "Unexpected '{a}'.", + W053: "Do not use {a} as a constructor.", + W054: "The Function constructor is a form of eval.", + W055: "A constructor name should start with an uppercase letter.", + W056: "Bad constructor.", + W057: "Weird construction. Is 'new' unnecessary?", + W058: "Missing '()' invoking a constructor.", + W059: "Avoid arguments.{a}.", + W060: "document.write can be a form of eval.", + W061: "eval can be harmful.", + W062: "Wrap an immediate function invocation in parens " + + "to assist the reader in understanding that the expression " + + "is the result of a function, and not the function itself.", + W063: "Math is not a function.", + W064: "Missing 'new' prefix when invoking a constructor.", + W065: "Missing radix parameter.", + W066: "Implied eval. Consider passing a function instead of a string.", + W067: "Bad invocation.", + W068: "Wrapping non-IIFE function literals in parens is unnecessary.", + W069: "['{a}'] is better written in dot notation.", + W070: "Extra comma. (it breaks older versions of IE)", + W071: "This function has too many statements. ({a})", + W072: "This function has too many parameters. ({a})", + W073: "Blocks are nested too deeply. ({a})", + W074: "This function's cyclomatic complexity is too high. ({a})", + W075: "Duplicate key '{a}'.", + W076: "Unexpected parameter '{a}' in get {b} function.", + W077: "Expected a single parameter in set {a} function.", + W078: "Setter is defined without getter.", + W079: "Redefinition of '{a}'.", + W080: "It's not necessary to initialize '{a}' to 'undefined'.", + W081: "Too many var statements.", + W082: "Function declarations should not be placed in blocks. " + + "Use a function expression or move the statement to the top of " + + "the outer function.", + W083: "Don't make functions within a loop.", + W084: "Expected a conditional expression and instead saw an assignment.", + W085: "Don't use 'with'.", + W086: "Expected a 'break' statement before '{a}'.", + W087: "Forgotten 'debugger' statement?", + W088: "Creating global 'for' variable. Should be 'for (var {a} ...'.", + W089: "The body of a for in should be wrapped in an if statement to filter " + + "unwanted properties from the prototype.", + W090: "'{a}' is not a statement label.", + W091: "'{a}' is out of scope.", + W092: "Wrap the /regexp/ literal in parens to disambiguate the slash operator.", + W093: "Did you mean to return a conditional instead of an assignment?", + W094: "Unexpected comma.", + W095: "Expected a string and instead saw {a}.", + W096: "The '{a}' key may produce unexpected results.", + W097: "Use the function form of \"use strict\".", + W098: "'{a}' is defined but never used.", + W099: "Mixed spaces and tabs.", + W100: "This character may get silently deleted by one or more browsers.", + W101: "Line is too long.", + W102: "Trailing whitespace.", + W103: "The '{a}' property is deprecated.", + W104: "'{a}' is only available in JavaScript 1.7.", + W105: "Unexpected {a} in '{b}'.", + W106: "Identifier '{a}' is not in camel case.", + W107: "Script URL.", + W108: "Strings must use doublequote.", + W109: "Strings must use singlequote.", + W110: "Mixed double and single quotes.", + W112: "Unclosed string.", + W113: "Control character in string: {a}.", + W114: "Avoid {a}.", + W115: "Octal literals are not allowed in strict mode.", + W116: "Expected '{a}' and instead saw '{b}'.", + W117: "'{a}' is not defined.", + W118: "'{a}' is only available in Mozilla JavaScript extensions (use moz option).", + W119: "'{a}' is only available in ES6 (use esnext option)." +}; + +var info = { + I001: "Comma warnings can be turned off with 'laxcomma'.", + I002: "Reserved words as properties can be used under the 'es5' option.", + I003: "ES5 option is now set per default" +}; + +exports.errors = {}; +exports.warnings = {}; +exports.info = {}; + +_.each(errors, function (desc, code) { + exports.errors[code] = { code: code, desc: desc }; +}); + +_.each(warnings, function (desc, code) { + exports.warnings[code] = { code: code, desc: desc }; +}); + +_.each(info, function (desc, code) { + exports.info[code] = { code: code, desc: desc }; +}); + +})() +},{"underscore":11}],8:[function(require,module,exports){ +var events = require('events'); + +exports.isArray = isArray; +exports.isDate = function(obj){return Object.prototype.toString.call(obj) === '[object Date]'}; +exports.isRegExp = function(obj){return Object.prototype.toString.call(obj) === '[object RegExp]'}; + + +exports.print = function () {}; +exports.puts = function () {}; +exports.debug = function() {}; + +exports.inspect = function(obj, showHidden, depth, colors) { + var seen = []; + + var stylize = function(str, styleType) { + // http://en.wikipedia.org/wiki/ANSI_escape_code#graphics + var styles = + { 'bold' : [1, 22], + 'italic' : [3, 23], + 'underline' : [4, 24], + 'inverse' : [7, 27], + 'white' : [37, 39], + 'grey' : [90, 39], + 'black' : [30, 39], + 'blue' : [34, 39], + 'cyan' : [36, 39], + 'green' : [32, 39], + 'magenta' : [35, 39], + 'red' : [31, 39], + 'yellow' : [33, 39] }; + + var style = + { 'special': 'cyan', + 'number': 'blue', + 'boolean': 'yellow', + 'undefined': 'grey', + 'null': 'bold', + 'string': 'green', + 'date': 'magenta', + // "name": intentionally not styling + 'regexp': 'red' }[styleType]; + + if (style) { + return '\033[' + styles[style][0] + 'm' + str + + '\033[' + styles[style][1] + 'm'; + } else { + return str; + } + }; + if (! colors) { + stylize = function(str, styleType) { return str; }; + } + + function format(value, recurseTimes) { + // Provide a hook for user-specified inspect functions. + // Check that value is an object with an inspect function on it + if (value && typeof value.inspect === 'function' && + // Filter out the util module, it's inspect function is special + value !== exports && + // Also filter out any prototype objects using the circular check. + !(value.constructor && value.constructor.prototype === value)) { + return value.inspect(recurseTimes); + } + + // Primitive types cannot have properties + switch (typeof value) { + case 'undefined': + return stylize('undefined', 'undefined'); + + case 'string': + var simple = '\'' + JSON.stringify(value).replace(/^"|"$/g, '') + .replace(/'/g, "\\'") + .replace(/\\"/g, '"') + '\''; + return stylize(simple, 'string'); + + case 'number': + return stylize('' + value, 'number'); + + case 'boolean': + return stylize('' + value, 'boolean'); + } + // For some reason typeof null is "object", so special case here. + if (value === null) { + return stylize('null', 'null'); + } + + // Look up the keys of the object. + var visible_keys = Object_keys(value); + var keys = showHidden ? Object_getOwnPropertyNames(value) : visible_keys; + + // Functions without properties can be shortcutted. + if (typeof value === 'function' && keys.length === 0) { + if (isRegExp(value)) { + return stylize('' + value, 'regexp'); + } else { + var name = value.name ? ': ' + value.name : ''; + return stylize('[Function' + name + ']', 'special'); + } + } + + // Dates without properties can be shortcutted + if (isDate(value) && keys.length === 0) { + return stylize(value.toUTCString(), 'date'); + } + + var base, type, braces; + // Determine the object type + if (isArray(value)) { + type = 'Array'; + braces = ['[', ']']; + } else { + type = 'Object'; + braces = ['{', '}']; + } + + // Make functions say that they are functions + if (typeof value === 'function') { + var n = value.name ? ': ' + value.name : ''; + base = (isRegExp(value)) ? ' ' + value : ' [Function' + n + ']'; + } else { + base = ''; + } + + // Make dates with properties first say the date + if (isDate(value)) { + base = ' ' + value.toUTCString(); + } + + if (keys.length === 0) { + return braces[0] + base + braces[1]; + } + + if (recurseTimes < 0) { + if (isRegExp(value)) { + return stylize('' + value, 'regexp'); + } else { + return stylize('[Object]', 'special'); + } + } + + seen.push(value); + + var output = keys.map(function(key) { + var name, str; + if (value.__lookupGetter__) { + if (value.__lookupGetter__(key)) { + if (value.__lookupSetter__(key)) { + str = stylize('[Getter/Setter]', 'special'); + } else { + str = stylize('[Getter]', 'special'); + } + } else { + if (value.__lookupSetter__(key)) { + str = stylize('[Setter]', 'special'); + } + } + } + if (visible_keys.indexOf(key) < 0) { + name = '[' + key + ']'; + } + if (!str) { + if (seen.indexOf(value[key]) < 0) { + if (recurseTimes === null) { + str = format(value[key]); + } else { + str = format(value[key], recurseTimes - 1); + } + if (str.indexOf('\n') > -1) { + if (isArray(value)) { + str = str.split('\n').map(function(line) { + return ' ' + line; + }).join('\n').substr(2); + } else { + str = '\n' + str.split('\n').map(function(line) { + return ' ' + line; + }).join('\n'); + } + } + } else { + str = stylize('[Circular]', 'special'); + } + } + if (typeof name === 'undefined') { + if (type === 'Array' && key.match(/^\d+$/)) { + return str; + } + name = JSON.stringify('' + key); + if (name.match(/^"([a-zA-Z_][a-zA-Z_0-9]*)"$/)) { + name = name.substr(1, name.length - 2); + name = stylize(name, 'name'); + } else { + name = name.replace(/'/g, "\\'") + .replace(/\\"/g, '"') + .replace(/(^"|"$)/g, "'"); + name = stylize(name, 'string'); + } + } + + return name + ': ' + str; + }); + + seen.pop(); + + var numLinesEst = 0; + var length = output.reduce(function(prev, cur) { + numLinesEst++; + if (cur.indexOf('\n') >= 0) numLinesEst++; + return prev + cur.length + 1; + }, 0); + + if (length > 50) { + output = braces[0] + + (base === '' ? '' : base + '\n ') + + ' ' + + output.join(',\n ') + + ' ' + + braces[1]; + + } else { + output = braces[0] + base + ' ' + output.join(', ') + ' ' + braces[1]; + } + + return output; + } + return format(obj, (typeof depth === 'undefined' ? 2 : depth)); +}; + + +function isArray(ar) { + return ar instanceof Array || + Array.isArray(ar) || + (ar && ar !== Object.prototype && isArray(ar.__proto__)); +} + + +function isRegExp(re) { + return re instanceof RegExp || + (typeof re === 'object' && Object.prototype.toString.call(re) === '[object RegExp]'); +} + + +function isDate(d) { + if (d instanceof Date) return true; + if (typeof d !== 'object') return false; + var properties = Date.prototype && Object_getOwnPropertyNames(Date.prototype); + var proto = d.__proto__ && Object_getOwnPropertyNames(d.__proto__); + return JSON.stringify(proto) === JSON.stringify(properties); +} + +function pad(n) { + return n < 10 ? '0' + n.toString(10) : n.toString(10); +} + +var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', + 'Oct', 'Nov', 'Dec']; + +// 26 Feb 16:19:34 +function timestamp() { + var d = new Date(); + var time = [pad(d.getHours()), + pad(d.getMinutes()), + pad(d.getSeconds())].join(':'); + return [d.getDate(), months[d.getMonth()], time].join(' '); +} + +exports.log = function (msg) {}; + +exports.pump = null; + +var Object_keys = Object.keys || function (obj) { + var res = []; + for (var key in obj) res.push(key); + return res; +}; + +var Object_getOwnPropertyNames = Object.getOwnPropertyNames || function (obj) { + var res = []; + for (var key in obj) { + if (Object.hasOwnProperty.call(obj, key)) res.push(key); + } + return res; +}; + +var Object_create = Object.create || function (prototype, properties) { + // from es5-shim + var object; + if (prototype === null) { + object = { '__proto__' : null }; + } + else { + if (typeof prototype !== 'object') { + throw new TypeError( + 'typeof prototype[' + (typeof prototype) + '] != \'object\'' + ); + } + var Type = function () {}; + Type.prototype = prototype; + object = new Type(); + object.__proto__ = prototype; + } + if (typeof properties !== 'undefined' && Object.defineProperties) { + Object.defineProperties(object, properties); + } + return object; +}; + +exports.inherits = function(ctor, superCtor) { + ctor.super_ = superCtor; + ctor.prototype = Object_create(superCtor.prototype, { + constructor: { + value: ctor, + enumerable: false, + writable: true, + configurable: true + } + }); +}; + +var formatRegExp = /%[sdj%]/g; +exports.format = function(f) { + if (typeof f !== 'string') { + var objects = []; + for (var i = 0; i < arguments.length; i++) { + objects.push(exports.inspect(arguments[i])); + } + return objects.join(' '); + } + + var i = 1; + var args = arguments; + var len = args.length; + var str = String(f).replace(formatRegExp, function(x) { + if (x === '%%') return '%'; + if (i >= len) return x; + switch (x) { + case '%s': return String(args[i++]); + case '%d': return Number(args[i++]); + case '%j': return JSON.stringify(args[i++]); + default: + return x; + } + }); + for(var x = args[i]; i < len; x = args[++i]){ + if (x === null || typeof x !== 'object') { + str += ' ' + x; + } else { + str += ' ' + exports.inspect(x); + } + } + return str; +}; + +},{"events":2}],9:[function(require,module,exports){ +(function(){// UTILITY +var util = require('util'); +var Buffer = require("buffer").Buffer; +var pSlice = Array.prototype.slice; + +function objectKeys(object) { + if (Object.keys) return Object.keys(object); + var result = []; + for (var name in object) { + if (Object.prototype.hasOwnProperty.call(object, name)) { + result.push(name); + } + } + return result; +} + +// 1. The assert module provides functions that throw +// AssertionError's when particular conditions are not met. The +// assert module must conform to the following interface. + +var assert = module.exports = ok; + +// 2. The AssertionError is defined in assert. +// new assert.AssertionError({ message: message, +// actual: actual, +// expected: expected }) + +assert.AssertionError = function AssertionError(options) { + this.name = 'AssertionError'; + this.message = options.message; + this.actual = options.actual; + this.expected = options.expected; + this.operator = options.operator; + var stackStartFunction = options.stackStartFunction || fail; + + if (Error.captureStackTrace) { + Error.captureStackTrace(this, stackStartFunction); + } +}; +util.inherits(assert.AssertionError, Error); + +function replacer(key, value) { + if (value === undefined) { + return '' + value; + } + if (typeof value === 'number' && (isNaN(value) || !isFinite(value))) { + return value.toString(); + } + if (typeof value === 'function' || value instanceof RegExp) { + return value.toString(); + } + return value; +} + +function truncate(s, n) { + if (typeof s == 'string') { + return s.length < n ? s : s.slice(0, n); + } else { + return s; + } +} + +assert.AssertionError.prototype.toString = function() { + if (this.message) { + return [this.name + ':', this.message].join(' '); + } else { + return [ + this.name + ':', + truncate(JSON.stringify(this.actual, replacer), 128), + this.operator, + truncate(JSON.stringify(this.expected, replacer), 128) + ].join(' '); + } +}; + +// assert.AssertionError instanceof Error + +assert.AssertionError.__proto__ = Error.prototype; + +// At present only the three keys mentioned above are used and +// understood by the spec. Implementations or sub modules can pass +// other keys to the AssertionError's constructor - they will be +// ignored. + +// 3. All of the following functions must throw an AssertionError +// when a corresponding condition is not met, with a message that +// may be undefined if not provided. All assertion methods provide +// both the actual and expected values to the assertion error for +// display purposes. + +function fail(actual, expected, message, operator, stackStartFunction) { + throw new assert.AssertionError({ + message: message, + actual: actual, + expected: expected, + operator: operator, + stackStartFunction: stackStartFunction + }); +} + +// EXTENSION! allows for well behaved errors defined elsewhere. +assert.fail = fail; + +// 4. Pure assertion tests whether a value is truthy, as determined +// by !!guard. +// assert.ok(guard, message_opt); +// This statement is equivalent to assert.equal(true, guard, +// message_opt);. To test strictly for the value true, use +// assert.strictEqual(true, guard, message_opt);. + +function ok(value, message) { + if (!!!value) fail(value, true, message, '==', assert.ok); +} +assert.ok = ok; + +// 5. The equality assertion tests shallow, coercive equality with +// ==. +// assert.equal(actual, expected, message_opt); + +assert.equal = function equal(actual, expected, message) { + if (actual != expected) fail(actual, expected, message, '==', assert.equal); +}; + +// 6. The non-equality assertion tests for whether two objects are not equal +// with != assert.notEqual(actual, expected, message_opt); + +assert.notEqual = function notEqual(actual, expected, message) { + if (actual == expected) { + fail(actual, expected, message, '!=', assert.notEqual); + } +}; + +// 7. The equivalence assertion tests a deep equality relation. +// assert.deepEqual(actual, expected, message_opt); + +assert.deepEqual = function deepEqual(actual, expected, message) { + if (!_deepEqual(actual, expected)) { + fail(actual, expected, message, 'deepEqual', assert.deepEqual); + } +}; + +function _deepEqual(actual, expected) { + // 7.1. All identical values are equivalent, as determined by ===. + if (actual === expected) { + return true; + + } else if (Buffer.isBuffer(actual) && Buffer.isBuffer(expected)) { + if (actual.length != expected.length) return false; + + for (var i = 0; i < actual.length; i++) { + if (actual[i] !== expected[i]) return false; + } + + return true; + + // 7.2. If the expected value is a Date object, the actual value is + // equivalent if it is also a Date object that refers to the same time. + } else if (actual instanceof Date && expected instanceof Date) { + return actual.getTime() === expected.getTime(); + + // 7.3. Other pairs that do not both pass typeof value == 'object', + // equivalence is determined by ==. + } else if (typeof actual != 'object' && typeof expected != 'object') { + return actual == expected; + + // 7.4. For all other Object pairs, including Array objects, equivalence is + // determined by having the same number of owned properties (as verified + // with Object.prototype.hasOwnProperty.call), the same set of keys + // (although not necessarily the same order), equivalent values for every + // corresponding key, and an identical 'prototype' property. Note: this + // accounts for both named and indexed properties on Arrays. + } else { + return objEquiv(actual, expected); + } +} + +function isUndefinedOrNull(value) { + return value === null || value === undefined; +} + +function isArguments(object) { + return Object.prototype.toString.call(object) == '[object Arguments]'; +} + +function objEquiv(a, b) { + if (isUndefinedOrNull(a) || isUndefinedOrNull(b)) + return false; + // an identical 'prototype' property. + if (a.prototype !== b.prototype) return false; + //~~~I've managed to break Object.keys through screwy arguments passing. + // Converting to array solves the problem. + if (isArguments(a)) { + if (!isArguments(b)) { + return false; + } + a = pSlice.call(a); + b = pSlice.call(b); + return _deepEqual(a, b); + } + try { + var ka = objectKeys(a), + kb = objectKeys(b), + key, i; + } catch (e) {//happens when one is a string literal and the other isn't + return false; + } + // having the same number of owned properties (keys incorporates + // hasOwnProperty) + if (ka.length != kb.length) + return false; + //the same set of keys (although not necessarily the same order), + ka.sort(); + kb.sort(); + //~~~cheap key test + for (i = ka.length - 1; i >= 0; i--) { + if (ka[i] != kb[i]) + return false; + } + //equivalent values for every corresponding key, and + //~~~possibly expensive deep test + for (i = ka.length - 1; i >= 0; i--) { + key = ka[i]; + if (!_deepEqual(a[key], b[key])) return false; + } + return true; +} + +// 8. The non-equivalence assertion tests for any deep inequality. +// assert.notDeepEqual(actual, expected, message_opt); + +assert.notDeepEqual = function notDeepEqual(actual, expected, message) { + if (_deepEqual(actual, expected)) { + fail(actual, expected, message, 'notDeepEqual', assert.notDeepEqual); + } +}; + +// 9. The strict equality assertion tests strict equality, as determined by ===. +// assert.strictEqual(actual, expected, message_opt); + +assert.strictEqual = function strictEqual(actual, expected, message) { + if (actual !== expected) { + fail(actual, expected, message, '===', assert.strictEqual); + } +}; + +// 10. The strict non-equality assertion tests for strict inequality, as +// determined by !==. assert.notStrictEqual(actual, expected, message_opt); + +assert.notStrictEqual = function notStrictEqual(actual, expected, message) { + if (actual === expected) { + fail(actual, expected, message, '!==', assert.notStrictEqual); + } +}; + +function expectedException(actual, expected) { + if (!actual || !expected) { + return false; + } + + if (expected instanceof RegExp) { + return expected.test(actual); + } else if (actual instanceof expected) { + return true; + } else if (expected.call({}, actual) === true) { + return true; + } + + return false; +} + +function _throws(shouldThrow, block, expected, message) { + var actual; + + if (typeof expected === 'string') { + message = expected; + expected = null; + } + + try { + block(); + } catch (e) { + actual = e; + } + + message = (expected && expected.name ? ' (' + expected.name + ').' : '.') + + (message ? ' ' + message : '.'); + + if (shouldThrow && !actual) { + fail('Missing expected exception' + message); + } + + if (!shouldThrow && expectedException(actual, expected)) { + fail('Got unwanted exception' + message); + } + + if ((shouldThrow && actual && expected && + !expectedException(actual, expected)) || (!shouldThrow && actual)) { + throw actual; + } +} + +// 11. Expected to throw an error: +// assert.throws(block, Error_opt, message_opt); + +assert.throws = function(block, /*optional*/error, /*optional*/message) { + _throws.apply(this, [true].concat(pSlice.call(arguments))); +}; + +// EXTENSION! This is annoying to write outside this module. +assert.doesNotThrow = function(block, /*optional*/error, /*optional*/message) { + _throws.apply(this, [false].concat(pSlice.call(arguments))); +}; + +assert.ifError = function(err) { if (err) {throw err;}}; + +})() +},{"util":8,"buffer":13}],11:[function(require,module,exports){ +(function(){// Underscore.js 1.4.4 +// http://underscorejs.org +// (c) 2009-2013 Jeremy Ashkenas, DocumentCloud Inc. +// Underscore may be freely distributed under the MIT license. + +(function() { + + // Baseline setup + // -------------- + + // Establish the root object, `window` in the browser, or `global` on the server. + var root = this; + + // Save the previous value of the `_` variable. + var previousUnderscore = root._; + + // Establish the object that gets returned to break out of a loop iteration. + var breaker = {}; + + // Save bytes in the minified (but not gzipped) version: + var ArrayProto = Array.prototype, ObjProto = Object.prototype, FuncProto = Function.prototype; + + // Create quick reference variables for speed access to core prototypes. + var push = ArrayProto.push, + slice = ArrayProto.slice, + concat = ArrayProto.concat, + toString = ObjProto.toString, + hasOwnProperty = ObjProto.hasOwnProperty; + + // All **ECMAScript 5** native function implementations that we hope to use + // are declared here. + var + nativeForEach = ArrayProto.forEach, + nativeMap = ArrayProto.map, + nativeReduce = ArrayProto.reduce, + nativeReduceRight = ArrayProto.reduceRight, + nativeFilter = ArrayProto.filter, + nativeEvery = ArrayProto.every, + nativeSome = ArrayProto.some, + nativeIndexOf = ArrayProto.indexOf, + nativeLastIndexOf = ArrayProto.lastIndexOf, + nativeIsArray = Array.isArray, + nativeKeys = Object.keys, + nativeBind = FuncProto.bind; + + // Create a safe reference to the Underscore object for use below. + var _ = function(obj) { + if (obj instanceof _) return obj; + if (!(this instanceof _)) return new _(obj); + this._wrapped = obj; + }; + + // Export the Underscore object for **Node.js**, with + // backwards-compatibility for the old `require()` API. If we're in + // the browser, add `_` as a global object via a string identifier, + // for Closure Compiler "advanced" mode. + if (typeof exports !== 'undefined') { + if (typeof module !== 'undefined' && module.exports) { + exports = module.exports = _; + } + exports._ = _; + } else { + root._ = _; + } + + // Current version. + _.VERSION = '1.4.4'; + + // Collection Functions + // -------------------- + + // The cornerstone, an `each` implementation, aka `forEach`. + // Handles objects with the built-in `forEach`, arrays, and raw objects. + // Delegates to **ECMAScript 5**'s native `forEach` if available. + var each = _.each = _.forEach = function(obj, iterator, context) { + if (obj == null) return; + if (nativeForEach && obj.forEach === nativeForEach) { + obj.forEach(iterator, context); + } else if (obj.length === +obj.length) { + for (var i = 0, l = obj.length; i < l; i++) { + if (iterator.call(context, obj[i], i, obj) === breaker) return; + } + } else { + for (var key in obj) { + if (_.has(obj, key)) { + if (iterator.call(context, obj[key], key, obj) === breaker) return; + } + } + } + }; + + // Return the results of applying the iterator to each element. + // Delegates to **ECMAScript 5**'s native `map` if available. + _.map = _.collect = function(obj, iterator, context) { + var results = []; + if (obj == null) return results; + if (nativeMap && obj.map === nativeMap) return obj.map(iterator, context); + each(obj, function(value, index, list) { + results[results.length] = iterator.call(context, value, index, list); + }); + return results; + }; + + var reduceError = 'Reduce of empty array with no initial value'; + + // **Reduce** builds up a single result from a list of values, aka `inject`, + // or `foldl`. Delegates to **ECMAScript 5**'s native `reduce` if available. + _.reduce = _.foldl = _.inject = function(obj, iterator, memo, context) { + var initial = arguments.length > 2; + if (obj == null) obj = []; + if (nativeReduce && obj.reduce === nativeReduce) { + if (context) iterator = _.bind(iterator, context); + return initial ? obj.reduce(iterator, memo) : obj.reduce(iterator); + } + each(obj, function(value, index, list) { + if (!initial) { + memo = value; + initial = true; + } else { + memo = iterator.call(context, memo, value, index, list); + } + }); + if (!initial) throw new TypeError(reduceError); + return memo; + }; + + // The right-associative version of reduce, also known as `foldr`. + // Delegates to **ECMAScript 5**'s native `reduceRight` if available. + _.reduceRight = _.foldr = function(obj, iterator, memo, context) { + var initial = arguments.length > 2; + if (obj == null) obj = []; + if (nativeReduceRight && obj.reduceRight === nativeReduceRight) { + if (context) iterator = _.bind(iterator, context); + return initial ? obj.reduceRight(iterator, memo) : obj.reduceRight(iterator); + } + var length = obj.length; + if (length !== +length) { + var keys = _.keys(obj); + length = keys.length; + } + each(obj, function(value, index, list) { + index = keys ? keys[--length] : --length; + if (!initial) { + memo = obj[index]; + initial = true; + } else { + memo = iterator.call(context, memo, obj[index], index, list); + } + }); + if (!initial) throw new TypeError(reduceError); + return memo; + }; + + // Return the first value which passes a truth test. Aliased as `detect`. + _.find = _.detect = function(obj, iterator, context) { + var result; + any(obj, function(value, index, list) { + if (iterator.call(context, value, index, list)) { + result = value; + return true; + } + }); + return result; + }; + + // Return all the elements that pass a truth test. + // Delegates to **ECMAScript 5**'s native `filter` if available. + // Aliased as `select`. + _.filter = _.select = function(obj, iterator, context) { + var results = []; + if (obj == null) return results; + if (nativeFilter && obj.filter === nativeFilter) return obj.filter(iterator, context); + each(obj, function(value, index, list) { + if (iterator.call(context, value, index, list)) results[results.length] = value; + }); + return results; + }; + + // Return all the elements for which a truth test fails. + _.reject = function(obj, iterator, context) { + return _.filter(obj, function(value, index, list) { + return !iterator.call(context, value, index, list); + }, context); + }; + + // Determine whether all of the elements match a truth test. + // Delegates to **ECMAScript 5**'s native `every` if available. + // Aliased as `all`. + _.every = _.all = function(obj, iterator, context) { + iterator || (iterator = _.identity); + var result = true; + if (obj == null) return result; + if (nativeEvery && obj.every === nativeEvery) return obj.every(iterator, context); + each(obj, function(value, index, list) { + if (!(result = result && iterator.call(context, value, index, list))) return breaker; + }); + return !!result; + }; + + // Determine if at least one element in the object matches a truth test. + // Delegates to **ECMAScript 5**'s native `some` if available. + // Aliased as `any`. + var any = _.some = _.any = function(obj, iterator, context) { + iterator || (iterator = _.identity); + var result = false; + if (obj == null) return result; + if (nativeSome && obj.some === nativeSome) return obj.some(iterator, context); + each(obj, function(value, index, list) { + if (result || (result = iterator.call(context, value, index, list))) return breaker; + }); + return !!result; + }; + + // Determine if the array or object contains a given value (using `===`). + // Aliased as `include`. + _.contains = _.include = function(obj, target) { + if (obj == null) return false; + if (nativeIndexOf && obj.indexOf === nativeIndexOf) return obj.indexOf(target) != -1; + return any(obj, function(value) { + return value === target; + }); + }; + + // Invoke a method (with arguments) on every item in a collection. + _.invoke = function(obj, method) { + var args = slice.call(arguments, 2); + var isFunc = _.isFunction(method); + return _.map(obj, function(value) { + return (isFunc ? method : value[method]).apply(value, args); + }); + }; + + // Convenience version of a common use case of `map`: fetching a property. + _.pluck = function(obj, key) { + return _.map(obj, function(value){ return value[key]; }); + }; + + // Convenience version of a common use case of `filter`: selecting only objects + // containing specific `key:value` pairs. + _.where = function(obj, attrs, first) { + if (_.isEmpty(attrs)) return first ? null : []; + return _[first ? 'find' : 'filter'](obj, function(value) { + for (var key in attrs) { + if (attrs[key] !== value[key]) return false; + } + return true; + }); + }; + + // Convenience version of a common use case of `find`: getting the first object + // containing specific `key:value` pairs. + _.findWhere = function(obj, attrs) { + return _.where(obj, attrs, true); + }; + + // Return the maximum element or (element-based computation). + // Can't optimize arrays of integers longer than 65,535 elements. + // See: https://bugs.webkit.org/show_bug.cgi?id=80797 + _.max = function(obj, iterator, context) { + if (!iterator && _.isArray(obj) && obj[0] === +obj[0] && obj.length < 65535) { + return Math.max.apply(Math, obj); + } + if (!iterator && _.isEmpty(obj)) return -Infinity; + var result = {computed : -Infinity, value: -Infinity}; + each(obj, function(value, index, list) { + var computed = iterator ? iterator.call(context, value, index, list) : value; + computed >= result.computed && (result = {value : value, computed : computed}); + }); + return result.value; + }; + + // Return the minimum element (or element-based computation). + _.min = function(obj, iterator, context) { + if (!iterator && _.isArray(obj) && obj[0] === +obj[0] && obj.length < 65535) { + return Math.min.apply(Math, obj); + } + if (!iterator && _.isEmpty(obj)) return Infinity; + var result = {computed : Infinity, value: Infinity}; + each(obj, function(value, index, list) { + var computed = iterator ? iterator.call(context, value, index, list) : value; + computed < result.computed && (result = {value : value, computed : computed}); + }); + return result.value; + }; + + // Shuffle an array. + _.shuffle = function(obj) { + var rand; + var index = 0; + var shuffled = []; + each(obj, function(value) { + rand = _.random(index++); + shuffled[index - 1] = shuffled[rand]; + shuffled[rand] = value; + }); + return shuffled; + }; + + // An internal function to generate lookup iterators. + var lookupIterator = function(value) { + return _.isFunction(value) ? value : function(obj){ return obj[value]; }; + }; + + // Sort the object's values by a criterion produced by an iterator. + _.sortBy = function(obj, value, context) { + var iterator = lookupIterator(value); + return _.pluck(_.map(obj, function(value, index, list) { + return { + value : value, + index : index, + criteria : iterator.call(context, value, index, list) + }; + }).sort(function(left, right) { + var a = left.criteria; + var b = right.criteria; + if (a !== b) { + if (a > b || a === void 0) return 1; + if (a < b || b === void 0) return -1; + } + return left.index < right.index ? -1 : 1; + }), 'value'); + }; + + // An internal function used for aggregate "group by" operations. + var group = function(obj, value, context, behavior) { + var result = {}; + var iterator = lookupIterator(value || _.identity); + each(obj, function(value, index) { + var key = iterator.call(context, value, index, obj); + behavior(result, key, value); + }); + return result; + }; + + // Groups the object's values by a criterion. Pass either a string attribute + // to group by, or a function that returns the criterion. + _.groupBy = function(obj, value, context) { + return group(obj, value, context, function(result, key, value) { + (_.has(result, key) ? result[key] : (result[key] = [])).push(value); + }); + }; + + // Counts instances of an object that group by a certain criterion. Pass + // either a string attribute to count by, or a function that returns the + // criterion. + _.countBy = function(obj, value, context) { + return group(obj, value, context, function(result, key) { + if (!_.has(result, key)) result[key] = 0; + result[key]++; + }); + }; + + // Use a comparator function to figure out the smallest index at which + // an object should be inserted so as to maintain order. Uses binary search. + _.sortedIndex = function(array, obj, iterator, context) { + iterator = iterator == null ? _.identity : lookupIterator(iterator); + var value = iterator.call(context, obj); + var low = 0, high = array.length; + while (low < high) { + var mid = (low + high) >>> 1; + iterator.call(context, array[mid]) < value ? low = mid + 1 : high = mid; + } + return low; + }; + + // Safely convert anything iterable into a real, live array. + _.toArray = function(obj) { + if (!obj) return []; + if (_.isArray(obj)) return slice.call(obj); + if (obj.length === +obj.length) return _.map(obj, _.identity); + return _.values(obj); + }; + + // Return the number of elements in an object. + _.size = function(obj) { + if (obj == null) return 0; + return (obj.length === +obj.length) ? obj.length : _.keys(obj).length; + }; + + // Array Functions + // --------------- + + // Get the first element of an array. Passing **n** will return the first N + // values in the array. Aliased as `head` and `take`. The **guard** check + // allows it to work with `_.map`. + _.first = _.head = _.take = function(array, n, guard) { + if (array == null) return void 0; + return (n != null) && !guard ? slice.call(array, 0, n) : array[0]; + }; + + // Returns everything but the last entry of the array. Especially useful on + // the arguments object. Passing **n** will return all the values in + // the array, excluding the last N. The **guard** check allows it to work with + // `_.map`. + _.initial = function(array, n, guard) { + return slice.call(array, 0, array.length - ((n == null) || guard ? 1 : n)); + }; + + // Get the last element of an array. Passing **n** will return the last N + // values in the array. The **guard** check allows it to work with `_.map`. + _.last = function(array, n, guard) { + if (array == null) return void 0; + if ((n != null) && !guard) { + return slice.call(array, Math.max(array.length - n, 0)); + } else { + return array[array.length - 1]; + } + }; + + // Returns everything but the first entry of the array. Aliased as `tail` and `drop`. + // Especially useful on the arguments object. Passing an **n** will return + // the rest N values in the array. The **guard** + // check allows it to work with `_.map`. + _.rest = _.tail = _.drop = function(array, n, guard) { + return slice.call(array, (n == null) || guard ? 1 : n); + }; + + // Trim out all falsy values from an array. + _.compact = function(array) { + return _.filter(array, _.identity); + }; + + // Internal implementation of a recursive `flatten` function. + var flatten = function(input, shallow, output) { + each(input, function(value) { + if (_.isArray(value)) { + shallow ? push.apply(output, value) : flatten(value, shallow, output); + } else { + output.push(value); + } + }); + return output; + }; + + // Return a completely flattened version of an array. + _.flatten = function(array, shallow) { + return flatten(array, shallow, []); + }; + + // Return a version of the array that does not contain the specified value(s). + _.without = function(array) { + return _.difference(array, slice.call(arguments, 1)); + }; + + // Produce a duplicate-free version of the array. If the array has already + // been sorted, you have the option of using a faster algorithm. + // Aliased as `unique`. + _.uniq = _.unique = function(array, isSorted, iterator, context) { + if (_.isFunction(isSorted)) { + context = iterator; + iterator = isSorted; + isSorted = false; + } + var initial = iterator ? _.map(array, iterator, context) : array; + var results = []; + var seen = []; + each(initial, function(value, index) { + if (isSorted ? (!index || seen[seen.length - 1] !== value) : !_.contains(seen, value)) { + seen.push(value); + results.push(array[index]); + } + }); + return results; + }; + + // Produce an array that contains the union: each distinct element from all of + // the passed-in arrays. + _.union = function() { + return _.uniq(concat.apply(ArrayProto, arguments)); + }; + + // Produce an array that contains every item shared between all the + // passed-in arrays. + _.intersection = function(array) { + var rest = slice.call(arguments, 1); + return _.filter(_.uniq(array), function(item) { + return _.every(rest, function(other) { + return _.indexOf(other, item) >= 0; + }); + }); + }; + + // Take the difference between one array and a number of other arrays. + // Only the elements present in just the first array will remain. + _.difference = function(array) { + var rest = concat.apply(ArrayProto, slice.call(arguments, 1)); + return _.filter(array, function(value){ return !_.contains(rest, value); }); + }; + + // Zip together multiple lists into a single array -- elements that share + // an index go together. + _.zip = function() { + var args = slice.call(arguments); + var length = _.max(_.pluck(args, 'length')); + var results = new Array(length); + for (var i = 0; i < length; i++) { + results[i] = _.pluck(args, "" + i); + } + return results; + }; + + // Converts lists into objects. Pass either a single array of `[key, value]` + // pairs, or two parallel arrays of the same length -- one of keys, and one of + // the corresponding values. + _.object = function(list, values) { + if (list == null) return {}; + var result = {}; + for (var i = 0, l = list.length; i < l; i++) { + if (values) { + result[list[i]] = values[i]; + } else { + result[list[i][0]] = list[i][1]; + } + } + return result; + }; + + // If the browser doesn't supply us with indexOf (I'm looking at you, **MSIE**), + // we need this function. Return the position of the first occurrence of an + // item in an array, or -1 if the item is not included in the array. + // Delegates to **ECMAScript 5**'s native `indexOf` if available. + // If the array is large and already in sort order, pass `true` + // for **isSorted** to use binary search. + _.indexOf = function(array, item, isSorted) { + if (array == null) return -1; + var i = 0, l = array.length; + if (isSorted) { + if (typeof isSorted == 'number') { + i = (isSorted < 0 ? Math.max(0, l + isSorted) : isSorted); + } else { + i = _.sortedIndex(array, item); + return array[i] === item ? i : -1; + } + } + if (nativeIndexOf && array.indexOf === nativeIndexOf) return array.indexOf(item, isSorted); + for (; i < l; i++) if (array[i] === item) return i; + return -1; + }; + + // Delegates to **ECMAScript 5**'s native `lastIndexOf` if available. + _.lastIndexOf = function(array, item, from) { + if (array == null) return -1; + var hasIndex = from != null; + if (nativeLastIndexOf && array.lastIndexOf === nativeLastIndexOf) { + return hasIndex ? array.lastIndexOf(item, from) : array.lastIndexOf(item); + } + var i = (hasIndex ? from : array.length); + while (i--) if (array[i] === item) return i; + return -1; + }; + + // Generate an integer Array containing an arithmetic progression. A port of + // the native Python `range()` function. See + // [the Python documentation](http://docs.python.org/library/functions.html#range). + _.range = function(start, stop, step) { + if (arguments.length <= 1) { + stop = start || 0; + start = 0; + } + step = arguments[2] || 1; + + var len = Math.max(Math.ceil((stop - start) / step), 0); + var idx = 0; + var range = new Array(len); + + while(idx < len) { + range[idx++] = start; + start += step; + } + + return range; + }; + + // Function (ahem) Functions + // ------------------ + + // Create a function bound to a given object (assigning `this`, and arguments, + // optionally). Delegates to **ECMAScript 5**'s native `Function.bind` if + // available. + _.bind = function(func, context) { + if (func.bind === nativeBind && nativeBind) return nativeBind.apply(func, slice.call(arguments, 1)); + var args = slice.call(arguments, 2); + return function() { + return func.apply(context, args.concat(slice.call(arguments))); + }; + }; + + // Partially apply a function by creating a version that has had some of its + // arguments pre-filled, without changing its dynamic `this` context. + _.partial = function(func) { + var args = slice.call(arguments, 1); + return function() { + return func.apply(this, args.concat(slice.call(arguments))); + }; + }; + + // Bind all of an object's methods to that object. Useful for ensuring that + // all callbacks defined on an object belong to it. + _.bindAll = function(obj) { + var funcs = slice.call(arguments, 1); + if (funcs.length === 0) funcs = _.functions(obj); + each(funcs, function(f) { obj[f] = _.bind(obj[f], obj); }); + return obj; + }; + + // Memoize an expensive function by storing its results. + _.memoize = function(func, hasher) { + var memo = {}; + hasher || (hasher = _.identity); + return function() { + var key = hasher.apply(this, arguments); + return _.has(memo, key) ? memo[key] : (memo[key] = func.apply(this, arguments)); + }; + }; + + // Delays a function for the given number of milliseconds, and then calls + // it with the arguments supplied. + _.delay = function(func, wait) { + var args = slice.call(arguments, 2); + return setTimeout(function(){ return func.apply(null, args); }, wait); + }; + + // Defers a function, scheduling it to run after the current call stack has + // cleared. + _.defer = function(func) { + return _.delay.apply(_, [func, 1].concat(slice.call(arguments, 1))); + }; + + // Returns a function, that, when invoked, will only be triggered at most once + // during a given window of time. + _.throttle = function(func, wait) { + var context, args, timeout, result; + var previous = 0; + var later = function() { + previous = new Date; + timeout = null; + result = func.apply(context, args); + }; + return function() { + var now = new Date; + var remaining = wait - (now - previous); + context = this; + args = arguments; + if (remaining <= 0) { + clearTimeout(timeout); + timeout = null; + previous = now; + result = func.apply(context, args); + } else if (!timeout) { + timeout = setTimeout(later, remaining); + } + return result; + }; + }; + + // Returns a function, that, as long as it continues to be invoked, will not + // be triggered. The function will be called after it stops being called for + // N milliseconds. If `immediate` is passed, trigger the function on the + // leading edge, instead of the trailing. + _.debounce = function(func, wait, immediate) { + var timeout, result; + return function() { + var context = this, args = arguments; + var later = function() { + timeout = null; + if (!immediate) result = func.apply(context, args); + }; + var callNow = immediate && !timeout; + clearTimeout(timeout); + timeout = setTimeout(later, wait); + if (callNow) result = func.apply(context, args); + return result; + }; + }; + + // Returns a function that will be executed at most one time, no matter how + // often you call it. Useful for lazy initialization. + _.once = function(func) { + var ran = false, memo; + return function() { + if (ran) return memo; + ran = true; + memo = func.apply(this, arguments); + func = null; + return memo; + }; + }; + + // Returns the first function passed as an argument to the second, + // allowing you to adjust arguments, run code before and after, and + // conditionally execute the original function. + _.wrap = function(func, wrapper) { + return function() { + var args = [func]; + push.apply(args, arguments); + return wrapper.apply(this, args); + }; + }; + + // Returns a function that is the composition of a list of functions, each + // consuming the return value of the function that follows. + _.compose = function() { + var funcs = arguments; + return function() { + var args = arguments; + for (var i = funcs.length - 1; i >= 0; i--) { + args = [funcs[i].apply(this, args)]; + } + return args[0]; + }; + }; + + // Returns a function that will only be executed after being called N times. + _.after = function(times, func) { + if (times <= 0) return func(); + return function() { + if (--times < 1) { + return func.apply(this, arguments); + } + }; + }; + + // Object Functions + // ---------------- + + // Retrieve the names of an object's properties. + // Delegates to **ECMAScript 5**'s native `Object.keys` + _.keys = nativeKeys || function(obj) { + if (obj !== Object(obj)) throw new TypeError('Invalid object'); + var keys = []; + for (var key in obj) if (_.has(obj, key)) keys[keys.length] = key; + return keys; + }; + + // Retrieve the values of an object's properties. + _.values = function(obj) { + var values = []; + for (var key in obj) if (_.has(obj, key)) values.push(obj[key]); + return values; + }; + + // Convert an object into a list of `[key, value]` pairs. + _.pairs = function(obj) { + var pairs = []; + for (var key in obj) if (_.has(obj, key)) pairs.push([key, obj[key]]); + return pairs; + }; + + // Invert the keys and values of an object. The values must be serializable. + _.invert = function(obj) { + var result = {}; + for (var key in obj) if (_.has(obj, key)) result[obj[key]] = key; + return result; + }; + + // Return a sorted list of the function names available on the object. + // Aliased as `methods` + _.functions = _.methods = function(obj) { + var names = []; + for (var key in obj) { + if (_.isFunction(obj[key])) names.push(key); + } + return names.sort(); + }; + + // Extend a given object with all the properties in passed-in object(s). + _.extend = function(obj) { + each(slice.call(arguments, 1), function(source) { + if (source) { + for (var prop in source) { + obj[prop] = source[prop]; + } + } + }); + return obj; + }; + + // Return a copy of the object only containing the whitelisted properties. + _.pick = function(obj) { + var copy = {}; + var keys = concat.apply(ArrayProto, slice.call(arguments, 1)); + each(keys, function(key) { + if (key in obj) copy[key] = obj[key]; + }); + return copy; + }; + + // Return a copy of the object without the blacklisted properties. + _.omit = function(obj) { + var copy = {}; + var keys = concat.apply(ArrayProto, slice.call(arguments, 1)); + for (var key in obj) { + if (!_.contains(keys, key)) copy[key] = obj[key]; + } + return copy; + }; + + // Fill in a given object with default properties. + _.defaults = function(obj) { + each(slice.call(arguments, 1), function(source) { + if (source) { + for (var prop in source) { + if (obj[prop] == null) obj[prop] = source[prop]; + } + } + }); + return obj; + }; + + // Create a (shallow-cloned) duplicate of an object. + _.clone = function(obj) { + if (!_.isObject(obj)) return obj; + return _.isArray(obj) ? obj.slice() : _.extend({}, obj); + }; + + // Invokes interceptor with the obj, and then returns obj. + // The primary purpose of this method is to "tap into" a method chain, in + // order to perform operations on intermediate results within the chain. + _.tap = function(obj, interceptor) { + interceptor(obj); + return obj; + }; + + // Internal recursive comparison function for `isEqual`. + var eq = function(a, b, aStack, bStack) { + // Identical objects are equal. `0 === -0`, but they aren't identical. + // See the Harmony `egal` proposal: http://wiki.ecmascript.org/doku.php?id=harmony:egal. + if (a === b) return a !== 0 || 1 / a == 1 / b; + // A strict comparison is necessary because `null == undefined`. + if (a == null || b == null) return a === b; + // Unwrap any wrapped objects. + if (a instanceof _) a = a._wrapped; + if (b instanceof _) b = b._wrapped; + // Compare `[[Class]]` names. + var className = toString.call(a); + if (className != toString.call(b)) return false; + switch (className) { + // Strings, numbers, dates, and booleans are compared by value. + case '[object String]': + // Primitives and their corresponding object wrappers are equivalent; thus, `"5"` is + // equivalent to `new String("5")`. + return a == String(b); + case '[object Number]': + // `NaN`s are equivalent, but non-reflexive. An `egal` comparison is performed for + // other numeric values. + return a != +a ? b != +b : (a == 0 ? 1 / a == 1 / b : a == +b); + case '[object Date]': + case '[object Boolean]': + // Coerce dates and booleans to numeric primitive values. Dates are compared by their + // millisecond representations. Note that invalid dates with millisecond representations + // of `NaN` are not equivalent. + return +a == +b; + // RegExps are compared by their source patterns and flags. + case '[object RegExp]': + return a.source == b.source && + a.global == b.global && + a.multiline == b.multiline && + a.ignoreCase == b.ignoreCase; + } + if (typeof a != 'object' || typeof b != 'object') return false; + // Assume equality for cyclic structures. The algorithm for detecting cyclic + // structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`. + var length = aStack.length; + while (length--) { + // Linear search. Performance is inversely proportional to the number of + // unique nested structures. + if (aStack[length] == a) return bStack[length] == b; + } + // Add the first object to the stack of traversed objects. + aStack.push(a); + bStack.push(b); + var size = 0, result = true; + // Recursively compare objects and arrays. + if (className == '[object Array]') { + // Compare array lengths to determine if a deep comparison is necessary. + size = a.length; + result = size == b.length; + if (result) { + // Deep compare the contents, ignoring non-numeric properties. + while (size--) { + if (!(result = eq(a[size], b[size], aStack, bStack))) break; + } + } + } else { + // Objects with different constructors are not equivalent, but `Object`s + // from different frames are. + var aCtor = a.constructor, bCtor = b.constructor; + if (aCtor !== bCtor && !(_.isFunction(aCtor) && (aCtor instanceof aCtor) && + _.isFunction(bCtor) && (bCtor instanceof bCtor))) { + return false; + } + // Deep compare objects. + for (var key in a) { + if (_.has(a, key)) { + // Count the expected number of properties. + size++; + // Deep compare each member. + if (!(result = _.has(b, key) && eq(a[key], b[key], aStack, bStack))) break; + } + } + // Ensure that both objects contain the same number of properties. + if (result) { + for (key in b) { + if (_.has(b, key) && !(size--)) break; + } + result = !size; + } + } + // Remove the first object from the stack of traversed objects. + aStack.pop(); + bStack.pop(); + return result; + }; + + // Perform a deep comparison to check if two objects are equal. + _.isEqual = function(a, b) { + return eq(a, b, [], []); + }; + + // Is a given array, string, or object empty? + // An "empty" object has no enumerable own-properties. + _.isEmpty = function(obj) { + if (obj == null) return true; + if (_.isArray(obj) || _.isString(obj)) return obj.length === 0; + for (var key in obj) if (_.has(obj, key)) return false; + return true; + }; + + // Is a given value a DOM element? + _.isElement = function(obj) { + return !!(obj && obj.nodeType === 1); + }; + + // Is a given value an array? + // Delegates to ECMA5's native Array.isArray + _.isArray = nativeIsArray || function(obj) { + return toString.call(obj) == '[object Array]'; + }; + + // Is a given variable an object? + _.isObject = function(obj) { + return obj === Object(obj); + }; + + // Add some isType methods: isArguments, isFunction, isString, isNumber, isDate, isRegExp. + each(['Arguments', 'Function', 'String', 'Number', 'Date', 'RegExp'], function(name) { + _['is' + name] = function(obj) { + return toString.call(obj) == '[object ' + name + ']'; + }; + }); + + // Define a fallback version of the method in browsers (ahem, IE), where + // there isn't any inspectable "Arguments" type. + if (!_.isArguments(arguments)) { + _.isArguments = function(obj) { + return !!(obj && _.has(obj, 'callee')); + }; + } + + // Optimize `isFunction` if appropriate. + if (typeof (/./) !== 'function') { + _.isFunction = function(obj) { + return typeof obj === 'function'; + }; + } + + // Is a given object a finite number? + _.isFinite = function(obj) { + return isFinite(obj) && !isNaN(parseFloat(obj)); + }; + + // Is the given value `NaN`? (NaN is the only number which does not equal itself). + _.isNaN = function(obj) { + return _.isNumber(obj) && obj != +obj; + }; + + // Is a given value a boolean? + _.isBoolean = function(obj) { + return obj === true || obj === false || toString.call(obj) == '[object Boolean]'; + }; + + // Is a given value equal to null? + _.isNull = function(obj) { + return obj === null; + }; + + // Is a given variable undefined? + _.isUndefined = function(obj) { + return obj === void 0; + }; + + // Shortcut function for checking if an object has a given property directly + // on itself (in other words, not on a prototype). + _.has = function(obj, key) { + return hasOwnProperty.call(obj, key); + }; + + // Utility Functions + // ----------------- + + // Run Underscore.js in *noConflict* mode, returning the `_` variable to its + // previous owner. Returns a reference to the Underscore object. + _.noConflict = function() { + root._ = previousUnderscore; + return this; + }; + + // Keep the identity function around for default iterators. + _.identity = function(value) { + return value; + }; + + // Run a function **n** times. + _.times = function(n, iterator, context) { + var accum = Array(n); + for (var i = 0; i < n; i++) accum[i] = iterator.call(context, i); + return accum; + }; + + // Return a random integer between min and max (inclusive). + _.random = function(min, max) { + if (max == null) { + max = min; + min = 0; + } + return min + Math.floor(Math.random() * (max - min + 1)); + }; + + // List of HTML entities for escaping. + var entityMap = { + escape: { + '&': '&', + '<': '<', + '>': '>', + '"': '"', + "'": ''', + '/': '/' + } + }; + entityMap.unescape = _.invert(entityMap.escape); + + // Regexes containing the keys and values listed immediately above. + var entityRegexes = { + escape: new RegExp('[' + _.keys(entityMap.escape).join('') + ']', 'g'), + unescape: new RegExp('(' + _.keys(entityMap.unescape).join('|') + ')', 'g') + }; + + // Functions for escaping and unescaping strings to/from HTML interpolation. + _.each(['escape', 'unescape'], function(method) { + _[method] = function(string) { + if (string == null) return ''; + return ('' + string).replace(entityRegexes[method], function(match) { + return entityMap[method][match]; + }); + }; + }); + + // If the value of the named property is a function then invoke it; + // otherwise, return it. + _.result = function(object, property) { + if (object == null) return null; + var value = object[property]; + return _.isFunction(value) ? value.call(object) : value; + }; + + // Add your own custom functions to the Underscore object. + _.mixin = function(obj) { + each(_.functions(obj), function(name){ + var func = _[name] = obj[name]; + _.prototype[name] = function() { + var args = [this._wrapped]; + push.apply(args, arguments); + return result.call(this, func.apply(_, args)); + }; + }); + }; + + // Generate a unique integer id (unique within the entire client session). + // Useful for temporary DOM ids. + var idCounter = 0; + _.uniqueId = function(prefix) { + var id = ++idCounter + ''; + return prefix ? prefix + id : id; + }; + + // By default, Underscore uses ERB-style template delimiters, change the + // following template settings to use alternative delimiters. + _.templateSettings = { + evaluate : /<%([\s\S]+?)%>/g, + interpolate : /<%=([\s\S]+?)%>/g, + escape : /<%-([\s\S]+?)%>/g + }; + + // When customizing `templateSettings`, if you don't want to define an + // interpolation, evaluation or escaping regex, we need one that is + // guaranteed not to match. + var noMatch = /(.)^/; + + // Certain characters need to be escaped so that they can be put into a + // string literal. + var escapes = { + "'": "'", + '\\': '\\', + '\r': 'r', + '\n': 'n', + '\t': 't', + '\u2028': 'u2028', + '\u2029': 'u2029' + }; + + var escaper = /\\|'|\r|\n|\t|\u2028|\u2029/g; + + // JavaScript micro-templating, similar to John Resig's implementation. + // Underscore templating handles arbitrary delimiters, preserves whitespace, + // and correctly escapes quotes within interpolated code. + _.template = function(text, data, settings) { + var render; + settings = _.defaults({}, settings, _.templateSettings); + + // Combine delimiters into one regular expression via alternation. + var matcher = new RegExp([ + (settings.escape || noMatch).source, + (settings.interpolate || noMatch).source, + (settings.evaluate || noMatch).source + ].join('|') + '|$', 'g'); + + // Compile the template source, escaping string literals appropriately. + var index = 0; + var source = "__p+='"; + text.replace(matcher, function(match, escape, interpolate, evaluate, offset) { + source += text.slice(index, offset) + .replace(escaper, function(match) { return '\\' + escapes[match]; }); + + if (escape) { + source += "'+\n((__t=(" + escape + "))==null?'':_.escape(__t))+\n'"; + } + if (interpolate) { + source += "'+\n((__t=(" + interpolate + "))==null?'':__t)+\n'"; + } + if (evaluate) { + source += "';\n" + evaluate + "\n__p+='"; + } + index = offset + match.length; + return match; + }); + source += "';\n"; + + // If a variable is not specified, place data values in local scope. + if (!settings.variable) source = 'with(obj||{}){\n' + source + '}\n'; + + source = "var __t,__p='',__j=Array.prototype.join," + + "print=function(){__p+=__j.call(arguments,'');};\n" + + source + "return __p;\n"; + + try { + render = new Function(settings.variable || 'obj', '_', source); + } catch (e) { + e.source = source; + throw e; + } + + if (data) return render(data, _); + var template = function(data) { + return render.call(this, data, _); + }; + + // Provide the compiled function source as a convenience for precompilation. + template.source = 'function(' + (settings.variable || 'obj') + '){\n' + source + '}'; + + return template; + }; + + // Add a "chain" function, which will delegate to the wrapper. + _.chain = function(obj) { + return _(obj).chain(); + }; + + // OOP + // --------------- + // If Underscore is called as a function, it returns a wrapped object that + // can be used OO-style. This wrapper holds altered versions of all the + // underscore functions. Wrapped objects may be chained. + + // Helper function to continue chaining intermediate results. + var result = function(obj) { + return this._chain ? _(obj).chain() : obj; + }; + + // Add all of the Underscore functions to the wrapper object. + _.mixin(_); + + // Add all mutator Array functions to the wrapper. + each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) { + var method = ArrayProto[name]; + _.prototype[name] = function() { + var obj = this._wrapped; + method.apply(obj, arguments); + if ((name == 'shift' || name == 'splice') && obj.length === 0) delete obj[0]; + return result.call(this, obj); + }; + }); + + // Add all accessor Array functions to the wrapper. + each(['concat', 'join', 'slice'], function(name) { + var method = ArrayProto[name]; + _.prototype[name] = function() { + return result.call(this, method.apply(this._wrapped, arguments)); + }; + }); + + _.extend(_.prototype, { + + // Start chaining a wrapped Underscore object. + chain: function() { + this._chain = true; + return this; + }, + + // Extracts the result from a wrapped and chained object. + value: function() { + return this._wrapped; + } + + }); + +}).call(this); + +})() +},{}],14:[function(require,module,exports){ +exports.readIEEE754 = function(buffer, offset, isBE, mLen, nBytes) { + var e, m, + eLen = nBytes * 8 - mLen - 1, + eMax = (1 << eLen) - 1, + eBias = eMax >> 1, + nBits = -7, + i = isBE ? 0 : (nBytes - 1), + d = isBE ? 1 : -1, + s = buffer[offset + i]; + + i += d; + + e = s & ((1 << (-nBits)) - 1); + s >>= (-nBits); + nBits += eLen; + for (; nBits > 0; e = e * 256 + buffer[offset + i], i += d, nBits -= 8); + + m = e & ((1 << (-nBits)) - 1); + e >>= (-nBits); + nBits += mLen; + for (; nBits > 0; m = m * 256 + buffer[offset + i], i += d, nBits -= 8); + + if (e === 0) { + e = 1 - eBias; + } else if (e === eMax) { + return m ? NaN : ((s ? -1 : 1) * Infinity); + } else { + m = m + Math.pow(2, mLen); + e = e - eBias; + } + return (s ? -1 : 1) * m * Math.pow(2, e - mLen); +}; + +exports.writeIEEE754 = function(buffer, value, offset, isBE, mLen, nBytes) { + var e, m, c, + eLen = nBytes * 8 - mLen - 1, + eMax = (1 << eLen) - 1, + eBias = eMax >> 1, + rt = (mLen === 23 ? Math.pow(2, -24) - Math.pow(2, -77) : 0), + i = isBE ? (nBytes - 1) : 0, + d = isBE ? -1 : 1, + s = value < 0 || (value === 0 && 1 / value < 0) ? 1 : 0; + + value = Math.abs(value); + + if (isNaN(value) || value === Infinity) { + m = isNaN(value) ? 1 : 0; + e = eMax; + } else { + e = Math.floor(Math.log(value) / Math.LN2); + if (value * (c = Math.pow(2, -e)) < 1) { + e--; + c *= 2; + } + if (e + eBias >= 1) { + value += rt / c; + } else { + value += rt * Math.pow(2, 1 - eBias); + } + if (value * c >= 2) { + e++; + c /= 2; + } + + if (e + eBias >= eMax) { + m = 0; + e = eMax; + } else if (e + eBias >= 1) { + m = (value * c - 1) * Math.pow(2, mLen); + e = e + eBias; + } else { + m = value * Math.pow(2, eBias - 1) * Math.pow(2, mLen); + e = 0; + } + } + + for (; mLen >= 8; buffer[offset + i] = m & 0xff, i += d, m /= 256, mLen -= 8); + + e = (e << mLen) | m; + eLen += mLen; + for (; eLen > 0; buffer[offset + i] = e & 0xff, i += d, e /= 256, eLen -= 8); + + buffer[offset + i - d] |= s * 128; +}; + +},{}],13:[function(require,module,exports){ +(function(){function SlowBuffer (size) { + this.length = size; +}; + +var assert = require('assert'); + +exports.INSPECT_MAX_BYTES = 50; + + +function toHex(n) { + if (n < 16) return '0' + n.toString(16); + return n.toString(16); +} + +function utf8ToBytes(str) { + var byteArray = []; + for (var i = 0; i < str.length; i++) + if (str.charCodeAt(i) <= 0x7F) + byteArray.push(str.charCodeAt(i)); + else { + var h = encodeURIComponent(str.charAt(i)).substr(1).split('%'); + for (var j = 0; j < h.length; j++) + byteArray.push(parseInt(h[j], 16)); + } + + return byteArray; +} + +function asciiToBytes(str) { + var byteArray = [] + for (var i = 0; i < str.length; i++ ) + // Node's code seems to be doing this and not & 0x7F.. + byteArray.push( str.charCodeAt(i) & 0xFF ); + + return byteArray; +} + +function base64ToBytes(str) { + return require("base64-js").toByteArray(str); +} + +SlowBuffer.byteLength = function (str, encoding) { + switch (encoding || "utf8") { + case 'hex': + return str.length / 2; + + case 'utf8': + case 'utf-8': + return utf8ToBytes(str).length; + + case 'ascii': + case 'binary': + return str.length; + + case 'base64': + return base64ToBytes(str).length; + + default: + throw new Error('Unknown encoding'); + } +}; + +function blitBuffer(src, dst, offset, length) { + var pos, i = 0; + while (i < length) { + if ((i+offset >= dst.length) || (i >= src.length)) + break; + + dst[i + offset] = src[i]; + i++; + } + return i; +} + +SlowBuffer.prototype.utf8Write = function (string, offset, length) { + var bytes, pos; + return SlowBuffer._charsWritten = blitBuffer(utf8ToBytes(string), this, offset, length); +}; + +SlowBuffer.prototype.asciiWrite = function (string, offset, length) { + var bytes, pos; + return SlowBuffer._charsWritten = blitBuffer(asciiToBytes(string), this, offset, length); +}; + +SlowBuffer.prototype.binaryWrite = SlowBuffer.prototype.asciiWrite; + +SlowBuffer.prototype.base64Write = function (string, offset, length) { + var bytes, pos; + return SlowBuffer._charsWritten = blitBuffer(base64ToBytes(string), this, offset, length); +}; + +SlowBuffer.prototype.base64Slice = function (start, end) { + var bytes = Array.prototype.slice.apply(this, arguments) + return require("base64-js").fromByteArray(bytes); +} + +function decodeUtf8Char(str) { + try { + return decodeURIComponent(str); + } catch (err) { + return String.fromCharCode(0xFFFD); // UTF 8 invalid char + } +} + +SlowBuffer.prototype.utf8Slice = function () { + var bytes = Array.prototype.slice.apply(this, arguments); + var res = ""; + var tmp = ""; + var i = 0; + while (i < bytes.length) { + if (bytes[i] <= 0x7F) { + res += decodeUtf8Char(tmp) + String.fromCharCode(bytes[i]); + tmp = ""; + } else + tmp += "%" + bytes[i].toString(16); + + i++; + } + + return res + decodeUtf8Char(tmp); +} + +SlowBuffer.prototype.asciiSlice = function () { + var bytes = Array.prototype.slice.apply(this, arguments); + var ret = ""; + for (var i = 0; i < bytes.length; i++) + ret += String.fromCharCode(bytes[i]); + return ret; +} + +SlowBuffer.prototype.binarySlice = SlowBuffer.prototype.asciiSlice; + +SlowBuffer.prototype.inspect = function() { + var out = [], + len = this.length; + for (var i = 0; i < len; i++) { + out[i] = toHex(this[i]); + if (i == exports.INSPECT_MAX_BYTES) { + out[i + 1] = '...'; + break; + } + } + return '<SlowBuffer ' + out.join(' ') + '>'; +}; + + +SlowBuffer.prototype.hexSlice = function(start, end) { + var len = this.length; + + if (!start || start < 0) start = 0; + if (!end || end < 0 || end > len) end = len; + + var out = ''; + for (var i = start; i < end; i++) { + out += toHex(this[i]); + } + return out; +}; + + +SlowBuffer.prototype.toString = function(encoding, start, end) { + encoding = String(encoding || 'utf8').toLowerCase(); + start = +start || 0; + if (typeof end == 'undefined') end = this.length; + + // Fastpath empty strings + if (+end == start) { + return ''; + } + + switch (encoding) { + case 'hex': + return this.hexSlice(start, end); + + case 'utf8': + case 'utf-8': + return this.utf8Slice(start, end); + + case 'ascii': + return this.asciiSlice(start, end); + + case 'binary': + return this.binarySlice(start, end); + + case 'base64': + return this.base64Slice(start, end); + + case 'ucs2': + case 'ucs-2': + return this.ucs2Slice(start, end); + + default: + throw new Error('Unknown encoding'); + } +}; + + +SlowBuffer.prototype.hexWrite = function(string, offset, length) { + offset = +offset || 0; + var remaining = this.length - offset; + if (!length) { + length = remaining; + } else { + length = +length; + if (length > remaining) { + length = remaining; + } + } + + // must be an even number of digits + var strLen = string.length; + if (strLen % 2) { + throw new Error('Invalid hex string'); + } + if (length > strLen / 2) { + length = strLen / 2; + } + for (var i = 0; i < length; i++) { + var byte = parseInt(string.substr(i * 2, 2), 16); + if (isNaN(byte)) throw new Error('Invalid hex string'); + this[offset + i] = byte; + } + SlowBuffer._charsWritten = i * 2; + return i; +}; + + +SlowBuffer.prototype.write = function(string, offset, length, encoding) { + // Support both (string, offset, length, encoding) + // and the legacy (string, encoding, offset, length) + if (isFinite(offset)) { + if (!isFinite(length)) { + encoding = length; + length = undefined; + } + } else { // legacy + var swap = encoding; + encoding = offset; + offset = length; + length = swap; + } + + offset = +offset || 0; + var remaining = this.length - offset; + if (!length) { + length = remaining; + } else { + length = +length; + if (length > remaining) { + length = remaining; + } + } + encoding = String(encoding || 'utf8').toLowerCase(); + + switch (encoding) { + case 'hex': + return this.hexWrite(string, offset, length); + + case 'utf8': + case 'utf-8': + return this.utf8Write(string, offset, length); + + case 'ascii': + return this.asciiWrite(string, offset, length); + + case 'binary': + return this.binaryWrite(string, offset, length); + + case 'base64': + return this.base64Write(string, offset, length); + + case 'ucs2': + case 'ucs-2': + return this.ucs2Write(string, offset, length); + + default: + throw new Error('Unknown encoding'); + } +}; + + +// slice(start, end) +SlowBuffer.prototype.slice = function(start, end) { + if (end === undefined) end = this.length; + + if (end > this.length) { + throw new Error('oob'); + } + if (start > end) { + throw new Error('oob'); + } + + return new Buffer(this, end - start, +start); +}; + +SlowBuffer.prototype.copy = function(target, targetstart, sourcestart, sourceend) { + var temp = []; + for (var i=sourcestart; i<sourceend; i++) { + assert.ok(typeof this[i] !== 'undefined', "copying undefined buffer bytes!"); + temp.push(this[i]); + } + + for (var i=targetstart; i<targetstart+temp.length; i++) { + target[i] = temp[i-targetstart]; + } +}; + +SlowBuffer.prototype.fill = function(value, start, end) { + if (end > this.length) { + throw new Error('oob'); + } + if (start > end) { + throw new Error('oob'); + } + + for (var i = start; i < end; i++) { + this[i] = value; + } +} + +function coerce(length) { + // Coerce length to a number (possibly NaN), round up + // in case it's fractional (e.g. 123.456) then do a + // double negate to coerce a NaN to 0. Easy, right? + length = ~~Math.ceil(+length); + return length < 0 ? 0 : length; +} + + +// Buffer + +function Buffer(subject, encoding, offset) { + if (!(this instanceof Buffer)) { + return new Buffer(subject, encoding, offset); + } + + var type; + + // Are we slicing? + if (typeof offset === 'number') { + this.length = coerce(encoding); + this.parent = subject; + this.offset = offset; + } else { + // Find the length + switch (type = typeof subject) { + case 'number': + this.length = coerce(subject); + break; + + case 'string': + this.length = Buffer.byteLength(subject, encoding); + break; + + case 'object': // Assume object is an array + this.length = coerce(subject.length); + break; + + default: + throw new Error('First argument needs to be a number, ' + + 'array or string.'); + } + + if (this.length > Buffer.poolSize) { + // Big buffer, just alloc one. + this.parent = new SlowBuffer(this.length); + this.offset = 0; + + } else { + // Small buffer. + if (!pool || pool.length - pool.used < this.length) allocPool(); + this.parent = pool; + this.offset = pool.used; + pool.used += this.length; + } + + // Treat array-ish objects as a byte array. + if (isArrayIsh(subject)) { + for (var i = 0; i < this.length; i++) { + if (subject instanceof Buffer) { + this.parent[i + this.offset] = subject.readUInt8(i); + } + else { + this.parent[i + this.offset] = subject[i]; + } + } + } else if (type == 'string') { + // We are a string + this.length = this.write(subject, 0, encoding); + } + } + +} + +function isArrayIsh(subject) { + return Array.isArray(subject) || Buffer.isBuffer(subject) || + subject && typeof subject === 'object' && + typeof subject.length === 'number'; +} + +exports.SlowBuffer = SlowBuffer; +exports.Buffer = Buffer; + +Buffer.poolSize = 8 * 1024; +var pool; + +function allocPool() { + pool = new SlowBuffer(Buffer.poolSize); + pool.used = 0; +} + + +// Static methods +Buffer.isBuffer = function isBuffer(b) { + return b instanceof Buffer || b instanceof SlowBuffer; +}; + +Buffer.concat = function (list, totalLength) { + if (!Array.isArray(list)) { + throw new Error("Usage: Buffer.concat(list, [totalLength])\n \ + list should be an Array."); + } + + if (list.length === 0) { + return new Buffer(0); + } else if (list.length === 1) { + return list[0]; + } + + if (typeof totalLength !== 'number') { + totalLength = 0; + for (var i = 0; i < list.length; i++) { + var buf = list[i]; + totalLength += buf.length; + } + } + + var buffer = new Buffer(totalLength); + var pos = 0; + for (var i = 0; i < list.length; i++) { + var buf = list[i]; + buf.copy(buffer, pos); + pos += buf.length; + } + return buffer; +}; + +// Inspect +Buffer.prototype.inspect = function inspect() { + var out = [], + len = this.length; + + for (var i = 0; i < len; i++) { + out[i] = toHex(this.parent[i + this.offset]); + if (i == exports.INSPECT_MAX_BYTES) { + out[i + 1] = '...'; + break; + } + } + + return '<Buffer ' + out.join(' ') + '>'; +}; + + +Buffer.prototype.get = function get(i) { + if (i < 0 || i >= this.length) throw new Error('oob'); + return this.parent[this.offset + i]; +}; + + +Buffer.prototype.set = function set(i, v) { + if (i < 0 || i >= this.length) throw new Error('oob'); + return this.parent[this.offset + i] = v; +}; + + +// write(string, offset = 0, length = buffer.length-offset, encoding = 'utf8') +Buffer.prototype.write = function(string, offset, length, encoding) { + // Support both (string, offset, length, encoding) + // and the legacy (string, encoding, offset, length) + if (isFinite(offset)) { + if (!isFinite(length)) { + encoding = length; + length = undefined; + } + } else { // legacy + var swap = encoding; + encoding = offset; + offset = length; + length = swap; + } + + offset = +offset || 0; + var remaining = this.length - offset; + if (!length) { + length = remaining; + } else { + length = +length; + if (length > remaining) { + length = remaining; + } + } + encoding = String(encoding || 'utf8').toLowerCase(); + + var ret; + switch (encoding) { + case 'hex': + ret = this.parent.hexWrite(string, this.offset + offset, length); + break; + + case 'utf8': + case 'utf-8': + ret = this.parent.utf8Write(string, this.offset + offset, length); + break; + + case 'ascii': + ret = this.parent.asciiWrite(string, this.offset + offset, length); + break; + + case 'binary': + ret = this.parent.binaryWrite(string, this.offset + offset, length); + break; + + case 'base64': + // Warning: maxLength not taken into account in base64Write + ret = this.parent.base64Write(string, this.offset + offset, length); + break; + + case 'ucs2': + case 'ucs-2': + ret = this.parent.ucs2Write(string, this.offset + offset, length); + break; + + default: + throw new Error('Unknown encoding'); + } + + Buffer._charsWritten = SlowBuffer._charsWritten; + + return ret; +}; + + +// toString(encoding, start=0, end=buffer.length) +Buffer.prototype.toString = function(encoding, start, end) { + encoding = String(encoding || 'utf8').toLowerCase(); + + if (typeof start == 'undefined' || start < 0) { + start = 0; + } else if (start > this.length) { + start = this.length; + } + + if (typeof end == 'undefined' || end > this.length) { + end = this.length; + } else if (end < 0) { + end = 0; + } + + start = start + this.offset; + end = end + this.offset; + + switch (encoding) { + case 'hex': + return this.parent.hexSlice(start, end); + + case 'utf8': + case 'utf-8': + return this.parent.utf8Slice(start, end); + + case 'ascii': + return this.parent.asciiSlice(start, end); + + case 'binary': + return this.parent.binarySlice(start, end); + + case 'base64': + return this.parent.base64Slice(start, end); + + case 'ucs2': + case 'ucs-2': + return this.parent.ucs2Slice(start, end); + + default: + throw new Error('Unknown encoding'); + } +}; + + +// byteLength +Buffer.byteLength = SlowBuffer.byteLength; + + +// fill(value, start=0, end=buffer.length) +Buffer.prototype.fill = function fill(value, start, end) { + value || (value = 0); + start || (start = 0); + end || (end = this.length); + + if (typeof value === 'string') { + value = value.charCodeAt(0); + } + if (!(typeof value === 'number') || isNaN(value)) { + throw new Error('value is not a number'); + } + + if (end < start) throw new Error('end < start'); + + // Fill 0 bytes; we're done + if (end === start) return 0; + if (this.length == 0) return 0; + + if (start < 0 || start >= this.length) { + throw new Error('start out of bounds'); + } + + if (end < 0 || end > this.length) { + throw new Error('end out of bounds'); + } + + return this.parent.fill(value, + start + this.offset, + end + this.offset); +}; + + +// copy(targetBuffer, targetStart=0, sourceStart=0, sourceEnd=buffer.length) +Buffer.prototype.copy = function(target, target_start, start, end) { + var source = this; + start || (start = 0); + end || (end = this.length); + target_start || (target_start = 0); + + if (end < start) throw new Error('sourceEnd < sourceStart'); + + // Copy 0 bytes; we're done + if (end === start) return 0; + if (target.length == 0 || source.length == 0) return 0; + + if (target_start < 0 || target_start >= target.length) { + throw new Error('targetStart out of bounds'); + } + + if (start < 0 || start >= source.length) { + throw new Error('sourceStart out of bounds'); + } + + if (end < 0 || end > source.length) { + throw new Error('sourceEnd out of bounds'); + } + + // Are we oob? + if (end > this.length) { + end = this.length; + } + + if (target.length - target_start < end - start) { + end = target.length - target_start + start; + } + + return this.parent.copy(target.parent, + target_start + target.offset, + start + this.offset, + end + this.offset); +}; + + +// slice(start, end) +Buffer.prototype.slice = function(start, end) { + if (end === undefined) end = this.length; + if (end > this.length) throw new Error('oob'); + if (start > end) throw new Error('oob'); + + return new Buffer(this.parent, end - start, +start + this.offset); +}; + + +// Legacy methods for backwards compatibility. + +Buffer.prototype.utf8Slice = function(start, end) { + return this.toString('utf8', start, end); +}; + +Buffer.prototype.binarySlice = function(start, end) { + return this.toString('binary', start, end); +}; + +Buffer.prototype.asciiSlice = function(start, end) { + return this.toString('ascii', start, end); +}; + +Buffer.prototype.utf8Write = function(string, offset) { + return this.write(string, offset, 'utf8'); +}; + +Buffer.prototype.binaryWrite = function(string, offset) { + return this.write(string, offset, 'binary'); +}; + +Buffer.prototype.asciiWrite = function(string, offset) { + return this.write(string, offset, 'ascii'); +}; + +Buffer.prototype.readUInt8 = function(offset, noAssert) { + var buffer = this; + + if (!noAssert) { + assert.ok(offset !== undefined && offset !== null, + 'missing offset'); + + assert.ok(offset < buffer.length, + 'Trying to read beyond buffer length'); + } + + if (offset >= buffer.length) return; + + return buffer.parent[buffer.offset + offset]; +}; + +function readUInt16(buffer, offset, isBigEndian, noAssert) { + var val = 0; + + + if (!noAssert) { + assert.ok(typeof (isBigEndian) === 'boolean', + 'missing or invalid endian'); + + assert.ok(offset !== undefined && offset !== null, + 'missing offset'); + + assert.ok(offset + 1 < buffer.length, + 'Trying to read beyond buffer length'); + } + + if (offset >= buffer.length) return 0; + + if (isBigEndian) { + val = buffer.parent[buffer.offset + offset] << 8; + if (offset + 1 < buffer.length) { + val |= buffer.parent[buffer.offset + offset + 1]; + } + } else { + val = buffer.parent[buffer.offset + offset]; + if (offset + 1 < buffer.length) { + val |= buffer.parent[buffer.offset + offset + 1] << 8; + } + } + + return val; +} + +Buffer.prototype.readUInt16LE = function(offset, noAssert) { + return readUInt16(this, offset, false, noAssert); +}; + +Buffer.prototype.readUInt16BE = function(offset, noAssert) { + return readUInt16(this, offset, true, noAssert); +}; + +function readUInt32(buffer, offset, isBigEndian, noAssert) { + var val = 0; + + if (!noAssert) { + assert.ok(typeof (isBigEndian) === 'boolean', + 'missing or invalid endian'); + + assert.ok(offset !== undefined && offset !== null, + 'missing offset'); + + assert.ok(offset + 3 < buffer.length, + 'Trying to read beyond buffer length'); + } + + if (offset >= buffer.length) return 0; + + if (isBigEndian) { + if (offset + 1 < buffer.length) + val = buffer.parent[buffer.offset + offset + 1] << 16; + if (offset + 2 < buffer.length) + val |= buffer.parent[buffer.offset + offset + 2] << 8; + if (offset + 3 < buffer.length) + val |= buffer.parent[buffer.offset + offset + 3]; + val = val + (buffer.parent[buffer.offset + offset] << 24 >>> 0); + } else { + if (offset + 2 < buffer.length) + val = buffer.parent[buffer.offset + offset + 2] << 16; + if (offset + 1 < buffer.length) + val |= buffer.parent[buffer.offset + offset + 1] << 8; + val |= buffer.parent[buffer.offset + offset]; + if (offset + 3 < buffer.length) + val = val + (buffer.parent[buffer.offset + offset + 3] << 24 >>> 0); + } + + return val; +} + +Buffer.prototype.readUInt32LE = function(offset, noAssert) { + return readUInt32(this, offset, false, noAssert); +}; + +Buffer.prototype.readUInt32BE = function(offset, noAssert) { + return readUInt32(this, offset, true, noAssert); +}; + + +/* + * Signed integer types, yay team! A reminder on how two's complement actually + * works. The first bit is the signed bit, i.e. tells us whether or not the + * number should be positive or negative. If the two's complement value is + * positive, then we're done, as it's equivalent to the unsigned representation. + * + * Now if the number is positive, you're pretty much done, you can just leverage + * the unsigned translations and return those. Unfortunately, negative numbers + * aren't quite that straightforward. + * + * At first glance, one might be inclined to use the traditional formula to + * translate binary numbers between the positive and negative values in two's + * complement. (Though it doesn't quite work for the most negative value) + * Mainly: + * - invert all the bits + * - add one to the result + * + * Of course, this doesn't quite work in Javascript. Take for example the value + * of -128. This could be represented in 16 bits (big-endian) as 0xff80. But of + * course, Javascript will do the following: + * + * > ~0xff80 + * -65409 + * + * Whoh there, Javascript, that's not quite right. But wait, according to + * Javascript that's perfectly correct. When Javascript ends up seeing the + * constant 0xff80, it has no notion that it is actually a signed number. It + * assumes that we've input the unsigned value 0xff80. Thus, when it does the + * binary negation, it casts it into a signed value, (positive 0xff80). Then + * when you perform binary negation on that, it turns it into a negative number. + * + * Instead, we're going to have to use the following general formula, that works + * in a rather Javascript friendly way. I'm glad we don't support this kind of + * weird numbering scheme in the kernel. + * + * (BIT-MAX - (unsigned)val + 1) * -1 + * + * The astute observer, may think that this doesn't make sense for 8-bit numbers + * (really it isn't necessary for them). However, when you get 16-bit numbers, + * you do. Let's go back to our prior example and see how this will look: + * + * (0xffff - 0xff80 + 1) * -1 + * (0x007f + 1) * -1 + * (0x0080) * -1 + */ +Buffer.prototype.readInt8 = function(offset, noAssert) { + var buffer = this; + var neg; + + if (!noAssert) { + assert.ok(offset !== undefined && offset !== null, + 'missing offset'); + + assert.ok(offset < buffer.length, + 'Trying to read beyond buffer length'); + } + + if (offset >= buffer.length) return; + + neg = buffer.parent[buffer.offset + offset] & 0x80; + if (!neg) { + return (buffer.parent[buffer.offset + offset]); + } + + return ((0xff - buffer.parent[buffer.offset + offset] + 1) * -1); +}; + +function readInt16(buffer, offset, isBigEndian, noAssert) { + var neg, val; + + if (!noAssert) { + assert.ok(typeof (isBigEndian) === 'boolean', + 'missing or invalid endian'); + + assert.ok(offset !== undefined && offset !== null, + 'missing offset'); + + assert.ok(offset + 1 < buffer.length, + 'Trying to read beyond buffer length'); + } + + val = readUInt16(buffer, offset, isBigEndian, noAssert); + neg = val & 0x8000; + if (!neg) { + return val; + } + + return (0xffff - val + 1) * -1; +} + +Buffer.prototype.readInt16LE = function(offset, noAssert) { + return readInt16(this, offset, false, noAssert); +}; + +Buffer.prototype.readInt16BE = function(offset, noAssert) { + return readInt16(this, offset, true, noAssert); +}; + +function readInt32(buffer, offset, isBigEndian, noAssert) { + var neg, val; + + if (!noAssert) { + assert.ok(typeof (isBigEndian) === 'boolean', + 'missing or invalid endian'); + + assert.ok(offset !== undefined && offset !== null, + 'missing offset'); + + assert.ok(offset + 3 < buffer.length, + 'Trying to read beyond buffer length'); + } + + val = readUInt32(buffer, offset, isBigEndian, noAssert); + neg = val & 0x80000000; + if (!neg) { + return (val); + } + + return (0xffffffff - val + 1) * -1; +} + +Buffer.prototype.readInt32LE = function(offset, noAssert) { + return readInt32(this, offset, false, noAssert); +}; + +Buffer.prototype.readInt32BE = function(offset, noAssert) { + return readInt32(this, offset, true, noAssert); +}; + +function readFloat(buffer, offset, isBigEndian, noAssert) { + if (!noAssert) { + assert.ok(typeof (isBigEndian) === 'boolean', + 'missing or invalid endian'); + + assert.ok(offset + 3 < buffer.length, + 'Trying to read beyond buffer length'); + } + + return require('./buffer_ieee754').readIEEE754(buffer, offset, isBigEndian, + 23, 4); +} + +Buffer.prototype.readFloatLE = function(offset, noAssert) { + return readFloat(this, offset, false, noAssert); +}; + +Buffer.prototype.readFloatBE = function(offset, noAssert) { + return readFloat(this, offset, true, noAssert); +}; + +function readDouble(buffer, offset, isBigEndian, noAssert) { + if (!noAssert) { + assert.ok(typeof (isBigEndian) === 'boolean', + 'missing or invalid endian'); + + assert.ok(offset + 7 < buffer.length, + 'Trying to read beyond buffer length'); + } + + return require('./buffer_ieee754').readIEEE754(buffer, offset, isBigEndian, + 52, 8); +} + +Buffer.prototype.readDoubleLE = function(offset, noAssert) { + return readDouble(this, offset, false, noAssert); +}; + +Buffer.prototype.readDoubleBE = function(offset, noAssert) { + return readDouble(this, offset, true, noAssert); +}; + + +/* + * We have to make sure that the value is a valid integer. This means that it is + * non-negative. It has no fractional component and that it does not exceed the + * maximum allowed value. + * + * value The number to check for validity + * + * max The maximum value + */ +function verifuint(value, max) { + assert.ok(typeof (value) == 'number', + 'cannot write a non-number as a number'); + + assert.ok(value >= 0, + 'specified a negative value for writing an unsigned value'); + + assert.ok(value <= max, 'value is larger than maximum value for type'); + + assert.ok(Math.floor(value) === value, 'value has a fractional component'); +} + +Buffer.prototype.writeUInt8 = function(value, offset, noAssert) { + var buffer = this; + + if (!noAssert) { + assert.ok(value !== undefined && value !== null, + 'missing value'); + + assert.ok(offset !== undefined && offset !== null, + 'missing offset'); + + assert.ok(offset < buffer.length, + 'trying to write beyond buffer length'); + + verifuint(value, 0xff); + } + + if (offset < buffer.length) { + buffer.parent[buffer.offset + offset] = value; + } +}; + +function writeUInt16(buffer, value, offset, isBigEndian, noAssert) { + if (!noAssert) { + assert.ok(value !== undefined && value !== null, + 'missing value'); + + assert.ok(typeof (isBigEndian) === 'boolean', + 'missing or invalid endian'); + + assert.ok(offset !== undefined && offset !== null, + 'missing offset'); + + assert.ok(offset + 1 < buffer.length, + 'trying to write beyond buffer length'); + + verifuint(value, 0xffff); + } + + for (var i = 0; i < Math.min(buffer.length - offset, 2); i++) { + buffer.parent[buffer.offset + offset + i] = + (value & (0xff << (8 * (isBigEndian ? 1 - i : i)))) >>> + (isBigEndian ? 1 - i : i) * 8; + } + +} + +Buffer.prototype.writeUInt16LE = function(value, offset, noAssert) { + writeUInt16(this, value, offset, false, noAssert); +}; + +Buffer.prototype.writeUInt16BE = function(value, offset, noAssert) { + writeUInt16(this, value, offset, true, noAssert); +}; + +function writeUInt32(buffer, value, offset, isBigEndian, noAssert) { + if (!noAssert) { + assert.ok(value !== undefined && value !== null, + 'missing value'); + + assert.ok(typeof (isBigEndian) === 'boolean', + 'missing or invalid endian'); + + assert.ok(offset !== undefined && offset !== null, + 'missing offset'); + + assert.ok(offset + 3 < buffer.length, + 'trying to write beyond buffer length'); + + verifuint(value, 0xffffffff); + } + + for (var i = 0; i < Math.min(buffer.length - offset, 4); i++) { + buffer.parent[buffer.offset + offset + i] = + (value >>> (isBigEndian ? 3 - i : i) * 8) & 0xff; + } +} + +Buffer.prototype.writeUInt32LE = function(value, offset, noAssert) { + writeUInt32(this, value, offset, false, noAssert); +}; + +Buffer.prototype.writeUInt32BE = function(value, offset, noAssert) { + writeUInt32(this, value, offset, true, noAssert); +}; + + +/* + * We now move onto our friends in the signed number category. Unlike unsigned + * numbers, we're going to have to worry a bit more about how we put values into + * arrays. Since we are only worrying about signed 32-bit values, we're in + * slightly better shape. Unfortunately, we really can't do our favorite binary + * & in this system. It really seems to do the wrong thing. For example: + * + * > -32 & 0xff + * 224 + * + * What's happening above is really: 0xe0 & 0xff = 0xe0. However, the results of + * this aren't treated as a signed number. Ultimately a bad thing. + * + * What we're going to want to do is basically create the unsigned equivalent of + * our representation and pass that off to the wuint* functions. To do that + * we're going to do the following: + * + * - if the value is positive + * we can pass it directly off to the equivalent wuint + * - if the value is negative + * we do the following computation: + * mb + val + 1, where + * mb is the maximum unsigned value in that byte size + * val is the Javascript negative integer + * + * + * As a concrete value, take -128. In signed 16 bits this would be 0xff80. If + * you do out the computations: + * + * 0xffff - 128 + 1 + * 0xffff - 127 + * 0xff80 + * + * You can then encode this value as the signed version. This is really rather + * hacky, but it should work and get the job done which is our goal here. + */ + +/* + * A series of checks to make sure we actually have a signed 32-bit number + */ +function verifsint(value, max, min) { + assert.ok(typeof (value) == 'number', + 'cannot write a non-number as a number'); + + assert.ok(value <= max, 'value larger than maximum allowed value'); + + assert.ok(value >= min, 'value smaller than minimum allowed value'); + + assert.ok(Math.floor(value) === value, 'value has a fractional component'); +} + +function verifIEEE754(value, max, min) { + assert.ok(typeof (value) == 'number', + 'cannot write a non-number as a number'); + + assert.ok(value <= max, 'value larger than maximum allowed value'); + + assert.ok(value >= min, 'value smaller than minimum allowed value'); +} + +Buffer.prototype.writeInt8 = function(value, offset, noAssert) { + var buffer = this; + + if (!noAssert) { + assert.ok(value !== undefined && value !== null, + 'missing value'); + + assert.ok(offset !== undefined && offset !== null, + 'missing offset'); + + assert.ok(offset < buffer.length, + 'Trying to write beyond buffer length'); + + verifsint(value, 0x7f, -0x80); + } + + if (value >= 0) { + buffer.writeUInt8(value, offset, noAssert); + } else { + buffer.writeUInt8(0xff + value + 1, offset, noAssert); + } +}; + +function writeInt16(buffer, value, offset, isBigEndian, noAssert) { + if (!noAssert) { + assert.ok(value !== undefined && value !== null, + 'missing value'); + + assert.ok(typeof (isBigEndian) === 'boolean', + 'missing or invalid endian'); + + assert.ok(offset !== undefined && offset !== null, + 'missing offset'); + + assert.ok(offset + 1 < buffer.length, + 'Trying to write beyond buffer length'); + + verifsint(value, 0x7fff, -0x8000); + } + + if (value >= 0) { + writeUInt16(buffer, value, offset, isBigEndian, noAssert); + } else { + writeUInt16(buffer, 0xffff + value + 1, offset, isBigEndian, noAssert); + } +} + +Buffer.prototype.writeInt16LE = function(value, offset, noAssert) { + writeInt16(this, value, offset, false, noAssert); +}; + +Buffer.prototype.writeInt16BE = function(value, offset, noAssert) { + writeInt16(this, value, offset, true, noAssert); +}; + +function writeInt32(buffer, value, offset, isBigEndian, noAssert) { + if (!noAssert) { + assert.ok(value !== undefined && value !== null, + 'missing value'); + + assert.ok(typeof (isBigEndian) === 'boolean', + 'missing or invalid endian'); + + assert.ok(offset !== undefined && offset !== null, + 'missing offset'); + + assert.ok(offset + 3 < buffer.length, + 'Trying to write beyond buffer length'); + + verifsint(value, 0x7fffffff, -0x80000000); + } + + if (value >= 0) { + writeUInt32(buffer, value, offset, isBigEndian, noAssert); + } else { + writeUInt32(buffer, 0xffffffff + value + 1, offset, isBigEndian, noAssert); + } +} + +Buffer.prototype.writeInt32LE = function(value, offset, noAssert) { + writeInt32(this, value, offset, false, noAssert); +}; + +Buffer.prototype.writeInt32BE = function(value, offset, noAssert) { + writeInt32(this, value, offset, true, noAssert); +}; + +function writeFloat(buffer, value, offset, isBigEndian, noAssert) { + if (!noAssert) { + assert.ok(value !== undefined && value !== null, + 'missing value'); + + assert.ok(typeof (isBigEndian) === 'boolean', + 'missing or invalid endian'); + + assert.ok(offset !== undefined && offset !== null, + 'missing offset'); + + assert.ok(offset + 3 < buffer.length, + 'Trying to write beyond buffer length'); + + verifIEEE754(value, 3.4028234663852886e+38, -3.4028234663852886e+38); + } + + require('./buffer_ieee754').writeIEEE754(buffer, value, offset, isBigEndian, + 23, 4); +} + +Buffer.prototype.writeFloatLE = function(value, offset, noAssert) { + writeFloat(this, value, offset, false, noAssert); +}; + +Buffer.prototype.writeFloatBE = function(value, offset, noAssert) { + writeFloat(this, value, offset, true, noAssert); +}; + +function writeDouble(buffer, value, offset, isBigEndian, noAssert) { + if (!noAssert) { + assert.ok(value !== undefined && value !== null, + 'missing value'); + + assert.ok(typeof (isBigEndian) === 'boolean', + 'missing or invalid endian'); + + assert.ok(offset !== undefined && offset !== null, + 'missing offset'); + + assert.ok(offset + 7 < buffer.length, + 'Trying to write beyond buffer length'); + + verifIEEE754(value, 1.7976931348623157E+308, -1.7976931348623157E+308); + } + + require('./buffer_ieee754').writeIEEE754(buffer, value, offset, isBigEndian, + 52, 8); +} + +Buffer.prototype.writeDoubleLE = function(value, offset, noAssert) { + writeDouble(this, value, offset, false, noAssert); +}; + +Buffer.prototype.writeDoubleBE = function(value, offset, noAssert) { + writeDouble(this, value, offset, true, noAssert); +}; + +SlowBuffer.prototype.readUInt8 = Buffer.prototype.readUInt8; +SlowBuffer.prototype.readUInt16LE = Buffer.prototype.readUInt16LE; +SlowBuffer.prototype.readUInt16BE = Buffer.prototype.readUInt16BE; +SlowBuffer.prototype.readUInt32LE = Buffer.prototype.readUInt32LE; +SlowBuffer.prototype.readUInt32BE = Buffer.prototype.readUInt32BE; +SlowBuffer.prototype.readInt8 = Buffer.prototype.readInt8; +SlowBuffer.prototype.readInt16LE = Buffer.prototype.readInt16LE; +SlowBuffer.prototype.readInt16BE = Buffer.prototype.readInt16BE; +SlowBuffer.prototype.readInt32LE = Buffer.prototype.readInt32LE; +SlowBuffer.prototype.readInt32BE = Buffer.prototype.readInt32BE; +SlowBuffer.prototype.readFloatLE = Buffer.prototype.readFloatLE; +SlowBuffer.prototype.readFloatBE = Buffer.prototype.readFloatBE; +SlowBuffer.prototype.readDoubleLE = Buffer.prototype.readDoubleLE; +SlowBuffer.prototype.readDoubleBE = Buffer.prototype.readDoubleBE; +SlowBuffer.prototype.writeUInt8 = Buffer.prototype.writeUInt8; +SlowBuffer.prototype.writeUInt16LE = Buffer.prototype.writeUInt16LE; +SlowBuffer.prototype.writeUInt16BE = Buffer.prototype.writeUInt16BE; +SlowBuffer.prototype.writeUInt32LE = Buffer.prototype.writeUInt32LE; +SlowBuffer.prototype.writeUInt32BE = Buffer.prototype.writeUInt32BE; +SlowBuffer.prototype.writeInt8 = Buffer.prototype.writeInt8; +SlowBuffer.prototype.writeInt16LE = Buffer.prototype.writeInt16LE; +SlowBuffer.prototype.writeInt16BE = Buffer.prototype.writeInt16BE; +SlowBuffer.prototype.writeInt32LE = Buffer.prototype.writeInt32LE; +SlowBuffer.prototype.writeInt32BE = Buffer.prototype.writeInt32BE; +SlowBuffer.prototype.writeFloatLE = Buffer.prototype.writeFloatLE; +SlowBuffer.prototype.writeFloatBE = Buffer.prototype.writeFloatBE; +SlowBuffer.prototype.writeDoubleLE = Buffer.prototype.writeDoubleLE; +SlowBuffer.prototype.writeDoubleBE = Buffer.prototype.writeDoubleBE; + +})() +},{"assert":9,"./buffer_ieee754":14,"base64-js":15}],15:[function(require,module,exports){ +(function (exports) { + 'use strict'; + + var lookup = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'; + + function b64ToByteArray(b64) { + var i, j, l, tmp, placeHolders, arr; + + if (b64.length % 4 > 0) { + throw 'Invalid string. Length must be a multiple of 4'; + } + + // the number of equal signs (place holders) + // if there are two placeholders, than the two characters before it + // represent one byte + // if there is only one, then the three characters before it represent 2 bytes + // this is just a cheap hack to not do indexOf twice + placeHolders = b64.indexOf('='); + placeHolders = placeHolders > 0 ? b64.length - placeHolders : 0; + + // base64 is 4/3 + up to two characters of the original data + arr = [];//new Uint8Array(b64.length * 3 / 4 - placeHolders); + + // if there are placeholders, only get up to the last complete 4 chars + l = placeHolders > 0 ? b64.length - 4 : b64.length; + + for (i = 0, j = 0; i < l; i += 4, j += 3) { + tmp = (lookup.indexOf(b64[i]) << 18) | (lookup.indexOf(b64[i + 1]) << 12) | (lookup.indexOf(b64[i + 2]) << 6) | lookup.indexOf(b64[i + 3]); + arr.push((tmp & 0xFF0000) >> 16); + arr.push((tmp & 0xFF00) >> 8); + arr.push(tmp & 0xFF); + } + + if (placeHolders === 2) { + tmp = (lookup.indexOf(b64[i]) << 2) | (lookup.indexOf(b64[i + 1]) >> 4); + arr.push(tmp & 0xFF); + } else if (placeHolders === 1) { + tmp = (lookup.indexOf(b64[i]) << 10) | (lookup.indexOf(b64[i + 1]) << 4) | (lookup.indexOf(b64[i + 2]) >> 2); + arr.push((tmp >> 8) & 0xFF); + arr.push(tmp & 0xFF); + } + + return arr; + } + + function uint8ToBase64(uint8) { + var i, + extraBytes = uint8.length % 3, // if we have 1 byte left, pad 2 bytes + output = "", + temp, length; + + function tripletToBase64 (num) { + return lookup[num >> 18 & 0x3F] + lookup[num >> 12 & 0x3F] + lookup[num >> 6 & 0x3F] + lookup[num & 0x3F]; + }; + + // go through the array every three bytes, we'll deal with trailing stuff later + for (i = 0, length = uint8.length - extraBytes; i < length; i += 3) { + temp = (uint8[i] << 16) + (uint8[i + 1] << 8) + (uint8[i + 2]); + output += tripletToBase64(temp); + } + + // pad the end with zeros, but make sure to not forget the extra bytes + switch (extraBytes) { + case 1: + temp = uint8[uint8.length - 1]; + output += lookup[temp >> 2]; + output += lookup[(temp << 4) & 0x3F]; + output += '=='; + break; + case 2: + temp = (uint8[uint8.length - 2] << 8) + (uint8[uint8.length - 1]); + output += lookup[temp >> 10]; + output += lookup[(temp >> 4) & 0x3F]; + output += lookup[(temp << 2) & 0x3F]; + output += '='; + break; + } + + return output; + } + + module.exports.toByteArray = b64ToByteArray; + module.exports.fromByteArray = uint8ToBase64; +}()); + +},{}]},{},["E/GbHF"]) +; +JSHINT = require('jshint').JSHINT; +}()); diff --git a/dom/system/gonk/tests/marionette/ril_jshint/jshintrc b/dom/system/gonk/tests/marionette/ril_jshint/jshintrc new file mode 100644 index 000000000..437fe1a6f --- /dev/null +++ b/dom/system/gonk/tests/marionette/ril_jshint/jshintrc @@ -0,0 +1,118 @@ +{ + // JSHint Default Configuration File (as on JSHint website) + // See http://jshint.com/docs/ for more details + + // Modify for RIL usage. + + "maxerr" : 10000, // {int} Maximum error before stopping + + // Enforcing + "bitwise" : false, // true: Prohibit bitwise operators (&, |, ^, etc.) + "camelcase" : false, // true: Identifiers must be in camelCase + "curly" : false, // true: Require {} for every new block or scope + "eqeqeq" : false, // true: Require triple equals (===) for comparison + "forin" : false, // true: Require filtering for..in loops with obj.hasOwnProperty() + "immed" : false, // true: Require immediate invocations to be wrapped in parens e.g. `(function () { } ());` + //"indent" : 2, // {int} Number of spaces to use for indentation + "latedef" : false, // true: Require variables/functions to be defined before being used + "newcap" : false, // true: Require capitalization of all constructor functions e.g. `new F()` + "noarg" : true, // true: Prohibit use of `arguments.caller` and `arguments.callee` + "noempty" : false, // true: Prohibit use of empty blocks + "nonew" : false, // true: Prohibit use of constructors for side-effects (without assignment) + "plusplus" : false, // true: Prohibit use of `++` & `--` + "quotmark" : false, // Quotation mark consistency: + // false : do nothing (default) + // true : ensure whatever is used is consistent + // "single" : require single quotes + // "double" : require double quotes + "undef" : false, // true: Require all non-global variables to be declared (prevents global leaks) + "unused" : false, // true: Require all defined variables be used + "strict" : false, // true: Requires all functions run in ES5 Strict Mode + "trailing" : false, // true: Prohibit trailing whitespaces + "maxparams" : false, // {int} Max number of formal params allowed per function + "maxdepth" : false, // {int} Max depth of nested blocks (within functions) + "maxstatements" : false, // {int} Max number statements per function + "maxcomplexity" : false, // {int} Max cyclomatic complexity per function + "maxlen" : false, // {int} Max number of characters per line + + // Relaxing + "asi" : false, // true: Tolerate Automatic Semicolon Insertion (no semicolons) + "boss" : false, // true: Tolerate assignments where comparisons would be expected + "debug" : false, // true: Allow debugger statements e.g. browser breakpoints. + "eqnull" : true, // true: Tolerate use of `== null` + "es5" : false, // true: Allow ES5 syntax (ex: getters and setters) + "esnext" : false, // true: Allow ES.next (ES6) syntax (ex: `const`) + "moz" : true, // true: Allow Mozilla specific syntax (extends and overrides esnext features) + // (ex: `for each`, multiple try/catch, function expression…) + "evil" : false, // true: Tolerate use of `eval` and `new Function()` + "expr" : false, // true: Tolerate `ExpressionStatement` as Programs + "funcscope" : false, // true: Tolerate defining variables inside control statements" + "globalstrict" : true, // true: Allow global "use strict" (also enables 'strict') + "iterator" : false, // true: Tolerate using the `__iterator__` property + "lastsemic" : false, // true: Tolerate omitting a semicolon for the last statement of a 1-line block + "laxbreak" : true, // true: Tolerate possibly unsafe line breakings + "laxcomma" : false, // true: Tolerate comma-first style coding + "loopfunc" : false, // true: Tolerate functions being defined in loops + "multistr" : false, // true: Tolerate multi-line strings + "proto" : true, // true: Tolerate using the `__proto__` property + "scripturl" : false, // true: Tolerate script-targeted URLs + "smarttabs" : false, // true: Tolerate mixed tabs/spaces when used for alignment + "shadow" : false, // true: Allows re-define variables later in code e.g. `var x=1; x=2;` + "sub" : false, // true: Tolerate using `[]` notation when it can still be expressed in dot notation + "supernew" : false, // true: Tolerate `new function () { ... };` and `new Object;` + "validthis" : true, // true: Tolerate using this in a non-constructor function + + // Environments + "browser" : false, // Web Browser (window, document, etc) + "couch" : false, // CouchDB + "devel" : true, // Development/debugging (alert, confirm, etc) + "dojo" : false, // Dojo Toolkit + "jquery" : false, // jQuery + "mootools" : false, // MooTools + "node" : false, // Node.js + "nonstandard" : false, // Widely adopted globals (escape, unescape, etc) + "prototypejs" : false, // Prototype and Scriptaculous + "rhino" : false, // Rhino + "worker" : true, // Web Workers + "wsh" : false, // Windows Scripting Host + "yui" : false, // Yahoo User Interface + + // Legacy + "nomen" : false, // true: Prohibit dangling `_` in variables + "onevar" : false, // true: Allow only one `var` statement per function + "passfail" : false, // true: Stop on first error + "white" : false, // true: Check against strict whitespace and indentation rules + + // Custom Globals + "predef" : [ ], // additional predefined global variables + + "globals": { + "ChromeWorker": false, + "Components": false, + "DOMRequestIpcHelper": false, + "ObjectWrapper": false, + "PhoneNumberUtils": false, + "RILNetworkInterface": false, + "Services": false, + "Uint8Array": false, + "WAP": false, + "XPCOMUtils": false, + "cpmm": false, + "dump": false, + "gAudioManager": false, + "gMessageManager": false, + "gMobileMessageDatabaseService": false, + "gMobileMessageService": false, + "gNetworkManager": false, + "gPowerManagerService": false, + "gSettingsService": false, + "gSmsService": false, + "gSystemMessenger": false, + "gSystemWorkerManager": false, + "gTimeService": false, + "gUUIDGenerator": false, + "ppmm": true, + + "__end_guardian_for_easy_sorting__": false + } +} diff --git a/dom/system/gonk/tests/marionette/test_all_network_info.js b/dom/system/gonk/tests/marionette/test_all_network_info.js new file mode 100644 index 000000000..5225ab6d6 --- /dev/null +++ b/dom/system/gonk/tests/marionette/test_all_network_info.js @@ -0,0 +1,106 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +MARIONETTE_TIMEOUT = 60000; +MARIONETTE_HEAD_JS = "head.js"; + +var networkManager = + Cc["@mozilla.org/network/manager;1"].getService(Ci.nsINetworkManager); +ok(networkManager, + "networkManager.constructor is " + networkManager.constructor); + +var wifiManager = window.navigator.mozWifiManager; +ok(wifiManager, "wifiManager.constructor is " + wifiManager.constructor); + +function setEmulatorAPN() { + let apn = [ + [{"carrier":"T-Mobile US", + "apn":"epc.tmobile.com", + "mmsc":"http://mms.msg.eng.t-mobile.com/mms/wapenc", + "types":["default","supl","mms","ims","dun", "fota"]}] + ]; + + return setSettings(SETTINGS_KEY_DATA_APN_SETTINGS, apn); +} + +function ensureWifiEnabled(aEnabled) { + if (wifiManager.enabled === aEnabled) { + log('Already ' + (aEnabled ? 'enabled' : 'disabled')); + return Promise.resolve(); + } + return requestWifiEnabled(aEnabled); +} + +function requestWifiEnabled(aEnabled) { + let promises = []; + + promises.push(waitForTargetEvent(wifiManager, aEnabled ? 'enabled' : 'disabled', + function() { + return wifiManager.enabled === aEnabled ? true : false; + })); + promises.push(setSettings(SETTINGS_KEY_WIFI_ENABLED, aEnabled)); + + return Promise.all(promises); +} + +// Test initial State +function verifyInitialState() { + log("= verifyInitialState ="); + + // Data and wifi should be off before starting any test. + return getSettings(SETTINGS_KEY_DATA_ENABLED) + .then(value => { + is(value, false, "Data must be off"); + }) + .then(() => ensureWifiEnabled(false)); +} + +function testAllNetworkInfo(aAnyConnected) { + log("= testAllNetworkInfo = " + aAnyConnected); + + let allNetworkInfo = networkManager.allNetworkInfo; + ok(allNetworkInfo, "NetworkManager.allNetworkInfo"); + + let count = Object.keys(allNetworkInfo).length; + ok(count > 0, "NetworkManager.allNetworkInfo count"); + + let connected = false; + for (let networkId in allNetworkInfo) { + if (allNetworkInfo.hasOwnProperty(networkId)) { + let networkInfo = allNetworkInfo[networkId]; + if (networkInfo.state == Ci.nsINetworkInfo.NETWORK_STATE_CONNECTED) { + connected = true; + break; + } + } + } + + is(aAnyConnected, connected, "NetworkManager.allNetworkInfo any connected"); +} + +// Start test +startTestBase(function() { + + let origApnSettings, origWifiEnabled; + return Promise.resolve() + .then(() => { + origWifiEnabled = wifiManager.enabled; + }) + .then(() => verifyInitialState()) + .then(() => getSettings(SETTINGS_KEY_DATA_APN_SETTINGS)) + .then(value => { + origApnSettings = value; + }) + .then(() => setEmulatorAPN()) + .then(() => setDataEnabledAndWait(true)) + .then(() => testAllNetworkInfo(true)) + .then(() => setDataEnabledAndWait(false)) + .then(() => testAllNetworkInfo(false)) + // Restore original apn settings and wifi state. + .then(() => { + if (origApnSettings) { + return setSettings(SETTINGS_KEY_DATA_APN_SETTINGS, origApnSettings); + } + }) + .then(() => ensureWifiEnabled(origWifiEnabled)); +}); diff --git a/dom/system/gonk/tests/marionette/test_data_connection.js b/dom/system/gonk/tests/marionette/test_data_connection.js new file mode 100644 index 000000000..5a53b1e5f --- /dev/null +++ b/dom/system/gonk/tests/marionette/test_data_connection.js @@ -0,0 +1,70 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +MARIONETTE_TIMEOUT = 60000; +MARIONETTE_HEAD_JS = "head.js"; + +function setEmulatorAPN() { + let apn = [ + [{"carrier":"T-Mobile US", + "apn":"epc.tmobile.com", + "mmsc":"http://mms.msg.eng.t-mobile.com/mms/wapenc", + "types":["default","supl","mms","ims","dun", "fota"]}] + ]; + + return setSettings(SETTINGS_KEY_DATA_APN_SETTINGS, apn); +} + +// Test initial State +function testInitialState() { + log("= testInitialState ="); + + // Data should be off before starting any test. + return getSettings(SETTINGS_KEY_DATA_ENABLED) + .then(value => { + is(value, false, "Data must be off"); + }); +} + +// Test default data Connection +function testDefaultDataConnection() { + log("= testDefaultDataConnection ="); + + // Enable default data + return setDataEnabledAndWait(true) + // Disable default data + .then(() => setDataEnabledAndWait(false)); +} + +// Test non default data connection +function testNonDefaultDataConnection() { + log("= testNonDefaultDataConnection ="); + + function doTestNonDefaultDataConnection(type) { + log("doTestNonDefaultDataConnection: " + type); + + return setupDataCallAndWait(type) + .then(() => deactivateDataCallAndWait(type)); + } + + let currentApn; + return getSettings(SETTINGS_KEY_DATA_APN_SETTINGS) + .then(value => { + currentApn = value; + }) + .then(setEmulatorAPN) + .then(() => doTestNonDefaultDataConnection(NETWORK_TYPE_MOBILE_MMS)) + .then(() => doTestNonDefaultDataConnection(NETWORK_TYPE_MOBILE_SUPL)) + .then(() => doTestNonDefaultDataConnection(NETWORK_TYPE_MOBILE_IMS)) + .then(() => doTestNonDefaultDataConnection(NETWORK_TYPE_MOBILE_DUN)) + .then(() => doTestNonDefaultDataConnection(NETWORK_TYPE_MOBILE_FOTA)) + // Restore APN settings + .then(() => setSettings(SETTINGS_KEY_DATA_APN_SETTINGS, currentApn)); +} + +// Start test +startTestBase(function() { + return testInitialState() + .then(() => testDefaultDataConnection()) + .then(() => testNonDefaultDataConnection()); +}); diff --git a/dom/system/gonk/tests/marionette/test_data_connection_proxy.js b/dom/system/gonk/tests/marionette/test_data_connection_proxy.js new file mode 100644 index 000000000..a99187538 --- /dev/null +++ b/dom/system/gonk/tests/marionette/test_data_connection_proxy.js @@ -0,0 +1,99 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +MARIONETTE_TIMEOUT = 60000; +MARIONETTE_HEAD_JS = "head.js"; + +const HTTP_PROXY = "10.0.2.200"; +const HTTP_PROXY_PORT = "8080"; +const MANUAL_PROXY_CONFIGURATION = 1; + +// Test initial State +function verifyInitialState() { + log("= verifyInitialState ="); + + // Data should be off before starting any test. + return getSettings(SETTINGS_KEY_DATA_ENABLED) + .then(value => { + is(value, false, "Data must be off"); + }); +} + +function setTestApn() { + let apn = [ + [ {"carrier": "T-Mobile US", + "apn": "epc.tmobile.com", + "proxy": HTTP_PROXY, + "port": HTTP_PROXY_PORT, + "mmsc": "http://mms.msg.eng.t-mobile.com/mms/wapenc", + "types": ["default","supl","mms"]} ] + ]; + + return setSettings(SETTINGS_KEY_DATA_APN_SETTINGS, apn); +} + +function waitForHttpProxyVerified(aShouldBeSet) { + let TIME_OUT_VALUE = 20000; + + return new Promise(function(aResolve, aReject) { + try { + waitFor(aResolve, () => { + let proxyType = SpecialPowers.getIntPref("network.proxy.type"); + let httpProxy = SpecialPowers.getCharPref("network.proxy.http"); + let sslProxy = SpecialPowers.getCharPref("network.proxy.ssl"); + let httpProxyPort = SpecialPowers.getIntPref("network.proxy.http_port"); + let sslProxyPort = SpecialPowers.getIntPref("network.proxy.ssl_port"); + + if ((aShouldBeSet && + proxyType == MANUAL_PROXY_CONFIGURATION && + httpProxy == HTTP_PROXY && + sslProxy == HTTP_PROXY && + httpProxyPort == HTTP_PROXY_PORT && + sslProxyPort == HTTP_PROXY_PORT) || + (!aShouldBeSet && proxyType != MANUAL_PROXY_CONFIGURATION && + !httpProxy && !sslProxy && !httpProxyPort && !sslProxyPort)) { + return true; + } + + return false; + }, TIME_OUT_VALUE); + } catch(aError) { + // Timed out. + aReject(aError); + } + }); +} + +function testDefaultDataHttpProxy() { + log("= testDefaultDataHttpProxy ="); + + return setDataEnabledAndWait(true) + .then(() => waitForHttpProxyVerified(true)) + .then(() => setDataEnabledAndWait(false)) + .then(() => waitForHttpProxyVerified(false)); +} + +function testNonDefaultDataHttpProxy(aType) { + log("= testNonDefaultDataHttpProxy - " + aType + " ="); + + return setupDataCallAndWait(aType) + // Http proxy should not be set for non-default data connections. + .then(() => waitForHttpProxyVerified(false)) + .then(() => deactivateDataCallAndWait(aType)); +} + +// Start test +startTestBase(function() { + let origApnSettings; + return verifyInitialState() + .then(() => getSettings(SETTINGS_KEY_DATA_APN_SETTINGS)) + .then(value => { + origApnSettings = value; + }) + .then(() => setTestApn()) + .then(() => testDefaultDataHttpProxy()) + .then(() => testNonDefaultDataHttpProxy(NETWORK_TYPE_MOBILE_MMS)) + .then(() => testNonDefaultDataHttpProxy(NETWORK_TYPE_MOBILE_SUPL)) + // Restore APN settings + .then(() => setSettings(SETTINGS_KEY_DATA_APN_SETTINGS, origApnSettings)); +}); diff --git a/dom/system/gonk/tests/marionette/test_dsds_numRadioInterfaces.js b/dom/system/gonk/tests/marionette/test_dsds_numRadioInterfaces.js new file mode 100644 index 000000000..e178b8b65 --- /dev/null +++ b/dom/system/gonk/tests/marionette/test_dsds_numRadioInterfaces.js @@ -0,0 +1,43 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +MARIONETTE_TIMEOUT = 60000; +MARIONETTE_CONTEXT = "chrome"; + +Cu.import("resource://gre/modules/Promise.jsm"); +Cu.import("resource://gre/modules/systemlibs.js"); + +const NS_RIL_CONTRACTID = "@mozilla.org/ril;1"; + +const PROP_RO_MOZ_RIL_NUMCLIENTS = "ro.moz.ril.numclients"; + +const PREF_RIL_NUM_RADIO_INTERFACES = "ril.numRadioInterfaces"; + +ok(libcutils, "libcutils is available"); + +var propNum = (function() { + try { + let numString = libcutils.property_get(PROP_RO_MOZ_RIL_NUMCLIENTS, "1"); + let num = parseInt(numString, 10); + if (num >= 0) { + return num; + } + } catch (e) {} +})(); + +log("Retrieved '" + PROP_RO_MOZ_RIL_NUMCLIENTS + "' = " + propNum); +ok(propNum, PROP_RO_MOZ_RIL_NUMCLIENTS); + +var prefNum = Services.prefs.getIntPref(PREF_RIL_NUM_RADIO_INTERFACES); +log("Retrieved '" + PREF_RIL_NUM_RADIO_INTERFACES + "' = " + prefNum); + +var ril = Cc[NS_RIL_CONTRACTID].getService(Ci.nsIRadioInterfaceLayer); +ok(ril, "ril.constructor is " + ril.constructor); + +var ifaceNum = ril.numRadioInterfaces; +log("Retrieved 'nsIRadioInterfaceLayer.numRadioInterfaces' = " + ifaceNum); + +is(propNum, prefNum); +is(propNum, ifaceNum); + +finish(); diff --git a/dom/system/gonk/tests/marionette/test_fakevolume.js b/dom/system/gonk/tests/marionette/test_fakevolume.js new file mode 100644 index 000000000..173f9ac11 --- /dev/null +++ b/dom/system/gonk/tests/marionette/test_fakevolume.js @@ -0,0 +1,25 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +MARIONETTE_TIMEOUT = 10000; + +var Cc = SpecialPowers.Cc; +var Ci = SpecialPowers.Ci; + +var volumeService = Cc["@mozilla.org/telephony/volume-service;1"].getService(Ci.nsIVolumeService); +ok(volumeService, "Should have volume service"); + +var volName = "fake"; +var mountPoint = "/data/fake/storage"; +volumeService.createFakeVolume(volName, mountPoint); + +var vol = volumeService.getVolumeByName(volName); +ok(vol, "volume shouldn't be null"); + +is(volName, vol.name, "name"); +is(mountPoint, vol.mountPoint, "moutnPoint"); +is(Ci.nsIVolume.STATE_MOUNTED, vol.state, "state"); + +ok(vol.mountGeneration > 0, "mount generation should not be zero"); + +finish(); diff --git a/dom/system/gonk/tests/marionette/test_geolocation.js b/dom/system/gonk/tests/marionette/test_geolocation.js new file mode 100644 index 000000000..201c8b3e3 --- /dev/null +++ b/dom/system/gonk/tests/marionette/test_geolocation.js @@ -0,0 +1,117 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +MARIONETTE_TIMEOUT = 10000; + +var geolocation = window.navigator.geolocation; +ok(geolocation); + +var sample = []; +var result = []; +var wpid; + +/** + * Grant special power to get the geolocation + */ +SpecialPowers.addPermission("geolocation", true, document); + +/** + * Disable wifi geolocation provider + */ +wifiUri = SpecialPowers.getCharPref("geo.wifi.uri"); +SpecialPowers.setCharPref("geo.wifi.uri", "http://mochi.test:8888/tests/dom/tests/mochitest/geolocation/network_geolocation.sjs?action=stop-responding"); + +/** + * Helper that compares the geolocation against the web API. + */ +function verifyLocation() { + + log("Sample:" + sample.join(',')); + log("Result:" + result.join(',')); + + for (i in sample) { + is(sample.pop(), result.pop()); + } + + window.setTimeout(cleanup, 0); +} + +/** + * Test story begins here. + */ +function setup() { + log("Providing initial setup: set geographic position watcher."); + + + wpid = geolocation.watchPosition(function(position) { + log("Position changes: (" + position.coords.latitude + "/" + position.coords.longitude + ")"); + result.push(""+position.coords.latitude + "/" + position.coords.longitude); + }); + + lat = 0; + lon = 0; + + cmd = "geo fix " + lon + " " + lat; + sample.push(lat+"/"+lon); + + runEmulatorCmd(cmd, function(result) { + window.setTimeout(movePosition_1, 0); + }); +} + +function movePosition_1() { + log("Geolocation changes. Move to Position 1."); + + lat = 25; + lon = 121.56499833333334; + + cmd = "geo fix " + lon + " " + lat; + sample.push(lat+"/"+lon); + + runEmulatorCmd(cmd, function(result) { + window.setTimeout(movePosition_2, 0); + }); +} + +function movePosition_2() { + log("Geolocation changes to a negative longitude. Move to Position 2."); + + lat = 37.393; + lon = -122.08199833333335; + + cmd = "geo fix " + lon + " " + lat; + sample.push(lat+"/"+lon); + + runEmulatorCmd(cmd, function(result) { + window.setTimeout(movePosition_3, 0); + }); +} + +function movePosition_3() { + log("Geolocation changes with WatchPosition. Move to Position 3."); + + lat = -22; + lon = -43; + + cmd = "geo fix " + lon + " " + lat; + sample.push(lat+"/"+lon); + + geolocation.getCurrentPosition(function(position) { + log("getCurrentPosition: Expected location: ("+lat+"/"+lon+"); Current location: (" + position.coords.latitude + "/" + position.coords.longitude + ")"); + is(lat, position.coords.latitude); + is(lon, position.coords.longitude); + }); + + runEmulatorCmd(cmd, function(result) { + window.setTimeout(verifyLocation, 0); + }); +} + +function cleanup() { + geolocation.clearWatch(wpid); + SpecialPowers.removePermission("geolocation", document); + SpecialPowers.setCharPref("geo.wifi.uri", wifiUri); + finish(); +} + +setup(); diff --git a/dom/system/gonk/tests/marionette/test_multiple_data_connection.js b/dom/system/gonk/tests/marionette/test_multiple_data_connection.js new file mode 100644 index 000000000..24abd4451 --- /dev/null +++ b/dom/system/gonk/tests/marionette/test_multiple_data_connection.js @@ -0,0 +1,89 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +MARIONETTE_TIMEOUT = 60000; +MARIONETTE_HEAD_JS = "head.js"; + +// Must sync with hardware/ril/reference-ril/reference-ril.c +const MAX_DATA_CONTEXTS = 4; + +function setEmulatorAPN() { + // Use different apn for each network type. + let apn = [[ { "carrier":"T-Mobile US", + "apn":"epc1.tmobile.com", + "types":["default"] }, + { "carrier":"T-Mobile US", + "apn":"epc2.tmobile.com", + "mmsc":"http://mms.msg.eng.t-mobile.com/mms/wapenc", + "types":["mms"] }, + { "carrier":"T-Mobile US", + "apn":"epc3.tmobile.com", + "types":["supl"] }, + { "carrier":"T-Mobile US", + "apn":"epc4.tmobile.com", + "types":["ims"] }, + { "carrier":"T-Mobile US", + "apn":"epc5.tmobile.com", + "types":["dun"] }, + { "carrier":"T-Mobile US", + "apn":"epc6.tmobile.com", + "types":["fota"] }]]; + + return setSettings(SETTINGS_KEY_DATA_APN_SETTINGS, apn); +} + +// Test initial State +function testInitialState() { + log("= testInitialState ="); + + // Data should be off before starting any test. + return getSettings(SETTINGS_KEY_DATA_ENABLED) + .then(value => { + is(value, false, "Data must be off"); + }); +} + +function testSetupConcurrentDataCalls() { + log("= testSetupConcurrentDataCalls ="); + + let promise = Promise.resolve(); + // Skip default mobile type. + for (let i = 1; i < MAX_DATA_CONTEXTS; i++) { + let type = networkTypes[i]; + promise = promise.then(() => setupDataCallAndWait(type)); + } + return promise; +} + +function testDeactivateConcurrentDataCalls() { + log("= testDeactivateConcurrentDataCalls ="); + + let promise = Promise.resolve(); + // Skip default mobile type. + for (let i = 1; i < MAX_DATA_CONTEXTS; i++) { + let type = networkTypes[i]; + promise = promise.then(() => deactivateDataCallAndWait(type)); + } + return promise; +} + +// Start test +startTestBase(function() { + + let origApnSettings; + return testInitialState() + .then(() => getSettings(SETTINGS_KEY_DATA_APN_SETTINGS)) + .then(value => { + origApnSettings = value; + }) + .then(() => setEmulatorAPN()) + .then(() => setDataEnabledAndWait(true)) + .then(() => testSetupConcurrentDataCalls()) + .then(() => testDeactivateConcurrentDataCalls()) + .then(() => setDataEnabledAndWait(false)) + .then(() => { + if (origApnSettings) { + return setSettings(SETTINGS_KEY_DATA_APN_SETTINGS, origApnSettings); + } + }); +}); diff --git a/dom/system/gonk/tests/marionette/test_network_active_changed.js b/dom/system/gonk/tests/marionette/test_network_active_changed.js new file mode 100644 index 000000000..5886f37ed --- /dev/null +++ b/dom/system/gonk/tests/marionette/test_network_active_changed.js @@ -0,0 +1,52 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +MARIONETTE_TIMEOUT = 60000; +MARIONETTE_HEAD_JS = "head.js"; + +var networkManager = + Cc["@mozilla.org/network/manager;1"].getService(Ci.nsINetworkManager); +ok(networkManager, + "networkManager.constructor is " + networkManager.constructor); + +function testInitialState() { + return getSettings(SETTINGS_KEY_DATA_ENABLED) + .then((enabled) => { + is(enabled, false, "data should be off by default"); + is(networkManager.activeNetworkInfo, null, + "networkManager.activeNetworkInfo should be null by default"); + }); +} + +function testActiveNetworkChangedBySwitchingDataCall(aDataCallEnabled) { + log("Test active network by switching dataCallEnabled to " + aDataCallEnabled); + + let promises = []; + promises.push(waitForObserverEvent(TOPIC_NETWORK_ACTIVE_CHANGED)); + promises.push(setSettings(SETTINGS_KEY_DATA_ENABLED, aDataCallEnabled)); + + return Promise.all(promises).then(function(results) { + let subject = results[0]; + + if (aDataCallEnabled) { + ok(subject instanceof Ci.nsINetworkInfo, + "subject should be an instance of nsINetworkInfo"); + ok(subject instanceof Ci.nsIRilNetworkInfo, + "subject should be an instance of nsIRilNetworkInfo"); + is(subject.type, NETWORK_TYPE_MOBILE, + "subject.type should be NETWORK_TYPE_MOBILE"); + } + + is(subject, networkManager.activeNetworkInfo, + "subject should be equal with networkManager.activeNetworkInfo"); + }); +} + +// Start test +startTestBase(function() { + return testInitialState() + // Test active network changed by enabling data call. + .then(() => testActiveNetworkChangedBySwitchingDataCall(true)) + // Test active network changed by disabling data call. + .then(() => testActiveNetworkChangedBySwitchingDataCall(false)); +}); diff --git a/dom/system/gonk/tests/marionette/test_network_interface_list_service.js b/dom/system/gonk/tests/marionette/test_network_interface_list_service.js new file mode 100644 index 000000000..549940fa5 --- /dev/null +++ b/dom/system/gonk/tests/marionette/test_network_interface_list_service.js @@ -0,0 +1,95 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +MARIONETTE_TIMEOUT = 60000; +MARIONETTE_HEAD_JS = "head.js"; + +function getNetworkInfo(aType) { + let networkListService = + Cc["@mozilla.org/network/interface-list-service;1"]. + getService(Ci.nsINetworkInterfaceListService); + // Get all available interfaces + let networkList = networkListService.getDataInterfaceList(0); + + // Try to get nsINetworkInterface for aType. + let numberOfInterface = networkList.getNumberOfInterface(); + for (let i = 0; i < numberOfInterface; i++) { + let info = networkList.getInterfaceInfo(i); + if (info.type === aType) { + return info; + } + } + + return null; +} + +// Test getDataInterfaceList by enabling/disabling mobile data. +function testGetDataInterfaceList(aMobileDataEnabled) { + log("Test getDataInterfaceList with mobile data " + + aMobileDataEnabled ? "enabled" : "disabled"); + + return setDataEnabledAndWait(aMobileDataEnabled) + .then(() => getNetworkInfo(NETWORK_TYPE_MOBILE)) + .then((networkInfo) => { + if (!networkInfo) { + ok(false, "Should get an valid nsINetworkInfo for mobile"); + return; + } + + ok(networkInfo instanceof Ci.nsINetworkInfo, + "networkInfo should be an instance of nsINetworkInfo"); + + let ipAddresses = {}; + let prefixs = {}; + let numOfGateways = {}; + let numOfDnses = {}; + let numOfIpAddresses = networkInfo.getAddresses(ipAddresses, prefixs); + let gateways = networkInfo.getGateways(numOfGateways); + let dnses = networkInfo.getDnses(numOfDnses); + + if (aMobileDataEnabled) { + // Mobile data is enabled. + is(networkInfo.state, NETWORK_STATE_CONNECTED, "check state"); + ok(numOfIpAddresses > 0, "check number of ipAddresses"); + ok(ipAddresses.value.length > 0, "check ipAddresses.length"); + ok(prefixs.value.length > 0, "check prefixs.length"); + ok(numOfGateways.value > 0, "check number of gateways"); + ok(prefixs.value.length > 0, "check prefixs.length"); + ok(gateways.length > 0, "check gateways.length"); + ok(numOfDnses.value > 0, "check number of dnses"); + ok(dnses.length > 0, "check dnses.length"); + } else { + // Mobile data is disabled. + is(networkInfo.state, NETWORK_STATE_DISCONNECTED, "check state"); + is(numOfIpAddresses, 0, "check number of ipAddresses"); + is(ipAddresses.value.length, 0, "check ipAddresses.length"); + is(prefixs.value.length, 0, "check prefixs.length"); + is(numOfGateways.value, 0, "check number of gateways"); + is(prefixs.value.length, 0, "check prefixs.length"); + is(gateways.length, 0, "check gateways.length"); + is(numOfDnses.value, 0, "check number of dnses"); + is(dnses.length, 0, "check dnses.length"); + } + }); +} + +// Start test +startTestBase(function() { + return Promise.resolve() + // Test initial State + .then(() => { + log("Test initial state"); + + // Data should be off before starting any test. + return getSettings(SETTINGS_KEY_DATA_ENABLED) + .then(value => { + is(value, false, "Mobile data must be off"); + }); + }) + + // Test getDataInterfaceList with mobile data enabled. + .then(() => testGetDataInterfaceList(true)) + + // Test getDataInterfaceList with mobile data disabled. + .then(() => testGetDataInterfaceList(false)); +}); diff --git a/dom/system/gonk/tests/marionette/test_network_interface_mtu.js b/dom/system/gonk/tests/marionette/test_network_interface_mtu.js new file mode 100644 index 000000000..679efe2ed --- /dev/null +++ b/dom/system/gonk/tests/marionette/test_network_interface_mtu.js @@ -0,0 +1,100 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +MARIONETTE_TIMEOUT = 60000; +MARIONETTE_HEAD_JS = "head.js"; + +const TEST_MTU1 = "1410"; +const TEST_MTU2 = "1440"; + +function setEmulatorAPN() { + let apn = [ + [ { "carrier":"T-Mobile US", + "apn":"epc1.tmobile.com", + "types":["default"], + "mtu": TEST_MTU1 }, + { "carrier":"T-Mobile US", + "apn":"epc2.tmobile.com", + "mmsc":"http://mms.msg.eng.t-mobile.com/mms/wapenc", + "types":["supl","mms","ims","dun", "fota"], + "mtu": TEST_MTU2 } ] + ]; + + return setSettings(SETTINGS_KEY_DATA_APN_SETTINGS, apn); +} + +function verifyInitialState() { + // Data should be off before starting any test. + return getSettings(SETTINGS_KEY_DATA_ENABLED) + .then(value => { + is(value, false, "Data must be off"); + }); +} + +function verifyMtu(aInterfaceName, aMtu) { + return runEmulatorShellCmdSafe(['ip', 'link', 'show', 'dev', aInterfaceName]) + .then(aLines => { + // Sample output: + // + // 4: rmnet0: <BROADCAST,MULTICAST> mtu 1410 qdisc pfifo_fast state DOWN mode DEFAULT qlen 1000 + // link/ether 52:54:00:12:34:58 brd ff:ff:ff:ff:ff:ff + // + let mtu; + aLines.some(function (aLine) { + let tokens = aLine.trim().split(/\s+/); + let mtuIndex = tokens.indexOf('mtu'); + if (mtuIndex < 0 || mtuIndex + 1 >= tokens.length) { + return false; + } + + mtu = tokens[mtuIndex + 1]; + return true; + }); + + is(mtu, aMtu, aInterfaceName + "'s mtu."); + }); +} + +function testDefaultDataCallMtu() { + log("= testDefaultDataCallMtu ="); + + return setDataEnabledAndWait(true) + .then(aNetworkInfo => verifyMtu(aNetworkInfo.name, TEST_MTU1)) + .then(() => setDataEnabledAndWait(false)); +} + +function testNonDefaultDataCallMtu() { + log("= testNonDefaultDataCallMtu ="); + + function doTestNonDefaultDataCallMtu(aType) { + log("doTestNonDefaultDataCallMtu: " + aType); + + return setupDataCallAndWait(aType) + .then(aNetworkInfo => verifyMtu(aNetworkInfo.name, TEST_MTU2)) + .then(() => deactivateDataCallAndWait(aType)); + } + + return doTestNonDefaultDataCallMtu(NETWORK_TYPE_MOBILE_MMS) + .then(() => doTestNonDefaultDataCallMtu(NETWORK_TYPE_MOBILE_SUPL)) + .then(() => doTestNonDefaultDataCallMtu(NETWORK_TYPE_MOBILE_IMS)) + .then(() => doTestNonDefaultDataCallMtu(NETWORK_TYPE_MOBILE_DUN)) + .then(() => doTestNonDefaultDataCallMtu(NETWORK_TYPE_MOBILE_FOTA)); +} + +// Start test +startTestBase(function() { + let origApnSettings; + return verifyInitialState() + .then(() => getSettings(SETTINGS_KEY_DATA_APN_SETTINGS)) + .then(value => { + origApnSettings = value; + }) + .then(() => setEmulatorAPN()) + .then(() => testDefaultDataCallMtu()) + .then(() => testNonDefaultDataCallMtu()) + .then(() => { + if (origApnSettings) { + return setSettings(SETTINGS_KEY_DATA_APN_SETTINGS, origApnSettings); + } + }); +}); diff --git a/dom/system/gonk/tests/marionette/test_ril_code_quality.py b/dom/system/gonk/tests/marionette/test_ril_code_quality.py new file mode 100644 index 000000000..d741d8a2e --- /dev/null +++ b/dom/system/gonk/tests/marionette/test_ril_code_quality.py @@ -0,0 +1,371 @@ +""" +The test performs the static code analysis check by JSHint. + +Target js files: +- RadioInterfaceLayer.js +- ril_worker.js +- ril_consts.js + +If the js file contains the line of 'importScript()' (Ex: ril_worker.js), the +test will perform a special merge step before excuting JSHint. + +Ex: Script A +-------------------------------- +importScripts('Script B') +... +-------------------------------- + +We merge these two scripts into one by the following way. + +-------------------------------- +[Script B (ex: ril_consts.js)] +(function(){ [Script A (ex: ril_worker.js)] +})(); +-------------------------------- + +Script A (ril_worker.js) runs global strict mode. +Script B (ril_consts.js) not. + +The above merge way ensures the correct scope of 'strict mode.' +""" + +import bisect +import inspect +import os +import os.path +import re +import unicodedata + +from marionette_harness import MarionetteTestCase + + +class StringUtility: + + """A collection of some string utilities.""" + + @staticmethod + def find_match_lines(lines, pattern): + """Return a list of lines that contains given pattern.""" + return [line for line in lines if pattern in line] + + @staticmethod + def remove_non_ascii(data): + """Remove non ascii characters in data and return it as new string.""" + if type(data).__name__ == 'unicode': + data = unicodedata.normalize( + 'NFKD', data).encode('ascii', 'ignore') + return data + + @staticmethod + def auto_close(lines): + """Ensure every line ends with '\n'.""" + if lines and not lines[-1].endswith('\n'): + lines[-1] += '\n' + return lines + + @staticmethod + def auto_wrap_strict_mode(lines): + """Wrap by function scope if lines contain 'use strict'.""" + if StringUtility.find_match_lines(lines, 'use strict'): + lines[0] = '(function(){' + lines[0] + lines.append('})();\n') + return lines + + @staticmethod + def get_imported_list(lines): + """Get a list of imported items.""" + return [item + for line in StringUtility.find_match_lines(lines, 'importScripts') + for item in StringUtility._get_imported_list_from_line(line)] + + @staticmethod + def _get_imported_list_from_line(line): + """Extract all items from 'importScripts(...)'. + + importScripts("ril_consts.js", "systemlibs.js") + => ['ril_consts', 'systemlibs.js'] + + """ + pattern = re.compile(r'\s*importScripts\((.*)\)') + m = pattern.match(line) + if not m: + raise Exception('Parse importScripts error.') + return [name.translate(None, '\' "') for name in m.group(1).split(',')] + + +class ResourceUriFileReader: + + """Handle the process of reading the source code from system.""" + + URI_PREFIX = 'resource://gre/' + URI_PATH = { + 'RadioInterfaceLayer.js': 'components/RadioInterfaceLayer.js', + 'ril_worker.js': 'modules/ril_worker.js', + 'ril_consts.js': 'modules/ril_consts.js', + 'systemlibs.js': 'modules/systemlibs.js', + 'worker_buf.js': 'modules/workers/worker_buf.js', + } + + CODE_OPEN_CHANNEL_BY_URI = ''' + var Cc = Components.classes; + var Ci = Components.interfaces; + var ios = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService); + var secMan = Cc["@mozilla.org/scriptsecuritymanager;1"].getService(Ci.nsIScriptSecurityManager); + global.uri = '%(uri)s'; + global.channel = ios.newChannel2(global.uri, + null, + null, + null, // aLoadingNode + secMan.getSystemPrincipal(), + null, // aTriggeringPrincipal + Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL, + Ci.nsIContentPolicy.TYPE_OTHER); + ''' + + CODE_GET_SPEC = ''' + return global.channel.URI.spec; + ''' + + CODE_READ_CONTENT = ''' + var Cc = Components.classes; + var Ci = Components.interfaces; + + var zipReader = Cc["@mozilla.org/libjar/zip-reader;1"].createInstance(Ci.nsIZipReader); + var inputStream = Cc["@mozilla.org/scriptableinputstream;1"].createInstance(Ci.nsIScriptableInputStream); + + var jaruri = global.channel.URI.QueryInterface(Ci.nsIJARURI); + var file = jaruri.JARFile.QueryInterface(Ci.nsIFileURL).file; + var entry = jaruri.JAREntry; + zipReader.open(file); + inputStream.init(zipReader.getInputStream(entry)); + var content = inputStream.read(inputStream.available()); + inputStream.close(); + zipReader.close(); + return content; + ''' + + @classmethod + def get_uri(cls, filename): + """Convert filename to URI in system.""" + if filename.startswith(cls.URI_PREFIX): + return filename + else: + return cls.URI_PREFIX + cls.URI_PATH[filename] + + def __init__(self, marionette): + self.runjs = lambda x: marionette.execute_script(x, + new_sandbox=False, + sandbox='system') + + def read_file(self, filename): + """Read file and return the contents as string.""" + content = self._read_uri(self.get_uri(filename)) + content = content.replace('"use strict";', '') + return StringUtility.remove_non_ascii(content) + + def _read_uri(self, uri): + """Read URI in system and return the contents as string.""" + # Open the uri as a channel. + self.runjs(self.CODE_OPEN_CHANNEL_BY_URI % {'uri': uri}) + + # Make sure spec is a jar uri, and not recursive. + # Ex: 'jar:file:///system/b2g/omni.ja!/modules/ril_worker.js' + # + # For simplicity, we don't handle other special cases in this test. + # If B2G build system changes in the future, such as put the jar in + # another jar, the test case will fail. + spec = self.runjs(self.CODE_GET_SPEC) + if (not spec.startswith('jar:file://')) or (spec.count('jar:') != 1): + raise Exception('URI resolve error') + + # Read the content from channel. + content = self.runjs(self.CODE_READ_CONTENT) + return content + + +class JSHintEngine: + + """Invoke jshint script on system.""" + + CODE_INIT_JSHINT = ''' + %(script)s; + global.JSHINT = JSHINT; + global.options = JSON.parse(%(config_string)s); + global.globals = global.options.globals; + delete global.options.globals; + ''' + + CODE_RUN_JSHINT = ''' + global.script = %(code)s; + return global.JSHINT(global.script, global.options, global.globals); + ''' + + CODE_GET_JSHINT_ERROR = ''' + return global.JSHINT.errors; + ''' + + def __init__(self, marionette, script, config): + # Remove single line comment in config. + config = '\n'.join([line.partition('//')[0] + for line in config.splitlines()]) + + # Set global (JSHINT, options, global) in js environment. + self.runjs = lambda x: marionette.execute_script(x, + new_sandbox=False, + sandbox='system') + self.runjs(self.CODE_INIT_JSHINT % + {'script': script, 'config_string': repr(config)}) + + def run(self, code, filename=''): + """Excute JShint check for the given code.""" + check_pass = self.runjs(self.CODE_RUN_JSHINT % {'code': repr(code)}) + errors = self.runjs(self.CODE_GET_JSHINT_ERROR) + return check_pass, self._get_error_messages(errors, filename) + + def _get_error_messages(self, errors, filename=''): + """ + Convert an error object to a list of readable string. + + [{"a": null, "c": null, "code": "W033", "d": null, "character": 6, + "evidence": "var a", "raw": "Missing semicolon.", + "reason": "Missing semicolon.", "b": null, "scope": "(main)", "line": 1, + "id": "(error)"}] + => line 1, col 6, Missing semicolon. + + """ + LINE, COL, REASON = u'line', u'character', u'reason' + return ["%s: line %s, col %s, %s" % + (filename, error[LINE], error[COL], error[REASON]) + for error in errors if error] + + +class Linter: + + """Handle the linting related process.""" + + def __init__(self, code_reader, jshint, reporter=None): + """Set the linter with code_reader, jshint engine, and reporter. + + Should have following functionality. + - code_reader.read_file(filename) + - jshint.run(code, filename) + - reporter([...]) + + """ + self.code_reader = code_reader + self.jshint = jshint + if reporter is None: + self.reporter = lambda x: '\n'.join(x) + else: + self.reporter = reporter + + def lint_file(self, filename): + """Lint the file and return (pass, error_message).""" + # Get code contents. + code = self.code_reader.read_file(filename) + lines = code.splitlines() + import_list = StringUtility.get_imported_list(lines) + if not import_list: + check_pass, error_message = self.jshint.run(code, filename) + else: + newlines, info = self._merge_multiple_codes(filename, import_list) + # Each line of |newlines| contains '\n'. + check_pass, error_message = self.jshint.run(''.join(newlines)) + error_message = self._convert_merged_result(error_message, info) + # Only keep errors for this file. + error_message = [line for line in error_message + if line.startswith(filename)] + check_pass = (len(error_message) == 0) + return check_pass, self.reporter(error_message) + + def _merge_multiple_codes(self, filename, import_list): + """Merge multiple codes from filename and import_list.""" + dirname, filename = os.path.split(filename) + dst_line = 1 + dst_results = [] + info = [] + + # Put the imported script first, and then the original script. + for f in import_list + [filename]: + filepath = os.path.join(dirname, f) + + # Maintain a mapping table. + # New line number after merge => original file and line number. + info.append((dst_line, filepath, 1)) + try: + code = self.code_reader.read_file(filepath) + lines = code.splitlines(True) # Keep '\n'. + src_results = StringUtility.auto_wrap_strict_mode( + StringUtility.auto_close(lines)) + dst_results.extend(src_results) + dst_line += len(src_results) + except: + info.pop() + return dst_results, info + + def _convert_merged_result(self, error_lines, line_info): + pattern = re.compile(r'(.*): line (\d+),(.*)') + start_line = [info[0] for info in line_info] + new_result_lines = [] + for line in error_lines: + m = pattern.match(line) + if not m: + continue + + line_number, remain = int(m.group(2)), m.group(3) + + # [1, 2, 7, 8] + # ^ for 7, pos = 3 + # ^ for 6, pos = 2 + pos = bisect.bisect_right(start_line, line_number) + dst_line, name, src_line = line_info[pos - 1] + real_line_number = line_number - dst_line + src_line + new_result_lines.append( + "%s: line %s,%s" % (name, real_line_number, remain)) + return new_result_lines + + +class TestRILCodeQuality(MarionetteTestCase): + + JSHINT_PATH = 'ril_jshint/jshint.js' + JSHINTRC_PATH = 'ril_jshint/jshintrc' + + def _read_local_file(self, filepath): + """Read file content from local (folder of this test case).""" + test_dir = os.path.dirname(inspect.getfile(TestRILCodeQuality)) + return open(os.path.join(test_dir, filepath)).read() + + def _get_extended_error_message(self, error_message): + return '\n'.join(['See errors below and more information in Bug 880643', + '\n'.join(error_message), + 'See errors above and more information in Bug 880643']) + + def _check(self, filename): + check_pass, error_message = self.linter.lint_file(filename) + self.assertTrue(check_pass, error_message) + + def setUp(self): + MarionetteTestCase.setUp(self) + self.linter = Linter( + ResourceUriFileReader(self.marionette), + JSHintEngine(self.marionette, + self._read_local_file(self.JSHINT_PATH), + self._read_local_file(self.JSHINTRC_PATH)), + self._get_extended_error_message) + + def tearDown(self): + MarionetteTestCase.tearDown(self) + + def test_RadioInterfaceLayer(self): + self._check('RadioInterfaceLayer.js') + + # Bug 936504. Disable the test for 'ril_worker.js'. It sometimes runs very + # slow and causes the timeout fail on try server. + #def test_ril_worker(self): + # self._check('ril_worker.js') + + def test_ril_consts(self): + self._check('ril_consts.js') + + def test_worker_buf(self): + self._check('worker_buf.js') diff --git a/dom/system/gonk/tests/marionette/test_screen_state.js b/dom/system/gonk/tests/marionette/test_screen_state.js new file mode 100644 index 000000000..2281412d5 --- /dev/null +++ b/dom/system/gonk/tests/marionette/test_screen_state.js @@ -0,0 +1,47 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +MARIONETTE_TIMEOUT = 10000; + +var Services = SpecialPowers.Services; + +function testScreenState(on, expected, msg) { + // send event to RadioInterface + Services.obs.notifyObservers(null, 'screen-state-changed', on); + // maybe rild/qemu needs some time to process the event + window.setTimeout(function() { + runEmulatorCmd('gsm report creg', function(result) { + is(result.pop(), 'OK', '\'gsm report creg\' successful'); + ok(result.indexOf(expected) !== -1, msg); + runNextTest(); + })}, 1000); +} + +function testScreenStateDisabled() { + testScreenState('off', '+CREG: 1', 'screen is disabled'); +} + +function testScreenStateEnabled() { + testScreenState('on', '+CREG: 2', 'screen is enabled'); +} + +var tests = [ + testScreenStateDisabled, + testScreenStateEnabled +]; + +function runNextTest() { + let test = tests.shift(); + if (!test) { + cleanUp(); + return; + } + + test(); +} + +function cleanUp() { + finish(); +} + +runNextTest(); diff --git a/dom/system/gonk/tests/marionette/test_timezone_changes.js b/dom/system/gonk/tests/marionette/test_timezone_changes.js new file mode 100644 index 000000000..11dbaec5a --- /dev/null +++ b/dom/system/gonk/tests/marionette/test_timezone_changes.js @@ -0,0 +1,135 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +MARIONETTE_TIMEOUT = 60000; +MARIONETTE_HEAD_JS = 'head.js'; + +function init() { + let promises = []; + + /* + * The initial timezone of the emulator could be anywhere, depends the host + * machine. Ensure resetting it to UTC before testing. + */ + promises.push(runEmulatorCmdSafe('gsm timezone 0')); + promises.push(new Promise((aResolve, aReject) => { + waitFor(aResolve, () => { + return new Date().getTimezoneOffset() === 0; + }); + })); + + return Promise.all(promises); +} + +function paddingZeros(aNumber, aLength) { + let str = '' + aNumber; + while (str.length < aLength) { + str = '0' + str; + } + + return str; +} + +function verifyDate(aTestDate, aUTCOffsetDate) { + // Verify basic properties. + is(aUTCOffsetDate.getUTCFullYear(), aTestDate.getFullYear(), 'year'); + is(aUTCOffsetDate.getUTCMonth(), aTestDate.getMonth(), 'month'); + is(aUTCOffsetDate.getUTCDate(), aTestDate.getDate(), 'date'); + is(aUTCOffsetDate.getUTCHours(), aTestDate.getHours(), 'hours'); + is(aUTCOffsetDate.getUTCMinutes(), aTestDate.getMinutes(), 'minutes'); + is(aUTCOffsetDate.getUTCMilliseconds(), aTestDate.getMilliseconds(), 'milliseconds'); + + // Ensure toLocaleString also uses correct timezone. + // It uses ICU's timezone instead of the offset calculated from gecko prtime. + let expectedDateString = + paddingZeros(aUTCOffsetDate.getUTCMonth() + 1, 2) + '/' + + paddingZeros(aUTCOffsetDate.getUTCDate(), 2); + let dateString = aTestDate.toLocaleString('en-US', { + month: '2-digit', + day: '2-digit', + }); + let expectedTimeString = + paddingZeros(aUTCOffsetDate.getUTCHours(), 2) + ':' + + paddingZeros(aUTCOffsetDate.getUTCMinutes(), 2); + let timeString = aTestDate.toLocaleString('en-US', { + hour12: false, + hour: '2-digit', + minute: '2-digit' + }); + + is(expectedDateString, dateString, 'dateString'); + is(expectedTimeString, timeString, 'timeString'); +} + +function waitForTimezoneUpdate(aTzOffset, + aTestDateInMillis = 86400000, // Use 'UTC 00:00:00, 2nd of Jan, 1970' by default. + aTransTzOffset, aTransTestDateInMillis) { + return new Promise(function(aResolve, aReject) { + window.addEventListener('moztimechange', function onevent(aEvent) { + // Since there could be multiple duplicate moztimechange event, wait until + // timezone is actually changed to expected value before removing the + // listener. + let testDate = new Date(aTestDateInMillis); + if (testDate.getTimezoneOffset() === aTzOffset) { + window.removeEventListener('moztimechange', onevent); + + // The UTC time of offsetDate is the same as the expected local time of + // testDate. We'll use it to verify the values. + let offsetDate = new Date(aTestDateInMillis - aTzOffset * 60 * 1000); + verifyDate(testDate, offsetDate); + + // Verify transition time if given. + if (aTransTzOffset !== undefined) { + testDate = new Date(aTransTestDateInMillis); + is(testDate.getTimezoneOffset(), aTransTzOffset); + + // Verify transition date. + offsetDate = new Date(aTransTestDateInMillis - aTransTzOffset * 60 * 1000); + verifyDate(testDate, offsetDate); + } + + aResolve(aEvent); + } + }); + }); +} + +function testChangeNitzTimezone(aTzDiff) { + let promises = []; + + // aTzOffset should be the expected value for getTimezoneOffset(). + // Note that getTimezoneOffset() is not so straightforward, + // it values (UTC - localtime), so UTC+08:00 returns -480. + promises.push(waitForTimezoneUpdate(-aTzDiff * 15)); + promises.push(runEmulatorCmdSafe('gsm timezone ' + aTzDiff)); + + return Promise.all(promises); +} + +function testChangeOlsonTimezone(aOlsonTz, aTzOffset, aTestDateInMillis, + aTransTzOffset, aTransTestDateInMillis) { + let promises = []; + + promises.push(waitForTimezoneUpdate(aTzOffset, aTestDateInMillis, + aTransTzOffset, aTransTestDateInMillis)); + promises.push(setSettings('time.timezone', aOlsonTz)); + + return Promise.all(promises); +} + +// Start test +startTestBase(function() { + return init() + .then(() => testChangeNitzTimezone(36)) // UTC+09:00 + .then(() => testChangeOlsonTimezone('America/New_York', + 300, 1446357600000, // 2015/11/01 02:00 UTC-04:00 => 01:00 UTC-05:00 (EST) + 240, 1425798000000)) // 2015/03/08 02:00 UTC-05:00 => 03:00 UTC-04:00 (EDT) + .then(() => testChangeNitzTimezone(-22)) // UTC-05:30 + .then(() => testChangeNitzTimezone(51)) // UTC+12:45 + .then(() => testChangeOlsonTimezone('Australia/Adelaide', + -570, 1428165000000, // 2015/04/05 03:00 UTC+10:30 => 02:00 UTC+09:30 (ACST) + -630, 1443889800000)) // 2015/10/04 02:00 UTC+09:30 => 03:00 UTC+10:30 (ACDT) + .then(() => testChangeNitzTimezone(-38)) // UTC-09:30 + .then(() => testChangeNitzTimezone(0)) // UTC + .then(() => runEmulatorCmdSafe('gsm timezone auto')); +}); diff --git a/dom/system/gonk/tests/test_ril_system_messenger.js b/dom/system/gonk/tests/test_ril_system_messenger.js new file mode 100644 index 000000000..a588d0ddb --- /dev/null +++ b/dom/system/gonk/tests/test_ril_system_messenger.js @@ -0,0 +1,1187 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +var {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components; + +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); + +var RIL = {}; +Cu.import("resource://gre/modules/ril_consts.js", RIL); + +XPCOMUtils.defineLazyServiceGetter(this, "gStkCmdFactory", + "@mozilla.org/icc/stkcmdfactory;1", + "nsIStkCmdFactory"); + +/** + * Name space for RILSystemMessenger.jsm. Only initialized after first call to + * newRILSystemMessenger. + */ +var RSM; + +var gReceivedMsgType = null; +var gReceivedMessage = null; + +/** + * Create a new RILSystemMessenger instance. + * + * @return a RILSystemMessenger instance. + */ +function newRILSystemMessenger() { + if (!RSM) { + RSM = Cu.import("resource://gre/modules/RILSystemMessenger.jsm", {}); + equal(typeof RSM.RILSystemMessenger, "function", "RSM.RILSystemMessenger"); + } + + let rsm = new RSM.RILSystemMessenger(); + rsm.broadcastMessage = (aType, aMessage) => { + gReceivedMsgType = aType; + gReceivedMessage = aMessage; + }; + + rsm.createCommandMessage = (aStkProactiveCmd) => { + return gStkCmdFactory.createCommandMessage(aStkProactiveCmd); + }; + + return rsm; +} + +function equal_received_system_message(aType, aMessage) { + equal(aType, gReceivedMsgType); + deepEqual(aMessage, gReceivedMessage); + gReceivedMsgType = null; + gReceivedMessage = null; +} + +/** + * Verify that each nsIXxxMessenger could be retrieved. + */ +function run_test() { + let telephonyMessenger = Cc["@mozilla.org/ril/system-messenger-helper;1"] + .getService(Ci.nsITelephonyMessenger); + + let smsMessenger = Cc["@mozilla.org/ril/system-messenger-helper;1"] + .getService(Ci.nsISmsMessenger); + + let cellbroadcastMessenger = Cc["@mozilla.org/ril/system-messenger-helper;1"] + .getService(Ci.nsICellbroadcastMessenger); + + let mobileConnectionMessenger = Cc["@mozilla.org/ril/system-messenger-helper;1"] + .getService(Ci.nsIMobileConnectionMessenger); + + let iccMessenger = Cc["@mozilla.org/ril/system-messenger-helper;1"] + .getService(Ci.nsIIccMessenger); + + ok(telephonyMessenger !== null, "Get TelephonyMessenger."); + ok(smsMessenger != null, "Get SmsMessenger."); + ok(cellbroadcastMessenger != null, "Get CellbroadcastMessenger."); + ok(mobileConnectionMessenger != null, "Get MobileConnectionMessenger."); + ok(iccMessenger != null, "Get IccMessenger."); + + run_next_test(); +} + +/** + * Verify RILSystemMessenger.notifyNewCall() + */ +add_test(function test_telephony_messenger_notify_new_call() { + let messenger = newRILSystemMessenger(); + + messenger.notifyNewCall(); + equal_received_system_message("telephony-new-call", {}); + + run_next_test(); +}); + +/** + * Verify RILSystemMessenger.notifyCallEnded() + */ +add_test(function test_telephony_messenger_notify_call_ended() { + let messenger = newRILSystemMessenger(); + + messenger.notifyCallEnded(1, + "+0987654321", + null, + true, + 500, + false, + true); + + equal_received_system_message("telephony-call-ended", { + serviceId: 1, + number: "+0987654321", + emergency: true, + duration: 500, + direction: "incoming", + hangUpLocal: true + }); + + // Verify 'optional' parameter of secondNumber. + messenger.notifyCallEnded(1, + "+0987654321", + "+1234567890", + true, + 500, + true, + false); + + equal_received_system_message("telephony-call-ended", { + serviceId: 1, + number: "+0987654321", + emergency: true, + duration: 500, + direction: "outgoing", + hangUpLocal: false, + secondNumber: "+1234567890" + }); + + run_next_test(); +}); + +/** + * Verify RILSystemMessenger.notifySms() + */ +add_test(function test_sms_messenger_notify_sms() { + let messenger = newRILSystemMessenger(); + let timestamp = Date.now(); + let sentTimestamp = timestamp + 100; + let deliveryTimestamp = sentTimestamp + 100; + + // Verify 'sms-received' system message. + messenger.notifySms(Ci.nsISmsMessenger.NOTIFICATION_TYPE_RECEIVED, + 1, + 2, + "99887766554433221100", + Ci.nsISmsService.DELIVERY_TYPE_RECEIVED, + Ci.nsISmsService.DELIVERY_STATUS_TYPE_SUCCESS, + "+0987654321", + null, + "Incoming message", + Ci.nsISmsService.MESSAGE_CLASS_TYPE_CLASS_2, + timestamp, + sentTimestamp, + 0, + false); + + equal_received_system_message("sms-received", { + iccId: "99887766554433221100", + type: "sms", + id: 1, + threadId: 2, + delivery: "received", + deliveryStatus: "success", + sender: "+0987654321", + receiver: null, + body: "Incoming message", + messageClass: "class-2", + timestamp: timestamp, + sentTimestamp: sentTimestamp, + deliveryTimestamp: 0, + read: false + }); + + // Verify 'sms-sent' system message. + messenger.notifySms(Ci.nsISmsMessenger.NOTIFICATION_TYPE_SENT, + 3, + 4, + "99887766554433221100", + Ci.nsISmsService.DELIVERY_TYPE_SENT, + Ci.nsISmsService.DELIVERY_STATUS_TYPE_PENDING, + null, + "+0987654321", + "Outgoing message", + Ci.nsISmsService.MESSAGE_CLASS_TYPE_NORMAL, + timestamp, + 0, + 0, + true); + + equal_received_system_message("sms-sent", { + iccId: "99887766554433221100", + type: "sms", + id: 3, + threadId: 4, + delivery: "sent", + deliveryStatus: "pending", + sender: null, + receiver: "+0987654321", + body: "Outgoing message", + messageClass: "normal", + timestamp: timestamp, + sentTimestamp: 0, + deliveryTimestamp: 0, + read: true + }); + + // Verify 'sms-delivery-success' system message. + messenger.notifySms(Ci.nsISmsMessenger.NOTIFICATION_TYPE_DELIVERY_SUCCESS, + 5, + 6, + "99887766554433221100", + Ci.nsISmsService.DELIVERY_TYPE_SENT, + Ci.nsISmsService.DELIVERY_STATUS_TYPE_SUCCESS, + null, + "+0987654321", + "Outgoing message", + Ci.nsISmsService.MESSAGE_CLASS_TYPE_NORMAL, + timestamp, + 0, + deliveryTimestamp, + true); + + equal_received_system_message("sms-delivery-success", { + iccId: "99887766554433221100", + type: "sms", + id: 5, + threadId: 6, + delivery: "sent", + deliveryStatus: "success", + sender: null, + receiver: "+0987654321", + body: "Outgoing message", + messageClass: "normal", + timestamp: timestamp, + sentTimestamp: 0, + deliveryTimestamp: deliveryTimestamp, + read: true + }); + + // Verify 'sms-failed' system message. + messenger.notifySms(Ci.nsISmsMessenger.NOTIFICATION_TYPE_SENT_FAILED, + 7, + 8, + "99887766554433221100", + Ci.nsISmsService.DELIVERY_TYPE_ERROR, + Ci.nsISmsService.DELIVERY_STATUS_TYPE_ERROR, + null, + "+0987654321", + "Outgoing message", + Ci.nsISmsService.MESSAGE_CLASS_TYPE_NORMAL, + timestamp, + 0, + 0, + true); + + equal_received_system_message("sms-failed", { + iccId: "99887766554433221100", + type: "sms", + id: 7, + threadId: 8, + delivery: "error", + deliveryStatus: "error", + sender: null, + receiver: "+0987654321", + body: "Outgoing message", + messageClass: "normal", + timestamp: timestamp, + sentTimestamp: 0, + deliveryTimestamp: 0, + read: true + }); + + // Verify 'sms-delivery-error' system message. + messenger.notifySms(Ci.nsISmsMessenger.NOTIFICATION_TYPE_DELIVERY_ERROR, + 9, + 10, + "99887766554433221100", + Ci.nsISmsService.DELIVERY_TYPE_SENT, + Ci.nsISmsService.DELIVERY_STATUS_TYPE_ERROR, + null, + "+0987654321", + "Outgoing message", + Ci.nsISmsService.MESSAGE_CLASS_TYPE_NORMAL, + timestamp, + 0, + 0, + true); + + equal_received_system_message("sms-delivery-error", { + iccId: "99887766554433221100", + type: "sms", + id: 9, + threadId: 10, + delivery: "sent", + deliveryStatus: "error", + sender: null, + receiver: "+0987654321", + body: "Outgoing message", + messageClass: "normal", + timestamp: timestamp, + sentTimestamp: 0, + deliveryTimestamp: 0, + read: true + }); + + // Verify the protection of invalid nsISmsMessenger.NOTIFICATION_TYPEs. + try { + messenger.notifySms(5, + 1, + 2, + "99887766554433221100", + Ci.nsISmsService.DELIVERY_TYPE_RECEIVED, + Ci.nsISmsService.DELIVERY_STATUS_TYPE_SUCCESS, + "+0987654321", + null, + "Incoming message", + Ci.nsISmsService.MESSAGE_CLASS_TYPE_NORMAL, + timestamp, + sentTimestamp, + 0, + false); + ok(false, "Failed to verify the protection of invalid nsISmsMessenger.NOTIFICATION_TYPE!"); + } catch (e) {} + + run_next_test(); +}); + +/** + * Verify RILSystemMessenger.notifyCbMessageReceived() + */ +add_test(function test_cellbroadcast_messenger_notify_cb_message_received() { + let messenger = newRILSystemMessenger(); + let timestamp = Date.now(); + + // Verify ETWS + messenger.notifyCbMessageReceived(0, + Ci.nsICellBroadcastService.GSM_GEOGRAPHICAL_SCOPE_CELL_IMMEDIATE, + 256, + 4352, + null, + null, + Ci.nsICellBroadcastService.GSM_MESSAGE_CLASS_NORMAL, + timestamp, + Ci.nsICellBroadcastService.CDMA_SERVICE_CATEGORY_INVALID, + true, + Ci.nsICellBroadcastService.GSM_ETWS_WARNING_EARTHQUAKE, + false, + true); + equal_received_system_message("cellbroadcast-received", { + serviceId: 0, + gsmGeographicalScope: "cell-immediate", + messageCode: 256, + messageId: 4352, + language: null, + body: null, + messageClass: "normal", + timestamp: timestamp, + cdmaServiceCategory: null, + etws: { + warningType: "earthquake", + emergencyUserAlert: false, + popup: true + } + }); + + // Verify Normal CB Message + messenger.notifyCbMessageReceived(1, + Ci.nsICellBroadcastService.GSM_GEOGRAPHICAL_SCOPE_PLMN, + 0, + 50, + "en", + "The quick brown fox jumps over the lazy dog", + Ci.nsICellBroadcastService.GSM_MESSAGE_CLASS_NORMAL, + timestamp, + Ci.nsICellBroadcastService.CDMA_SERVICE_CATEGORY_INVALID, + false, + Ci.nsICellBroadcastService.GSM_ETWS_WARNING_INVALID, + false, + false); + equal_received_system_message("cellbroadcast-received", { + serviceId: 1, + gsmGeographicalScope: "plmn", + messageCode: 0, + messageId: 50, + language: "en", + body: "The quick brown fox jumps over the lazy dog", + messageClass: "normal", + timestamp: timestamp, + cdmaServiceCategory: null, + etws: null + }); + + // Verify CB Message with ETWS Info + messenger.notifyCbMessageReceived(0, + Ci.nsICellBroadcastService.GSM_GEOGRAPHICAL_SCOPE_LOCATION_AREA, + 0, + 4354, + "en", + "Earthquake & Tsunami Warning!", + Ci.nsICellBroadcastService.GSM_MESSAGE_CLASS_0, + timestamp, + Ci.nsICellBroadcastService.CDMA_SERVICE_CATEGORY_INVALID, + true, + Ci.nsICellBroadcastService.GSM_ETWS_WARNING_EARTHQUAKE_TSUNAMI, + true, + false); + equal_received_system_message("cellbroadcast-received", { + serviceId: 0, + gsmGeographicalScope: "location-area", + messageCode: 0, + messageId: 4354, + language: "en", + body: "Earthquake & Tsunami Warning!", + messageClass: "class-0", + timestamp: timestamp, + cdmaServiceCategory: null, + etws: { + warningType: "earthquake-tsunami", + emergencyUserAlert: true, + popup: false + } + }); + + // Verify CDMA CB Message + messenger.notifyCbMessageReceived(0, + Ci.nsICellBroadcastService.GSM_GEOGRAPHICAL_SCOPE_INVALID, + 0, + 0, + null, + "CDMA CB Message", + Ci.nsICellBroadcastService.GSM_MESSAGE_CLASS_NORMAL, + timestamp, + 512, + false, + Ci.nsICellBroadcastService.GSM_ETWS_WARNING_INVALID, + false, + false); + equal_received_system_message("cellbroadcast-received", { + serviceId: 0, + gsmGeographicalScope: null, + messageCode: 0, + messageId: 0, + language: null, + body: "CDMA CB Message", + messageClass: "normal", + timestamp: timestamp, + cdmaServiceCategory: 512, + etws: null + }); + + run_next_test(); +}); + +/** + * Verify RILSystemMessenger.notifyUssdReceived() + */ +add_test(function test_mobileconnection_notify_ussd_received() { + let messenger = newRILSystemMessenger(); + + messenger.notifyUssdReceived(0, "USSD Message", false); + + equal_received_system_message("ussd-received", { + serviceId: 0, + message: "USSD Message", + sessionEnded: false + }); + + messenger.notifyUssdReceived(1, "USSD Message", true); + + equal_received_system_message("ussd-received", { + serviceId: 1, + message: "USSD Message", + sessionEnded: true + }); + + run_next_test(); +}); + +/** + * Verify RILSystemMessenger.notifyCdmaInfoRecXXX() + */ +add_test(function test_mobileconnection_notify_cdma_info() { + let messenger = newRILSystemMessenger(); + + messenger.notifyCdmaInfoRecDisplay(0, "CDMA Display Info"); + + equal_received_system_message("cdma-info-rec-received", { + clientId: 0, + display: "CDMA Display Info" + }); + + messenger.notifyCdmaInfoRecCalledPartyNumber(1, 1, 2, "+0987654321", 3, 4); + + equal_received_system_message("cdma-info-rec-received", { + clientId: 1, + calledNumber: { + type: 1, + plan: 2, + number: "+0987654321", + pi: 3, + si: 4 + } + }); + + messenger.notifyCdmaInfoRecCallingPartyNumber(0, 5, 6, "+1234567890", 7, 8); + + equal_received_system_message("cdma-info-rec-received", { + clientId: 0, + callingNumber: { + type: 5, + plan: 6, + number: "+1234567890", + pi: 7, + si: 8 + } + }); + + messenger.notifyCdmaInfoRecConnectedPartyNumber(1, 4, 3, "+56473839201", 2, 1); + + equal_received_system_message("cdma-info-rec-received", { + clientId: 1, + connectedNumber: { + type: 4, + plan: 3, + number: "+56473839201", + pi: 2, + si: 1 + } + }); + + messenger.notifyCdmaInfoRecSignal(0, 1, 2, 3); + + equal_received_system_message("cdma-info-rec-received", { + clientId: 0, + signal: { + type: 1, + alertPitch: 2, + signal: 3 + } + }); + + messenger.notifyCdmaInfoRecRedirectingNumber(1, 8, 7, "+1029384756", 6, 5, 4); + + equal_received_system_message("cdma-info-rec-received", { + clientId: 1, + redirect: { + type: 8, + plan: 7, + number: "+1029384756", + pi: 6, + si: 5, + reason: 4 + } + }); + + messenger.notifyCdmaInfoRecLineControl(0, 1, 0, 1, 255); + + equal_received_system_message("cdma-info-rec-received", { + clientId: 0, + lineControl: { + polarityIncluded: 1, + toggle: 0, + reverse: 1, + powerDenial: 255 + } + }); + + messenger.notifyCdmaInfoRecClir(1, 256); + + equal_received_system_message("cdma-info-rec-received", { + clientId: 1, + clirCause: 256 + }); + + messenger.notifyCdmaInfoRecAudioControl(0, 255, -1); + + equal_received_system_message("cdma-info-rec-received", { + clientId: 0, + audioControl: { + upLink: 255, + downLink: -1 + } + }); + + run_next_test(); +}); + +/** + * Verify Error Handling of StkProactiveCmdFactory.createCommand() + */ +add_test(function test_icc_stk_cmd_factory_create_command_error() { + let messenger = newRILSystemMessenger(); + + // Verify the protection of invalid typeOfCommand. + try { + gStkCmdFactory.createCommand({ + commandNumber: 0, + typeOfCommand: RIL.STK_CMD_MORE_TIME, // Invalid TypeOfCommand + commandQualifier: 0x00 + }); + + ok(false, "Failed to verify the protection of createCommand()!"); + } catch (e) { + ok(e.message.indexOf("Unknown Command Type") !== -1, + "Invalid typeOfCommand!"); + } + + run_next_test(); +}); + +/** + * Verify Error Handling of StkProactiveCmdFactory.createCommandMessage() + */ +add_test(function test_icc_stk_cmd_factory_create_system_msg_invalid_cmd_type() { + let messenger = newRILSystemMessenger(); + let iccId = "99887766554433221100"; + + // Verify the protection of invalid typeOfCommand. + try { + gStkCmdFactory.createCommandMessage({ + QueryInterface: XPCOMUtils.generateQI([Ci.nsIStkProactiveCmd]), + + // nsIStkProactiveCmd + commandNumber: 0, + typeOfCommand: RIL.STK_CMD_MORE_TIME, // Invalid TypeOfCommand + commandQualifier: 0 + }); + + ok(false, "Failed to identify invalid typeOfCommand!"); + } catch (e) { + ok(e.message.indexOf("Unknown Command Type") !== -1, + "Invalid typeOfCommand!"); + } + + run_next_test(); +}); + +/** + * Verify Error Handling of StkProactiveCmdFactory.createCommandMessage() + */ +add_test(function test_icc_stk_cmd_factory_create_system_msg_incorrect_cmd_type() { + let messenger = newRILSystemMessenger(); + let iccId = "99887766554433221100"; + + // Verify the protection of invalid typeOfCommand. + try { + gStkCmdFactory.createCommandMessage({ + QueryInterface: XPCOMUtils.generateQI([Ci.nsIStkProactiveCmd, + Ci.nsIStkProvideLocalInfoCmd]), + + // nsIStkProactiveCmd + commandNumber: 0, + typeOfCommand: RIL.STK_CMD_POLL_INTERVAL, // Incorrect typeOfCommand + commandQualifier: 0, + // nsIStkProvideLocalInfoCmd + localInfoType: 0x00, + }); + + ok(false, "Failed to identify incorrect typeOfCommand!"); + } catch (e) { + ok(e.message.indexOf("Failed to convert command into concrete class: ") !== -1); + } + + run_next_test(); +}); + +/** + * Verify RILSystemMessenger.notifyStkProactiveCommand() + */ +add_test(function test_icc_notify_stk_proactive_command() { + let messenger = newRILSystemMessenger(); + let iccId = "99887766554433221100"; + let WHT = 0xFFFFFFFF; + let BLK = 0x000000FF; + let RED = 0xFF0000FF; + let GRN = 0x00FF00FF; + let BLU = 0x0000FFFF; + let TSP = 0; + // Basic Image, see Anex B.1 in TS 31.102. + let basicIcon = { + width: 8, + height: 8, + codingScheme: "basic", + pixels: [WHT, WHT, WHT, WHT, WHT, WHT, WHT, WHT, + BLK, BLK, BLK, BLK, BLK, BLK, WHT, WHT, + WHT, BLK, WHT, BLK, BLK, WHT, BLK, WHT, + WHT, BLK, BLK, WHT, WHT, BLK, BLK, WHT, + WHT, BLK, BLK, WHT, WHT, BLK, BLK, WHT, + WHT, BLK, WHT, BLK, BLK, WHT, BLK, WHT, + WHT, WHT, BLK, BLK, BLK, BLK, WHT, WHT, + WHT, WHT, WHT, WHT, WHT, WHT, WHT, WHT] + }; + // Color Image, see Anex B.2 in TS 31.102. + let colorIcon = { + width: 8, + height: 8, + codingScheme: "color", + pixels: [BLU, BLU, BLU, BLU, BLU, BLU, BLU, BLU, + BLU, RED, RED, RED, RED, RED, RED, BLU, + BLU, RED, GRN, GRN, GRN, RED, RED, BLU, + BLU, RED, RED, GRN, GRN, RED, RED, BLU, + BLU, RED, RED, GRN, GRN, RED, RED, BLU, + BLU, RED, RED, GRN, GRN, GRN, RED, BLU, + BLU, RED, RED, RED, RED, RED, RED, BLU, + BLU, BLU, BLU, BLU, BLU, BLU, BLU, BLU] + }; + // Color Image with Transparency, see Anex B.2 in TS 31.102. + let colorTransparencyIcon = { + width: 8, + height: 8, + codingScheme: "color-transparency", + pixels: [TSP, TSP, TSP, TSP, TSP, TSP, TSP, TSP, + TSP, RED, RED, RED, RED, RED, RED, TSP, + TSP, RED, GRN, GRN, GRN, RED, RED, TSP, + TSP, RED, RED, GRN, GRN, RED, RED, TSP, + TSP, RED, RED, GRN, GRN, RED, RED, TSP, + TSP, RED, RED, GRN, GRN, GRN, RED, TSP, + TSP, RED, RED, RED, RED, RED, RED, TSP, + TSP, TSP, TSP, TSP, TSP, TSP, TSP, TSP] + }; + + let cmdCount = 0; + + // Test Messages: + let messages = [ + // STK_CMD_REFRESH + { + commandNumber: ++cmdCount, + typeOfCommand: RIL.STK_CMD_REFRESH, + commandQualifier: 0x04 // UICC Reset + }, + // STK_CMD_POLL_INTERVAL + { + commandNumber: ++cmdCount, + typeOfCommand: RIL.STK_CMD_POLL_INTERVAL, + commandQualifier: 0x00, // RFU + options: { + timeUnit: RIL.STK_TIME_UNIT_TENTH_SECOND, + timeInterval: 0x05 + } + }, + // STK_CMD_POLL_OFF + { + commandNumber: ++cmdCount, + typeOfCommand: RIL.STK_CMD_POLL_OFF, + commandQualifier: 0x00, // RFU + }, + // STK_CMD_PROVIDE_LOCAL_INFO + { + commandNumber: ++cmdCount, + typeOfCommand: RIL.STK_CMD_PROVIDE_LOCAL_INFO, + commandQualifier: 0x01, // IMEI of the terminal + options: { + localInfoType: 0x01 // IMEI of the terminal + } + }, + // STK_CMD_SET_UP_EVENT_LIST with eventList + { + commandNumber: ++cmdCount, + typeOfCommand: RIL.STK_CMD_SET_UP_EVENT_LIST, + commandQualifier: 0x00, // RFU + options: { + eventList: [ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1A, 0x1B, 0x1C ] + } + }, + // STK_CMD_SET_UP_EVENT_LIST without eventList + { + commandNumber: ++cmdCount, + typeOfCommand: RIL.STK_CMD_SET_UP_EVENT_LIST, + commandQualifier: 0x00, // RFU + options: { + eventList: null + } + }, + // STK_CMD_SET_UP_MENU with mandatory properties. + { + commandNumber: ++cmdCount, + typeOfCommand: RIL.STK_CMD_SET_UP_MENU, + commandQualifier: 0x80, // bit 8: 1 = help information available + options: { + title: "Toolkit Menu 1", + items: [ + { identifier: 0x01, text: "Menu Item 1" }, + { identifier: 0x02, text: "Menu Item 2" }, + { identifier: 0x03, text: "Menu Item 3" } + ], + isHelpAvailable: true + } + }, + // STK_CMD_SET_UP_MENU with optional properties including: + // iconInfo for this menu, iconInfo for each item and nextActionList. + { + commandNumber: ++cmdCount, + typeOfCommand: RIL.STK_CMD_SET_UP_MENU, + commandQualifier: 0x00, // bit 8: 0 = help information is not available + options: { + title: "Toolkit Menu 2", + items: [ + { identifier: 0x01, + text: "Menu Item 1", + iconSelfExplanatory: true, + icons: [basicIcon] + }, + { identifier: 0x02, + text: "Menu Item 2", + iconSelfExplanatory: false, + icons: [basicIcon, colorIcon] + }, + { identifier: 0x03, + text: "Menu Item 3", + iconSelfExplanatory: true, + icons: [basicIcon, colorIcon, colorTransparencyIcon] + }, + ], + nextActionList: [ + RIL.STK_NEXT_ACTION_END_PROACTIVE_SESSION, + RIL.STK_NEXT_ACTION_NULL, + RIL.STK_NEXT_ACTION_NULL, + RIL.STK_NEXT_ACTION_NULL + ], + iconSelfExplanatory: false, + icons: [basicIcon, colorIcon, colorTransparencyIcon], + isHelpAvailable: false + } + }, + // STK_CMD_SELECT_ITEM with mandatory properties. + { + commandNumber: ++cmdCount, + typeOfCommand: RIL.STK_CMD_SELECT_ITEM, + commandQualifier: RIL.STK_PRESENTATION_TYPE_NOT_SPECIFIED, + options: { + title: null, + items: [ + { identifier: 0x01, text: "Menu Item 1" }, + { identifier: 0x02, text: "Menu Item 2" }, + { identifier: 0x03, text: "Menu Item 3" } + ], + presentationType: RIL.STK_PRESENTATION_TYPE_NOT_SPECIFIED, + isHelpAvailable: false + } + }, + // STK_CMD_SELECT_ITEM with optional properties including: + // title, iconInfo for this menu, iconInfo for each item and nextActionList. + { + commandNumber: ++cmdCount, + typeOfCommand: RIL.STK_CMD_SELECT_ITEM, + commandQualifier: RIL.STK_PRESENTATION_TYPE_NAVIGATION_OPTIONS, + options: { + title: "Selected Toolkit Menu", + items: [ + { identifier: 0x01, + text: "Menu Item 1", + iconSelfExplanatory: true, + icons: [basicIcon] + }, + { identifier: 0x02, + text: "Menu Item 2", + iconSelfExplanatory: false, + icons: [basicIcon, colorIcon] + }, + { identifier: 0x03, + text: "Menu Item 3", + iconSelfExplanatory: true, + icons: [basicIcon, colorIcon, colorTransparencyIcon] + }, + ], + nextActionList: [ + RIL.STK_NEXT_ACTION_END_PROACTIVE_SESSION, + RIL.STK_NEXT_ACTION_NULL, + RIL.STK_NEXT_ACTION_NULL, + RIL.STK_NEXT_ACTION_NULL + ], + defaultItem: 0x02, + iconSelfExplanatory: false, + icons: [basicIcon, colorIcon, colorTransparencyIcon], + presentationType: RIL.STK_PRESENTATION_TYPE_NAVIGATION_OPTIONS, + isHelpAvailable: false + } + }, + // STK_CMD_DISPLAY_TEXT with mandatory properties. + { + commandNumber: ++cmdCount, + typeOfCommand: RIL.STK_CMD_DISPLAY_TEXT, + commandQualifier: 0x01, // bit 1: High Priority + options: { + text: "Display Text 1", + isHighPriority: true, + userClear: false, + responseNeeded: false + } + }, + // STK_CMD_DISPLAY_TEXT with optional properties. + { + commandNumber: ++cmdCount, + typeOfCommand: RIL.STK_CMD_DISPLAY_TEXT, + commandQualifier: 0x80, // bit 8: User Clear + options: { + text: "Display Text 2", + isHighPriority: false, + userClear: true, + responseNeeded: true, + duration: { + timeUnit: RIL.STK_TIME_UNIT_TENTH_SECOND, + timeInterval: 0x05 + }, + iconSelfExplanatory: true, + icons: [basicIcon] + } + }, + // STK_CMD_SET_UP_IDLE_MODE_TEXT + { + commandNumber: ++cmdCount, + typeOfCommand: RIL.STK_CMD_SET_UP_IDLE_MODE_TEXT, + commandQualifier: 0x00, // RFU + options: { + text: "Setup Idle Mode Text" + } + }, + // STK_CMD_SEND_SS + { + commandNumber: ++cmdCount, + typeOfCommand: RIL.STK_CMD_SEND_SS, + commandQualifier: 0x00, // RFU + options: { + text: "Send SS", + iconSelfExplanatory: true, + icons: [colorIcon] + } + }, + // STK_CMD_SEND_USSD + { + commandNumber: ++cmdCount, + typeOfCommand: RIL.STK_CMD_SEND_USSD, + commandQualifier: 0x00, // RFU + options: { + text: "Send USSD" + } + }, + // STK_CMD_SEND_SMS + { + commandNumber: ++cmdCount, + typeOfCommand: RIL.STK_CMD_SEND_SMS, + commandQualifier: 0x00, // RFU + options: { + text: "Send SMS", + iconSelfExplanatory: false, + icons: [colorTransparencyIcon] + } + }, + // STK_CMD_SEND_DTMF + { + commandNumber: ++cmdCount, + typeOfCommand: RIL.STK_CMD_SEND_DTMF, + commandQualifier: 0x00, // RFU + options: { + text: "Send DTMF", + iconSelfExplanatory: true, + icons: [basicIcon] + } + }, + // STK_CMD_GET_INKEY + { + commandNumber: ++cmdCount, + typeOfCommand: RIL.STK_CMD_GET_INKEY, + commandQualifier: 0x84, // bit 3: isYesNoRequested, bit 8: isHelpAvailable + options: { + text: "Get Input Key", + minLength: 1, + maxLength: 1, + duration: { + timeUnit: RIL.STK_TIME_UNIT_SECOND, + timeInterval: 0x0A + }, + isAlphabet: false, + isUCS2: false, + isYesNoRequested: true, + isHelpAvailable: true, + defaultText: null, + iconSelfExplanatory: false, + icons: [colorIcon] + } + }, + // STK_CMD_GET_INPUT + { + commandNumber: ++cmdCount, + typeOfCommand: RIL.STK_CMD_GET_INPUT, + commandQualifier: 0x0F, // bit 1-4: isAlphabet, isUCS2, hideInput, isPacked + options: { + text: "Get Input Text", + minLength: 1, + maxLength: 255, + defaultText: "Default Input Text", + isAlphabet: true, + isUCS2: true, + hideInput: true, + isPacked: true, + isHelpAvailable: false, + defaultText: null, + iconSelfExplanatory: true, + icons: [basicIcon] + } + }, + // STK_CMD_SET_UP_CALL with mandatory properties. + { + commandNumber: ++cmdCount, + typeOfCommand: RIL.STK_CMD_SET_UP_CALL, + commandQualifier: 0x00, // RFU + options: { + address: "+0987654321" + } + }, + // STK_CMD_SET_UP_CALL with optional properties. + { + commandNumber: ++cmdCount, + typeOfCommand: RIL.STK_CMD_SET_UP_CALL, + commandQualifier: 0x00, // RFU + options: { + address: "+0987654321", + confirmMessage: { + text: "Confirm Message", + iconSelfExplanatory: false, + icons: [colorIcon] + }, + callMessage: { + text: "Call Message", + iconSelfExplanatory: true, + icons: [basicIcon] + }, + duration: { + timeUnit: RIL.STK_TIME_UNIT_SECOND, + timeInterval: 0x0A + } + } + }, + // STK_CMD_LAUNCH_BROWSER with mandatory properties. + { + commandNumber: ++cmdCount, + typeOfCommand: RIL.STK_CMD_LAUNCH_BROWSER, + commandQualifier: RIL.STK_BROWSER_MODE_USING_NEW_BROWSER, + options: { + url: "http://www.mozilla.org", + mode: RIL.STK_BROWSER_MODE_USING_NEW_BROWSER + } + }, + // STK_CMD_LAUNCH_BROWSER with optional properties. + { + commandNumber: ++cmdCount, + typeOfCommand: RIL.STK_CMD_LAUNCH_BROWSER, + commandQualifier: RIL.STK_BROWSER_MODE_USING_NEW_BROWSER, + options: { + url: "http://www.mozilla.org", + mode: RIL.STK_BROWSER_MODE_USING_NEW_BROWSER, + confirmMessage: { + text: "Confirm Message for Launch Browser", + iconSelfExplanatory: false, + icons: [colorTransparencyIcon] + } + } + }, + // STK_CMD_PLAY_TONE with mandatory properties. + { + commandNumber: ++cmdCount, + typeOfCommand: RIL.STK_CMD_PLAY_TONE, + commandQualifier: 0x01, // isVibrate + options: { + text: null, + isVibrate: true + } + }, + // STK_CMD_PLAY_TONE with optional properties. + { + commandNumber: ++cmdCount, + typeOfCommand: RIL.STK_CMD_PLAY_TONE, + commandQualifier: 0x00, // isVibrate = false + options: { + text: "Play Tone", + tone: RIL.STK_TONE_TYPE_CONGESTION, + isVibrate: false, + duration: { + timeUnit: RIL.STK_TIME_UNIT_SECOND, + timeInterval: 0x0A + }, + iconSelfExplanatory: true, + icons: [basicIcon] + } + }, + // STK_CMD_TIMER_MANAGEMENT with mandatory properties. + { + commandNumber: ++cmdCount, + typeOfCommand: RIL.STK_CMD_TIMER_MANAGEMENT, + commandQualifier: RIL.STK_TIMER_DEACTIVATE, + options: { + timerId: 0x08, + timerAction: RIL.STK_TIMER_DEACTIVATE + } + }, + // STK_CMD_TIMER_MANAGEMENT with optional properties. + { + commandNumber: ++cmdCount, + typeOfCommand: RIL.STK_CMD_TIMER_MANAGEMENT, + commandQualifier: RIL.STK_TIMER_START, + options: { + timerId: 0x01, + timerValue: (12 * 60 * 60) + (30 * 60) + (30), // 12:30:30 + timerAction: RIL.STK_TIMER_START + } + }, + // STK_CMD_OPEN_CHANNEL with mandatory properties. + { + commandNumber: ++cmdCount, + typeOfCommand: RIL.STK_CMD_OPEN_CHANNEL, + commandQualifier: 0x00, //RFU + options: { + text: null, + } + }, + // STK_CMD_OPEN_CHANNEL with optional properties. + { + commandNumber: ++cmdCount, + typeOfCommand: RIL.STK_CMD_OPEN_CHANNEL, + commandQualifier: 0x00, //RFU + options: { + text: "Open Channel", + iconSelfExplanatory: false, + icons: [colorIcon] + } + }, + // STK_CMD_CLOSE_CHANNEL with optional properties. + { + commandNumber: ++cmdCount, + typeOfCommand: RIL.STK_CMD_CLOSE_CHANNEL, + commandQualifier: 0x00, //RFU + options: { + text: "Close Channel", + iconSelfExplanatory: true, + icons: [colorTransparencyIcon] + } + }, + // STK_CMD_SEND_DATA with optional properties. + { + commandNumber: ++cmdCount, + typeOfCommand: RIL.STK_CMD_SEND_DATA, + commandQualifier: 0x00, //RFU + options: { + text: null, + iconSelfExplanatory: false, + icons: [basicIcon] + } + }, + // STK_CMD_RECEIVE_DATA with optional properties. + { + commandNumber: ++cmdCount, + typeOfCommand: RIL.STK_CMD_RECEIVE_DATA, + commandQualifier: 0x00, //RFU + options: { + text: "Receive Data" + } + }, + null // Termination condition to run_next_test() + ]; + + messages.forEach(function(aMessage) { + if (!aMessage) { + run_next_test(); + return; + } + + messenger.notifyStkProactiveCommand(iccId, + gStkCmdFactory.createCommand(aMessage)); + + equal_received_system_message("icc-stkcommand", { + iccId: iccId, + command: aMessage + }); + }); +}); diff --git a/dom/system/gonk/tests/test_ril_worker_barring_password.js b/dom/system/gonk/tests/test_ril_worker_barring_password.js new file mode 100644 index 000000000..fcd3e4405 --- /dev/null +++ b/dom/system/gonk/tests/test_ril_worker_barring_password.js @@ -0,0 +1,61 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +subscriptLoader.loadSubScript("resource://gre/modules/ril_consts.js", this); + +function run_test() { + run_next_test(); +} + +const PIN = "0000"; +const NEW_PIN = "1234"; + +add_test(function test_change_call_barring_password() { + let worker = newUint8Worker(); + let context = worker.ContextPool._contexts[0]; + let buf = context.Buf; + + function do_test(facility, pin, newPin) { + buf.sendParcel = function fakeSendParcel () { + // Request Type. + equal(this.readInt32(), REQUEST_CHANGE_BARRING_PASSWORD); + + // Token : we don't care. + this.readInt32(); + + let parcel = this.readStringList(); + equal(parcel.length, 3); + equal(parcel[0], facility); + equal(parcel[1], pin); + equal(parcel[2], newPin); + }; + + let options = {facility: facility, pin: pin, newPin: newPin}; + context.RIL.changeCallBarringPassword(options); + } + + do_test(ICC_CB_FACILITY_BA_ALL, PIN, NEW_PIN); + + run_next_test(); +}); + +add_test(function test_check_change_call_barring_password_result() { + let barringPasswordOptions; + let workerHelper = newInterceptWorker(); + let worker = workerHelper.worker; + + let context = worker.ContextPool._contexts[0]; + context.RIL.changeCallBarringPassword = + function fakeChangeCallBarringPassword(options) { + barringPasswordOptions = options; + context.RIL[REQUEST_CHANGE_BARRING_PASSWORD](0, {}); + }; + + context.RIL.changeCallBarringPassword({pin: PIN, newPin: NEW_PIN}); + + let postedMessage = workerHelper.postedMessage; + equal(barringPasswordOptions.pin, PIN); + equal(barringPasswordOptions.newPin, NEW_PIN); + + run_next_test(); +}); diff --git a/dom/system/gonk/tests/test_ril_worker_buf.js b/dom/system/gonk/tests/test_ril_worker_buf.js new file mode 100644 index 000000000..30054a881 --- /dev/null +++ b/dom/system/gonk/tests/test_ril_worker_buf.js @@ -0,0 +1,187 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +function run_test() { + run_next_test(); +} + +/** + * Add test function with specified parcel and request handler. + * + * @param parcel + * Incoming parcel to be tested. + * @param handler + * Handler to be invoked as RIL request handler. + */ +function add_test_incoming_parcel(parcel, handler) { + add_test(function test_incoming_parcel() { + let worker = newWorker({ + postRILMessage: function(data) { + // do nothing + }, + postMessage: function(message) { + // do nothing + } + }); + + if (!parcel) { + parcel = newIncomingParcel(-1, + worker.RESPONSE_TYPE_UNSOLICITED, + worker.REQUEST_VOICE_REGISTRATION_STATE, + [0, 0, 0, 0]); + } + + let context = worker.ContextPool._contexts[0]; + // supports only requests less or equal than UINT8_MAX(255). + let buf = context.Buf; + let request = parcel[buf.PARCEL_SIZE_SIZE + buf.UINT32_SIZE]; + context.RIL[request] = function ril_request_handler() { + handler.apply(this, arguments); + }; + + worker.onRILMessage(0, parcel); + + // end of incoming parcel's trip, let's do next test. + run_next_test(); + }); +} + +// Test normal parcel handling. +add_test_incoming_parcel(null, + function test_normal_parcel_handling() { + let self = this; + try { + // reads exactly the same size, should not throw anything. + self.context.Buf.readInt32(); + } catch (e) { + ok(false, "Got exception: " + e); + } + } +); + +// Test parcel under read. +add_test_incoming_parcel(null, + function test_parcel_under_read() { + let self = this; + try { + // reads less than parcel size, should not throw. + self.context.Buf.readUint16(); + } catch (e) { + ok(false, "Got exception: " + e); + } + } +); + +// Test parcel over read. +add_test_incoming_parcel(null, + function test_parcel_over_read() { + let buf = this.context.Buf; + + // read all data available + while (buf.readAvailable > 0) { + buf.readUint8(); + } + + throws(function over_read_handler() { + // reads more than parcel size, should throw an error. + buf.readUint8(); + },"Trying to read data beyond the parcel end!"); + } +); + +// Test Bug 814761: buffer overwritten +add_test(function test_incoming_parcel_buffer_overwritten() { + let worker = newWorker({ + postRILMessage: function(data) { + // do nothing + }, + postMessage: function(message) { + // do nothing + } + }); + + let context = worker.ContextPool._contexts[0]; + // A convenient alias. + let buf = context.Buf; + + // Allocate an array of specified size and set each of its elements to value. + function calloc(length, value) { + let array = new Array(length); + for (let i = 0; i < length; i++) { + array[i] = value; + } + return array; + } + + // Do nothing in handleParcel(). + let request = worker.REQUEST_VOICE_REGISTRATION_STATE; + context.RIL[request] = null; + + // Prepare two parcels, whose sizes are both smaller than the incoming buffer + // size but larger when combined, to trigger the bug. + let pA_dataLength = buf.incomingBufferLength / 2; + let pA = newIncomingParcel(-1, + worker.RESPONSE_TYPE_UNSOLICITED, + request, + calloc(pA_dataLength, 1)); + let pA_parcelSize = pA.length - buf.PARCEL_SIZE_SIZE; + + let pB_dataLength = buf.incomingBufferLength * 3 / 4; + let pB = newIncomingParcel(-1, + worker.RESPONSE_TYPE_UNSOLICITED, + request, + calloc(pB_dataLength, 1)); + let pB_parcelSize = pB.length - buf.PARCEL_SIZE_SIZE; + + // First, send an incomplete pA and verifies related data pointer: + let p1 = pA.subarray(0, pA.length - 1); + worker.onRILMessage(0, p1); + // The parcel should not have been processed. + equal(buf.readAvailable, 0); + // buf.currentParcelSize should have been set because incoming data has more + // than 4 octets. + equal(buf.currentParcelSize, pA_parcelSize); + // buf.readIncoming should contains remaining unconsumed octets count. + equal(buf.readIncoming, p1.length - buf.PARCEL_SIZE_SIZE); + // buf.incomingWriteIndex should be ready to accept the last octet. + equal(buf.incomingWriteIndex, p1.length); + + // Second, send the last octet of pA and whole pB. The Buf should now expand + // to cover both pA & pB. + let p2 = new Uint8Array(1 + pB.length); + p2.set(pA.subarray(pA.length - 1), 0); + p2.set(pB, 1); + worker.onRILMessage(0, p2); + // The parcels should have been both consumed. + equal(buf.readAvailable, 0); + // No parcel data remains. + equal(buf.currentParcelSize, 0); + // No parcel data remains. + equal(buf.readIncoming, 0); + // The Buf should now expand to cover both pA & pB. + equal(buf.incomingWriteIndex, pA.length + pB.length); + + // end of incoming parcel's trip, let's do next test. + run_next_test(); +}); + +// Test Buf.readUint8Array. +add_test_incoming_parcel(null, + function test_buf_readUint8Array() { + let buf = this.context.Buf; + + let u8array = buf.readUint8Array(1); + equal(u8array instanceof Uint8Array, true); + equal(u8array.length, 1); + equal(buf.readAvailable, 3); + + u8array = buf.readUint8Array(2); + equal(u8array.length, 2); + equal(buf.readAvailable, 1); + + throws(function over_read_handler() { + // reads more than parcel size, should throw an error. + u8array = buf.readUint8Array(2); + }, "Trying to read data beyond the parcel end!"); + } +); diff --git a/dom/system/gonk/tests/test_ril_worker_cdma_info_rec.js b/dom/system/gonk/tests/test_ril_worker_cdma_info_rec.js new file mode 100644 index 000000000..335c0c403 --- /dev/null +++ b/dom/system/gonk/tests/test_ril_worker_cdma_info_rec.js @@ -0,0 +1,234 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +subscriptLoader.loadSubScript("resource://gre/modules/ril_consts.js", this); + +function run_test() { + run_next_test(); +} + +/** + * Helper function. + */ +function newWorkerWithParcel(parcelBuf) { + let worker = newWorker({ + postRILMessage: function(data) { + // Do nothing + }, + postMessage: function(message) { + // Do nothing + } + }); + + let index = 0; // index for read + let buf = parcelBuf; + + let context = worker.ContextPool._contexts[0]; + context.Buf.readUint8 = function() { + return buf[index++]; + }; + + context.Buf.readUint16 = function() { + return buf[index++]; + }; + + context.Buf.readInt32 = function() { + return buf[index++]; + }; + + context.Buf.seekIncoming = function(offset) { + index += offset / context.Buf.UINT32_SIZE; + }; + + return worker; +} + +// Test CDMA information record decoder. + +/** + * Verify decoder for type DISPLAY + */ +add_test(function test_display() { + let worker = newWorkerWithParcel([ + 0x01, // one inforemation record + 0x00, // type: display + 0x09, // length: 9 + 0x54, 0x65, 0x73, 0x74, 0x20, 0x49, 0x6E, 0x66, + 0x6F, 0x00]); + let context = worker.ContextPool._contexts[0]; + let helper = context.CdmaPDUHelper; + let records = helper.decodeInformationRecord(); + + equal(records[0].display, "Test Info"); + + run_next_test(); +}); + +/** + * Verify decoder for type EXTENDED DISPLAY + */ +add_test(function test_extended_display() { + let worker = newWorkerWithParcel([ + 0x01, // one inforemation record + 0x07, // type: extended display + 0x12, // length: 18 + 0x54, 0x65, 0x73, 0x74, 0x20, 0x45, 0x78, 0x74, + 0x65, 0x6E, 0x64, 0x65, 0x64, 0x20, 0x49, 0x6E, + 0x66, 0x6F, 0x00, 0x00]); + let context = worker.ContextPool._contexts[0]; + let helper = context.CdmaPDUHelper; + let records = helper.decodeInformationRecord(); + + equal(records[0].display, "Test Extended Info"); + + run_next_test(); +}); + +/** + * Verify decoder for mixed type + */ +add_test(function test_mixed() { + let worker = newWorkerWithParcel([ + 0x02, // two inforemation record + 0x00, // type: display + 0x0B, // length: 11 + 0x54, 0x65, 0x73, 0x74, 0x20, 0x49, 0x6E, 0x66, + 0x6F, 0x20, 0x31, 0x00, + 0x07, // type: extended display + 0x0B, // length: 11 + 0x54, 0x65, 0x73, 0x74, 0x20, 0x49, 0x6E, 0x66, + 0x6F, 0x20, 0x32, 0x00]); + let context = worker.ContextPool._contexts[0]; + let helper = context.CdmaPDUHelper; + let records = helper.decodeInformationRecord(); + + equal(records[0].display, "Test Info 1"); + equal(records[1].display, "Test Info 2"); + + run_next_test(); +}); + +/** + * Verify decoder for multiple types + */ +add_test(function test_multiple() { + let worker = newWorkerWithParcel([ + 0x02, // two inforemation record + 0x00, // type: display + 0x0B, // length: 11 + 0x54, 0x65, 0x73, 0x74, 0x20, 0x49, 0x6E, 0x66, + 0x6F, 0x20, 0x31, 0x00, + 0x00, // type: display + 0x0B, // length: 11 + 0x54, 0x65, 0x73, 0x74, 0x20, 0x49, 0x6E, 0x66, + 0x6F, 0x20, 0x32, 0x00]); + let context = worker.ContextPool._contexts[0]; + let helper = context.CdmaPDUHelper; + let records = helper.decodeInformationRecord(); + + equal(records[0].display, "Test Info 1"); + equal(records[1].display, "Test Info 2"); + + run_next_test(); +}); + +/** + * Verify decoder for Signal Type + */ +add_test(function test_signal() { + let worker = newWorkerWithParcel([ + 0x01, // one inforemation record + 0x04, // type: signal + 0x01, // isPresent: non-zero + 0x00, // signalType: Tone signal (00) + 0x01, // alertPitch: High pitch + 0x03]); // signal: Abbreviated intercept (000011) + let context = worker.ContextPool._contexts[0]; + let helper = context.CdmaPDUHelper; + let records = helper.decodeInformationRecord(); + + equal(records[0].signal.type, 0x00); + equal(records[0].signal.alertPitch, 0x01); + equal(records[0].signal.signal, 0x03); + + run_next_test(); +}); + +/** + * Verify decoder for Signal Type for Not Presented + */ +add_test(function test_signal_not_present() { + let worker = newWorkerWithParcel([ + 0x01, // one inforemation record + 0x04, // type: signal + 0x00, // isPresent: zero + 0x00, // signalType: Tone signal (00) + 0x01, // alertPitch: High pitch + 0x03]); // signal: Abbreviated intercept (000011) + let context = worker.ContextPool._contexts[0]; + let helper = context.CdmaPDUHelper; + let records = helper.decodeInformationRecord(); + + equal(records.length, 0); + + run_next_test(); +}); + +/** + * Verify decoder for Line Control + */ +add_test(function test_line_control() { + let worker = newWorkerWithParcel([ + 0x01, // one inforemation record + 0x06, // type: line control + 0x01, // polarity included + 0x00, // not toggled + 0x01, // reversed + 0xFF]); // Power denial timeout: 255 * 5 ms + let context = worker.ContextPool._contexts[0]; + let helper = context.CdmaPDUHelper; + let records = helper.decodeInformationRecord(); + + equal(records[0].lineControl.polarityIncluded, 1); + equal(records[0].lineControl.toggle, 0); + equal(records[0].lineControl.reverse, 1); + equal(records[0].lineControl.powerDenial, 255); + + run_next_test(); +}); + +/** + * Verify decoder for CLIR Cause + */ +add_test(function test_clir() { + let worker = newWorkerWithParcel([ + 0x01, // one inforemation record + 0x08, // type: clir + 0x01]); // cause: Rejected by user + let context = worker.ContextPool._contexts[0]; + let helper = context.CdmaPDUHelper; + let records = helper.decodeInformationRecord(); + + equal(records[0].clirCause, 1); + + run_next_test(); +}); + +/** + * Verify decoder for Audio Control + */ +add_test(function test_clir() { + let worker = newWorkerWithParcel([ + 0x01, // one inforemation record + 0x0A, // type: audio control + 0x01, // uplink + 0xFF]); // downlink + let context = worker.ContextPool._contexts[0]; + let helper = context.CdmaPDUHelper; + let records = helper.decodeInformationRecord(); + + equal(records[0].audioControl.upLink, 1); + equal(records[0].audioControl.downLink, 255); + + run_next_test(); +}); diff --git a/dom/system/gonk/tests/test_ril_worker_cellbroadcast_config.js b/dom/system/gonk/tests/test_ril_worker_cellbroadcast_config.js new file mode 100644 index 000000000..d5645a3cf --- /dev/null +++ b/dom/system/gonk/tests/test_ril_worker_cellbroadcast_config.js @@ -0,0 +1,470 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +subscriptLoader.loadSubScript("resource://gre/modules/ril_consts.js", this); + +function run_test() { + run_next_test(); +} + +add_test(function test_ril_worker_cellbroadcast_activate() { + let worker = newWorker({ + postRILMessage: function(id, parcel) { + // Do nothing + }, + postMessage: function(message) { + // Do nothing + } + }); + let context = worker.ContextPool._contexts[0]; + + let parcelTypes = []; + let org_newParcel = context.Buf.newParcel; + context.Buf.newParcel = function(type, options) { + parcelTypes.push(type); + org_newParcel.apply(this, arguments); + }; + + function setup(isCdma) { + context.RIL._isCdma = isCdma; + context.RIL.cellBroadcastDisabled = false; + context.RIL.mergedCellBroadcastConfig = [1, 2, 4, 7]; // 1, 4-6 + parcelTypes = []; + } + + function test(isCdma, expectedRequest) { + setup(isCdma); + context.RIL.setCellBroadcastDisabled({disabled: true}); + // Makesure that request parcel is sent out. + notEqual(parcelTypes.indexOf(expectedRequest), -1); + equal(context.RIL.cellBroadcastDisabled, true); + } + + test(false, REQUEST_GSM_SMS_BROADCAST_ACTIVATION); + test(true, REQUEST_CDMA_SMS_BROADCAST_ACTIVATION); + + run_next_test(); +}); + +add_test(function test_ril_worker_cellbroadcast_config() { + let currentParcel; + let worker = newWorker({ + postRILMessage: function(id, parcel) { + currentParcel = parcel; + }, + postMessage: function(message) { + // Do nothing + } + }); + let context = worker.ContextPool._contexts[0]; + + function U32ArrayFromParcelArray(pa) { + do_print(pa); + let out = []; + for (let i = 0; i < pa.length; i += 4) { + let data = pa[i] + (pa[i+1] << 8) + (pa[i+2] << 16) + (pa[i+3] << 24); + out.push(data); + } + return out; + } + + function test(isCdma, configs, expected) { + let parcelType = isCdma ? REQUEST_CDMA_SET_BROADCAST_SMS_CONFIG + : REQUEST_GSM_SET_BROADCAST_SMS_CONFIG; + + let found = false; + worker.postRILMessage = function(id, parcel) { + let u32Parcel = U32ArrayFromParcelArray(Array.slice(parcel)); + if (u32Parcel[1] != parcelType) { + return; + } + + found = true; + // Check parcel. Data start from 4th word (32bit) + equal(u32Parcel.slice(3).toString(), expected); + }; + + context.RIL._isCdma = isCdma; + context.RIL.setSmsBroadcastConfig(configs); + + // Makesure that request parcel is sent out. + ok(found); + } + + // (GSM) RIL writes the following data to outgoing parcel: + // nums [(from, to, 0, 0xFF, 1), ... ] + test(false, + [1, 2, 4, 7] /* 1, 4-6 */, + ["2", "1,1,0,255,1", "4,6,0,255,1"].join()); + + // (CDMA) RIL writes the following data to outgoing parcel: + // nums [(id, 0, 1), ... ] + test(true, + [1, 2, 4, 7] /* 1, 4-6 */, + ["4", "1,0,1", "4,0,1", "5,0,1", "6,0,1"].join()); + + run_next_test(); +}); + +add_test(function test_ril_worker_cellbroadcast_merge_config() { + let worker = newWorker({ + postRILMessage: function(id, parcel) { + // Do nothing + }, + postMessage: function(message) { + // Do nothing + } + }); + let context = worker.ContextPool._contexts[0]; + + function test(isCdma, configs, expected) { + context.RIL._isCdma = isCdma; + context.RIL.cellBroadcastConfigs = configs; + context.RIL._mergeAllCellBroadcastConfigs(); + equal(context.RIL.mergedCellBroadcastConfig.toString(), expected); + } + + let configs = { + MMI: [1, 2, 4, 7], // 1, 4-6 + CBMI: [6, 9], // 6-8 + CBMID: [8, 11], // 8-10 + CBMIR: [10, 13] // 10-12 + }; + + test(false, configs, "1,2,4,13"); + test(true, configs, "1,2,4,7"); + + run_next_test(); +}); + +add_test(function test_ril_worker_cellbroadcast_set_search_list() { + let worker = newWorker({ + postRILMessage: function(id, parcel) { + // Do nothing + }, + postMessage: function(message) { + // Do nothing + } + }); + + let context = worker.ContextPool._contexts[0]; + + function test(aIsCdma, aSearchList, aExpected) { + context.RIL._isCdma = aIsCdma; + + let options = { searchList: aSearchList }; + context.RIL.setCellBroadcastSearchList(options); + // Enforce the MMI result to string for comparison. + equal("" + context.RIL.cellBroadcastConfigs.MMI, aExpected); + do_check_eq(options.errorMsg, undefined); + } + + let searchListStr = "1,2,3,4"; + let searchList = { gsm: "1,2,3,4", cdma: "5,6,7,8" }; + + test(false, searchListStr, "1,2,2,3,3,4,4,5"); + test(true, searchListStr, "1,2,2,3,3,4,4,5"); + test(false, searchList, "1,2,2,3,3,4,4,5"); + test(true, searchList, "5,6,6,7,7,8,8,9"); + test(false, null, "null"); + test(true, null, "null"); + + run_next_test(); +}); + +add_test(function test_ril_worker_mergeCellBroadcastConfigs() { + let worker = newWorker({ + postRILMessage: function(data) { + // Do nothing + }, + postMessage: function(message) { + // Do nothing + } + }); + + let context = worker.ContextPool._contexts[0]; + let ril = context.RIL; + + function test(olist, from, to, expected) { + let result = ril._mergeCellBroadcastConfigs(olist, from, to); + equal(JSON.stringify(expected), JSON.stringify(result)); + } + + test(null, 0, 1, [0, 1]); + + test([10, 13], 7, 8, [ 7, 8, 10, 13]); + test([10, 13], 7, 9, [ 7, 9, 10, 13]); + test([10, 13], 7, 10, [ 7, 13]); + test([10, 13], 7, 11, [ 7, 13]); + test([10, 13], 7, 12, [ 7, 13]); + test([10, 13], 7, 13, [ 7, 13]); + test([10, 13], 7, 14, [ 7, 14]); + test([10, 13], 7, 15, [ 7, 15]); + test([10, 13], 7, 16, [ 7, 16]); + test([10, 13], 8, 9, [ 8, 9, 10, 13]); + test([10, 13], 8, 10, [ 8, 13]); + test([10, 13], 8, 11, [ 8, 13]); + test([10, 13], 8, 12, [ 8, 13]); + test([10, 13], 8, 13, [ 8, 13]); + test([10, 13], 8, 14, [ 8, 14]); + test([10, 13], 8, 15, [ 8, 15]); + test([10, 13], 8, 16, [ 8, 16]); + test([10, 13], 9, 10, [ 9, 13]); + test([10, 13], 9, 11, [ 9, 13]); + test([10, 13], 9, 12, [ 9, 13]); + test([10, 13], 9, 13, [ 9, 13]); + test([10, 13], 9, 14, [ 9, 14]); + test([10, 13], 9, 15, [ 9, 15]); + test([10, 13], 9, 16, [ 9, 16]); + test([10, 13], 10, 11, [10, 13]); + test([10, 13], 10, 12, [10, 13]); + test([10, 13], 10, 13, [10, 13]); + test([10, 13], 10, 14, [10, 14]); + test([10, 13], 10, 15, [10, 15]); + test([10, 13], 10, 16, [10, 16]); + test([10, 13], 11, 12, [10, 13]); + test([10, 13], 11, 13, [10, 13]); + test([10, 13], 11, 14, [10, 14]); + test([10, 13], 11, 15, [10, 15]); + test([10, 13], 11, 16, [10, 16]); + test([10, 13], 12, 13, [10, 13]); + test([10, 13], 12, 14, [10, 14]); + test([10, 13], 12, 15, [10, 15]); + test([10, 13], 12, 16, [10, 16]); + test([10, 13], 13, 14, [10, 14]); + test([10, 13], 13, 15, [10, 15]); + test([10, 13], 13, 16, [10, 16]); + test([10, 13], 14, 15, [10, 13, 14, 15]); + test([10, 13], 14, 16, [10, 13, 14, 16]); + test([10, 13], 15, 16, [10, 13, 15, 16]); + + test([10, 13, 14, 17], 7, 8, [ 7, 8, 10, 13, 14, 17]); + test([10, 13, 14, 17], 7, 9, [ 7, 9, 10, 13, 14, 17]); + test([10, 13, 14, 17], 7, 10, [ 7, 13, 14, 17]); + test([10, 13, 14, 17], 7, 11, [ 7, 13, 14, 17]); + test([10, 13, 14, 17], 7, 12, [ 7, 13, 14, 17]); + test([10, 13, 14, 17], 7, 13, [ 7, 13, 14, 17]); + test([10, 13, 14, 17], 7, 14, [ 7, 17]); + test([10, 13, 14, 17], 7, 15, [ 7, 17]); + test([10, 13, 14, 17], 7, 16, [ 7, 17]); + test([10, 13, 14, 17], 7, 17, [ 7, 17]); + test([10, 13, 14, 17], 7, 18, [ 7, 18]); + test([10, 13, 14, 17], 7, 19, [ 7, 19]); + test([10, 13, 14, 17], 8, 9, [ 8, 9, 10, 13, 14, 17]); + test([10, 13, 14, 17], 8, 10, [ 8, 13, 14, 17]); + test([10, 13, 14, 17], 8, 11, [ 8, 13, 14, 17]); + test([10, 13, 14, 17], 8, 12, [ 8, 13, 14, 17]); + test([10, 13, 14, 17], 8, 13, [ 8, 13, 14, 17]); + test([10, 13, 14, 17], 8, 14, [ 8, 17]); + test([10, 13, 14, 17], 8, 15, [ 8, 17]); + test([10, 13, 14, 17], 8, 16, [ 8, 17]); + test([10, 13, 14, 17], 8, 17, [ 8, 17]); + test([10, 13, 14, 17], 8, 18, [ 8, 18]); + test([10, 13, 14, 17], 8, 19, [ 8, 19]); + test([10, 13, 14, 17], 9, 10, [ 9, 13, 14, 17]); + test([10, 13, 14, 17], 9, 11, [ 9, 13, 14, 17]); + test([10, 13, 14, 17], 9, 12, [ 9, 13, 14, 17]); + test([10, 13, 14, 17], 9, 13, [ 9, 13, 14, 17]); + test([10, 13, 14, 17], 9, 14, [ 9, 17]); + test([10, 13, 14, 17], 9, 15, [ 9, 17]); + test([10, 13, 14, 17], 9, 16, [ 9, 17]); + test([10, 13, 14, 17], 9, 17, [ 9, 17]); + test([10, 13, 14, 17], 9, 18, [ 9, 18]); + test([10, 13, 14, 17], 9, 19, [ 9, 19]); + test([10, 13, 14, 17], 10, 11, [10, 13, 14, 17]); + test([10, 13, 14, 17], 10, 12, [10, 13, 14, 17]); + test([10, 13, 14, 17], 10, 13, [10, 13, 14, 17]); + test([10, 13, 14, 17], 10, 14, [10, 17]); + test([10, 13, 14, 17], 10, 15, [10, 17]); + test([10, 13, 14, 17], 10, 16, [10, 17]); + test([10, 13, 14, 17], 10, 17, [10, 17]); + test([10, 13, 14, 17], 10, 18, [10, 18]); + test([10, 13, 14, 17], 10, 19, [10, 19]); + test([10, 13, 14, 17], 11, 12, [10, 13, 14, 17]); + test([10, 13, 14, 17], 11, 13, [10, 13, 14, 17]); + test([10, 13, 14, 17], 11, 14, [10, 17]); + test([10, 13, 14, 17], 11, 15, [10, 17]); + test([10, 13, 14, 17], 11, 16, [10, 17]); + test([10, 13, 14, 17], 11, 17, [10, 17]); + test([10, 13, 14, 17], 11, 18, [10, 18]); + test([10, 13, 14, 17], 11, 19, [10, 19]); + test([10, 13, 14, 17], 12, 13, [10, 13, 14, 17]); + test([10, 13, 14, 17], 12, 14, [10, 17]); + test([10, 13, 14, 17], 12, 15, [10, 17]); + test([10, 13, 14, 17], 12, 16, [10, 17]); + test([10, 13, 14, 17], 12, 17, [10, 17]); + test([10, 13, 14, 17], 12, 18, [10, 18]); + test([10, 13, 14, 17], 12, 19, [10, 19]); + test([10, 13, 14, 17], 13, 14, [10, 17]); + test([10, 13, 14, 17], 13, 15, [10, 17]); + test([10, 13, 14, 17], 13, 16, [10, 17]); + test([10, 13, 14, 17], 13, 17, [10, 17]); + test([10, 13, 14, 17], 13, 18, [10, 18]); + test([10, 13, 14, 17], 13, 19, [10, 19]); + test([10, 13, 14, 17], 14, 15, [10, 13, 14, 17]); + test([10, 13, 14, 17], 14, 16, [10, 13, 14, 17]); + test([10, 13, 14, 17], 14, 17, [10, 13, 14, 17]); + test([10, 13, 14, 17], 14, 18, [10, 13, 14, 18]); + test([10, 13, 14, 17], 14, 19, [10, 13, 14, 19]); + test([10, 13, 14, 17], 15, 16, [10, 13, 14, 17]); + test([10, 13, 14, 17], 15, 17, [10, 13, 14, 17]); + test([10, 13, 14, 17], 15, 18, [10, 13, 14, 18]); + test([10, 13, 14, 17], 15, 19, [10, 13, 14, 19]); + test([10, 13, 14, 17], 16, 17, [10, 13, 14, 17]); + test([10, 13, 14, 17], 16, 18, [10, 13, 14, 18]); + test([10, 13, 14, 17], 16, 19, [10, 13, 14, 19]); + test([10, 13, 14, 17], 17, 18, [10, 13, 14, 18]); + test([10, 13, 14, 17], 17, 19, [10, 13, 14, 19]); + test([10, 13, 14, 17], 18, 19, [10, 13, 14, 17, 18, 19]); + + test([10, 13, 16, 19], 7, 14, [ 7, 14, 16, 19]); + test([10, 13, 16, 19], 7, 15, [ 7, 15, 16, 19]); + test([10, 13, 16, 19], 7, 16, [ 7, 19]); + test([10, 13, 16, 19], 8, 14, [ 8, 14, 16, 19]); + test([10, 13, 16, 19], 8, 15, [ 8, 15, 16, 19]); + test([10, 13, 16, 19], 8, 16, [ 8, 19]); + test([10, 13, 16, 19], 9, 14, [ 9, 14, 16, 19]); + test([10, 13, 16, 19], 9, 15, [ 9, 15, 16, 19]); + test([10, 13, 16, 19], 9, 16, [ 9, 19]); + test([10, 13, 16, 19], 10, 14, [10, 14, 16, 19]); + test([10, 13, 16, 19], 10, 15, [10, 15, 16, 19]); + test([10, 13, 16, 19], 10, 16, [10, 19]); + test([10, 13, 16, 19], 11, 14, [10, 14, 16, 19]); + test([10, 13, 16, 19], 11, 15, [10, 15, 16, 19]); + test([10, 13, 16, 19], 11, 16, [10, 19]); + test([10, 13, 16, 19], 12, 14, [10, 14, 16, 19]); + test([10, 13, 16, 19], 12, 15, [10, 15, 16, 19]); + test([10, 13, 16, 19], 12, 16, [10, 19]); + test([10, 13, 16, 19], 13, 14, [10, 14, 16, 19]); + test([10, 13, 16, 19], 13, 15, [10, 15, 16, 19]); + test([10, 13, 16, 19], 13, 16, [10, 19]); + test([10, 13, 16, 19], 14, 15, [10, 13, 14, 15, 16, 19]); + test([10, 13, 16, 19], 14, 16, [10, 13, 14, 19]); + test([10, 13, 16, 19], 15, 16, [10, 13, 15, 19]); + + run_next_test(); +}); + +add_test(function test_ril_consts_cellbroadcast_misc() { + // Must be 16 for indexing. + equal(CB_DCS_LANG_GROUP_1.length, 16); + equal(CB_DCS_LANG_GROUP_2.length, 16); + + // Array length must be even. + equal(CB_NON_MMI_SETTABLE_RANGES.length & 0x01, 0); + for (let i = 0; i < CB_NON_MMI_SETTABLE_RANGES.length;) { + let from = CB_NON_MMI_SETTABLE_RANGES[i++]; + let to = CB_NON_MMI_SETTABLE_RANGES[i++]; + equal(from < to, true); + } + + run_next_test(); +}); + +add_test(function test_ril_worker_checkCellBroadcastMMISettable() { + let worker = newWorker({ + postRILMessage: function(data) { + // Do nothing + }, + postMessage: function(message) { + // Do nothing + } + }); + + let context = worker.ContextPool._contexts[0]; + let ril = context.RIL; + + function test(from, to, expected) { + equal(expected, ril._checkCellBroadcastMMISettable(from, to)); + } + + test(-2, -1, false); + test(-1, 0, false); + test(0, 1, true); + test(1, 1, false); + test(2, 1, false); + test(65536, 65537, false); + + // We have both [4096, 4224), [4224, 4352), so it's actually [4096, 4352), + // and [61440, 65536), [65535, 65536), so it's actually [61440, 65536). + for (let i = 0; i < CB_NON_MMI_SETTABLE_RANGES.length;) { + let from = CB_NON_MMI_SETTABLE_RANGES[i++]; + let to = CB_NON_MMI_SETTABLE_RANGES[i++]; + if ((from != 4224) && (from != 65535)) { + test(from - 1, from, true); + } + test(from - 1, from + 1, false); + test(from - 1, to, false); + test(from - 1, to + 1, false); + test(from, from + 1, false); + test(from, to, false); + test(from, to + 1, false); + if ((from + 1) < to) { + test(from + 1, to, false); + test(from + 1, to + 1, false); + } + if ((to != 4224) && (to < 65535)) { + test(to, to + 1, true); + test(to + 1, to + 2, true); + } + } + + run_next_test(); +}); + +add_test(function test_ril_worker_CellBroadcastDisabled() { + let count = 0; + let worker = newWorker({ + postRILMessage: function(data) { + // Do nothing + }, + postMessage: function(message) { + if (message.rilMessageType == "cellbroadcast-received") { + ok(true, "cellbroadcast-received: " + JSON.stringify(message)); + count++; + } + } + }); + + function buildPdu(aMessageId) { + return "C002" + aMessageId + "011154741914AFA7C76B9058" + + "FEBEBB41E6371EA4AEB7E173D0DB5E96" + + "83E8E832881DD6E741E4F7B9D168341A" + + "8D46A3D168341A8D46A3D168341A8D46" + + "A3D168341A8D46A3D168341A8D46A3D1" + + "68341A8D46A3D100"; + } + + worker.ContextPool._contexts[0].RIL.cellBroadcastDisabled = true; + + let networkAlertIds = [ + "1100", "1107", // ETWS + "1112", "112F", // CMAS + "1130", "18FF", // PWS + ]; + networkAlertIds.forEach(aMessageId => { + worker.onRILMessage( + 0, + newIncomingParcel( + -1, + RESPONSE_TYPE_UNSOLICITED, + UNSOLICITED_RESPONSE_NEW_BROADCAST_SMS, + hexStringToParcelByteArrayData(buildPdu(aMessageId)))); + }); + equal(count, networkAlertIds.length, "Alerts shall not be ignored."); + + count = 0; + let normalMsgIds = [ "0000", "03E7", "1108", "1901" ]; + normalMsgIds.forEach(aMessageId => { + worker.onRILMessage( + 0, + newIncomingParcel( + -1, + RESPONSE_TYPE_UNSOLICITED, + UNSOLICITED_RESPONSE_NEW_BROADCAST_SMS, + hexStringToParcelByteArrayData(buildPdu(aMessageId)))); + }); + equal(count, 0, "Normal messages shall be ignored."); + + run_next_test(); +}); diff --git a/dom/system/gonk/tests/test_ril_worker_cellbroadcast_gsm.js b/dom/system/gonk/tests/test_ril_worker_cellbroadcast_gsm.js new file mode 100644 index 000000000..b08b64135 --- /dev/null +++ b/dom/system/gonk/tests/test_ril_worker_cellbroadcast_gsm.js @@ -0,0 +1,230 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +subscriptLoader.loadSubScript("resource://gre/modules/ril_consts.js", this); + +function run_test() { + run_next_test(); +} + +add_test(function test_ril_worker_GsmPDUHelper_readCbDataCodingScheme() { + let worker = newWorker({ + postRILMessage: function(data) { + // Do nothing + }, + postMessage: function(message) { + // Do nothing + } + }); + + let context = worker.ContextPool._contexts[0]; + function test_dcs(dcs, encoding, language, hasLanguageIndicator, messageClass) { + context.Buf.readUint8 = function() { + return dcs; + }; + + let msg = {}; + context.GsmPDUHelper.readCbDataCodingScheme(msg); + + equal(msg.dcs, dcs); + equal(msg.encoding, encoding); + equal(msg.language, language); + equal(msg.hasLanguageIndicator, hasLanguageIndicator); + equal(msg.messageClass, messageClass); + } + + function test_dcs_throws(dcs) { + context.Buf.readUint8 = function() { + return dcs; + }; + + throws(function() { + context.GsmPDUHelper.readCbDataCodingScheme({}); + }, "Unsupported CBS data coding scheme: " + dcs); + } + + // Group 0000 + for (let i = 0; i < 16; i++) { + test_dcs(i, PDU_DCS_MSG_CODING_7BITS_ALPHABET, CB_DCS_LANG_GROUP_1[i], + false, GECKO_SMS_MESSAGE_CLASSES[PDU_DCS_MSG_CLASS_NORMAL]); + } + + // Group 0001 + // 0000 GSM 7 bit default alphabet; message preceded by language indication. + test_dcs(0x10, PDU_DCS_MSG_CODING_7BITS_ALPHABET, null, true, + GECKO_SMS_MESSAGE_CLASSES[PDU_DCS_MSG_CLASS_NORMAL]); + // 0001 UCS2; message preceded by language indication. + test_dcs(0x11, PDU_DCS_MSG_CODING_16BITS_ALPHABET, null, true, + GECKO_SMS_MESSAGE_CLASSES[PDU_DCS_MSG_CLASS_NORMAL]); + + // Group 0010 + // 0000..0100 + for (let i = 0; i < 5; i++) { + test_dcs(0x20 + i, PDU_DCS_MSG_CODING_7BITS_ALPHABET, + CB_DCS_LANG_GROUP_2[i], false, + GECKO_SMS_MESSAGE_CLASSES[PDU_DCS_MSG_CLASS_NORMAL]); + } + // 0101..1111 Reserved + for (let i = 5; i < 16; i++) { + test_dcs(0x20 + i, PDU_DCS_MSG_CODING_7BITS_ALPHABET, null, false, + GECKO_SMS_MESSAGE_CLASSES[PDU_DCS_MSG_CLASS_NORMAL]); + } + + // Group 0100, 0101, 1001 + for (let group of [0x40, 0x50, 0x90]) { + for (let i = 0; i < 16; i++) { + let encoding = i & 0x0C; + if (encoding == 0x0C) { + encoding = PDU_DCS_MSG_CODING_7BITS_ALPHABET; + } + let messageClass = GECKO_SMS_MESSAGE_CLASSES[i & PDU_DCS_MSG_CLASS_BITS]; + test_dcs(group + i, encoding, null, false, messageClass); + } + } + + // Group 1111 + for (let i = 0; i < 16; i ++) { + let encoding = i & 0x04 ? PDU_DCS_MSG_CODING_8BITS_ALPHABET + : PDU_DCS_MSG_CODING_7BITS_ALPHABET; + let messageClass; + switch(i & PDU_DCS_MSG_CLASS_BITS) { + case 0x01: messageClass = PDU_DCS_MSG_CLASS_USER_1; break; + case 0x02: messageClass = PDU_DCS_MSG_CLASS_USER_2; break; + case 0x03: messageClass = PDU_DCS_MSG_CLASS_3; break; + default: messageClass = PDU_DCS_MSG_CLASS_NORMAL; break; + } + test_dcs(0xF0 + i, encoding, null, false, + GECKO_SMS_MESSAGE_CLASSES[messageClass]); + } + + // Group 0011, 1000, 1010, 1011, 1100 + // 0000..1111 Reserved + for (let group of [0x30, 0x80, 0xA0, 0xB0, 0xC0]) { + for (let i = 0; i < 16; i++) { + test_dcs(group + i, PDU_DCS_MSG_CODING_7BITS_ALPHABET, null, false, + GECKO_SMS_MESSAGE_CLASSES[PDU_DCS_MSG_CLASS_NORMAL]); + } + } + + // Group 0110, 0111, 1101, 1110 + // TODO: unsupported + for (let group of [0x60, 0x70, 0xD0, 0xE0]) { + for (let i = 0; i < 16; i++) { + test_dcs_throws(group + i); + } + } + + run_next_test(); +}); + +add_test(function test_ril_worker_GsmPDUHelper_readGsmCbData() { + let worker = newWorker({ + postRILMessage: function(data) { + // Do nothing + }, + postMessage: function(message) { + // Do nothing + } + }); + + let context = worker.ContextPool._contexts[0]; + function test_data(options, expected) { + let readIndex = 0; + context.Buf.readUint8 = function() { + return options[3][readIndex++]; + }; + context.Buf.readUint8Array = function(length) { + let array = new Uint8Array(length); + for (let i = 0; i < length; i++) { + array[i] = this.readUint8(); + } + return array; + }; + + let msg = { + encoding: options[0], + language: options[1], + hasLanguageIndicator: options[2] + }; + context.GsmPDUHelper.readGsmCbData(msg, options[3].length); + + equal(msg.body, expected[0]); + equal(msg.data == null, expected[1] == null); + if (expected[1] != null) { + equal(msg.data.length, expected[1].length); + for (let i = 0; i < expected[1].length; i++) { + equal(msg.data[i], expected[1][i]); + } + } + equal(msg.language, expected[2]); + } + + // We're testing Cell Broadcast message body with all zeros octet stream. As + // shown in 3GPP TS 23.038, septet 0x00 will be decoded as '@' when both + // langTableIndex and langShiftTableIndex equal to + // PDU_DCS_MSG_CODING_7BITS_ALPHABET. + + // PDU_DCS_MSG_CODING_7BITS_ALPHABET + test_data([PDU_DCS_MSG_CODING_7BITS_ALPHABET, null, false, + [0]], + ["@", null, null]); + test_data([PDU_DCS_MSG_CODING_7BITS_ALPHABET, null, true, + [0, 0, 0, 0]], + ["@", null, "@@"]); + test_data([PDU_DCS_MSG_CODING_7BITS_ALPHABET, "@@", false, + [0]], + ["@", null, "@@"]); + + // PDU_DCS_MSG_CODING_8BITS_ALPHABET + test_data([PDU_DCS_MSG_CODING_8BITS_ALPHABET, null, false, + [0]], + [null, [0], null]); + + // PDU_DCS_MSG_CODING_16BITS_ALPHABET + test_data([PDU_DCS_MSG_CODING_16BITS_ALPHABET, null, false, + [0x00, 0x40]], + ["@", null, null]); + test_data([PDU_DCS_MSG_CODING_16BITS_ALPHABET, null, true, + [0x00, 0x00, 0x00, 0x40]], + ["@", null, "@@"]); + test_data([PDU_DCS_MSG_CODING_16BITS_ALPHABET, "@@", false, + [0x00, 0x40]], + ["@", null, "@@"]); + + run_next_test(); +}); + +add_test(function test_ril_worker_Sim_Download_Message() { + let worker = newWorker({ + postRILMessage: function(data) { + // Do nothing + }, + postMessage: function(message) { + ok(message.rilMessageType !== "cellbroadcast-received", + "Data-Download message shall be ignored."); + } + }); + + function buildPdu(aMessageId) { + return "C002" + aMessageId + "011154741914AFA7C76B9058" + + "FEBEBB41E6371EA4AEB7E173D0DB5E96" + + "83E8E832881DD6E741E4F7B9D168341A" + + "8D46A3D168341A8D46A3D168341A8D46" + + "A3D168341A8D46A3D168341A8D46A3D1" + + "68341A8D46A3D100"; + } + + ["1000", "107F", "1080", "10FF"].forEach(aMessageId => { + worker.onRILMessage( + 0, + newIncomingParcel( + -1, + RESPONSE_TYPE_UNSOLICITED, + UNSOLICITED_RESPONSE_NEW_BROADCAST_SMS, + hexStringToParcelByteArrayData(buildPdu(aMessageId)))); + }); + + ok(true, "All Data-Download Messages are ingored."); + + run_next_test(); +}); diff --git a/dom/system/gonk/tests/test_ril_worker_cellbroadcast_umts.js b/dom/system/gonk/tests/test_ril_worker_cellbroadcast_umts.js new file mode 100644 index 000000000..0380c4122 --- /dev/null +++ b/dom/system/gonk/tests/test_ril_worker_cellbroadcast_umts.js @@ -0,0 +1,105 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +subscriptLoader.loadSubScript("resource://gre/modules/ril_consts.js", this); + +function run_test() { + run_next_test(); +} + +function buildHexStr(aNum, aNumSemiOctets) { + let str = aNum.toString(16); + while (str.length < aNumSemiOctets) { + str = "0" + str; + } + return str; +} + +/** + * Verify GsmPDUHelper#readUmtsCbMessage with numOfPages from 1 to 15. + */ +add_test(function test_GsmPDUHelper_readUmtsCbMessage_MultiParts() { + let CB_UMTS_MESSAGE_PAGE_SIZE = 82; + let CB_MAX_CONTENT_PER_PAGE_7BIT = 93; + let workerHelper = newInterceptWorker(), + worker = workerHelper.worker, + context = worker.ContextPool._contexts[0], + GsmPDUHelper = context.GsmPDUHelper; + + function test_MultiParts(aNumOfPages) { + let pdu = buildHexStr(CB_UMTS_MESSAGE_TYPE_CBS, 2) // msg_type + + buildHexStr(0, 4) // skip msg_id + + buildHexStr(0, 4) // skip SN + + buildHexStr(0, 2) // skip dcs + + buildHexStr(aNumOfPages, 2); // set num_of_pages + for (let i = 1; i <= aNumOfPages; i++) { + pdu = pdu + buildHexStr(0, CB_UMTS_MESSAGE_PAGE_SIZE * 2) + + buildHexStr(CB_UMTS_MESSAGE_PAGE_SIZE, 2); // msg_info_length + } + + worker.onRILMessage(0, newIncomingParcel(-1, + RESPONSE_TYPE_UNSOLICITED, + UNSOLICITED_RESPONSE_NEW_BROADCAST_SMS, + hexStringToParcelByteArrayData(pdu))); + + let postedMessage = workerHelper.postedMessage; + equal("cellbroadcast-received", postedMessage.rilMessageType); + equal(postedMessage.fullBody.length, + aNumOfPages * CB_MAX_CONTENT_PER_PAGE_7BIT); + } + + [1, 5, 15].forEach(function(i) { + test_MultiParts(i); + }); + + run_next_test(); +}); + +/** + * Verify GsmPDUHelper#readUmtsCbMessage with 8bit encoded. + */ +add_test(function test_GsmPDUHelper_readUmtsCbMessage_Binary() { + let CB_UMTS_MESSAGE_PAGE_SIZE = 82; + let CB_MAX_CONTENT_PER_PAGE_7BIT = 93; + let TEXT_BINARY = "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + + "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + + "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + + "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + + "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + + "FFFF"; + let workerHelper = newInterceptWorker(), + worker = workerHelper.worker, + context = worker.ContextPool._contexts[0], + GsmPDUHelper = context.GsmPDUHelper; + + function test_MultiPartsBinary(aNumOfPages) { + let pdu = buildHexStr(CB_UMTS_MESSAGE_TYPE_CBS, 2) // msg_type + + buildHexStr(0, 4) // skip msg_id + + buildHexStr(0, 4) // skip SN + + buildHexStr(68, 2) // set DCS to 8bit data + + buildHexStr(aNumOfPages, 2); // set num_of_pages + for (let i = 1; i <= aNumOfPages; i++) { + pdu = pdu + TEXT_BINARY + + buildHexStr(CB_UMTS_MESSAGE_PAGE_SIZE, 2); // msg_info_length + } + + worker.onRILMessage(0, newIncomingParcel(-1, + RESPONSE_TYPE_UNSOLICITED, + UNSOLICITED_RESPONSE_NEW_BROADCAST_SMS, + hexStringToParcelByteArrayData(pdu))); + + let postedMessage = workerHelper.postedMessage; + equal("cellbroadcast-received", postedMessage.rilMessageType); + equal(postedMessage.fullData.length, + aNumOfPages * CB_UMTS_MESSAGE_PAGE_SIZE); + for (let i = 0; i < postedMessage.fullData.length; i++) { + equal(postedMessage.fullData[i], 255); + } + } + + [1, 5, 15].forEach(function(i) { + test_MultiPartsBinary(i); + }); + + run_next_test(); +}); diff --git a/dom/system/gonk/tests/test_ril_worker_cf.js b/dom/system/gonk/tests/test_ril_worker_cf.js new file mode 100644 index 000000000..b8db716b7 --- /dev/null +++ b/dom/system/gonk/tests/test_ril_worker_cf.js @@ -0,0 +1,126 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +subscriptLoader.loadSubScript("resource://gre/modules/ril_consts.js", this); + +function run_test() { + run_next_test(); +} + +function toaFromString(number) { + let worker = newWorker({ + postRILMessage: function(data) { + // Do nothing + }, + postMessage: function(message) { + // Do nothing + } + }); + let context = worker.ContextPool._contexts[0]; + return context.RIL._toaFromString(number); +} + +add_test(function test_toaFromString_empty() { + let retval = toaFromString(""); + + equal(retval, TOA_UNKNOWN); + + run_next_test(); +}); + +add_test(function test_toaFromString_undefined() { + let retval = toaFromString(); + + equal(retval, TOA_UNKNOWN); + + run_next_test(); +}); + +add_test(function test_toaFromString_unknown() { + let retval = toaFromString("666222333"); + + equal(retval, TOA_UNKNOWN); + + run_next_test(); +}); + +add_test(function test_toaFromString_international() { + let retval = toaFromString("+34666222333"); + + equal(retval, TOA_INTERNATIONAL); + + run_next_test(); +}); + +add_test(function test_setCallForward_unconditional() { + let workerHelper = newInterceptWorker(); + let worker = workerHelper.worker; + let context = worker.ContextPool._contexts[0]; + + context.RIL.setCallForward = function fakeSetCallForward(options) { + context.RIL[REQUEST_SET_CALL_FORWARD](0, {}); + }; + + context.RIL.setCallForward({ + action: CALL_FORWARD_ACTION_REGISTRATION, + reason: CALL_FORWARD_REASON_UNCONDITIONAL, + serviceClass: ICC_SERVICE_CLASS_VOICE, + number: "666222333", + timeSeconds: 10 + }); + + let postedMessage = workerHelper.postedMessage; + + equal(postedMessage.errorMsg, undefined); + + run_next_test(); +}); + +add_test(function test_queryCallForwardStatus_unconditional() { + let workerHelper = newInterceptWorker(); + let worker = workerHelper.worker; + let context = worker.ContextPool._contexts[0]; + + context.RIL.setCallForward = function fakeSetCallForward(options) { + context.RIL[REQUEST_SET_CALL_FORWARD](0, {}); + }; + + context.Buf.readInt32 = function fakeReadUint32() { + return context.Buf.int32Array.pop(); + }; + + context.Buf.readString = function fakeReadString() { + return "+34666222333"; + }; + + context.RIL.queryCallForwardStatus = function fakeQueryCallForward(options) { + context.Buf.int32Array = [ + 0, // rules.timeSeconds + 145, // rules.toa + 49, // rules.serviceClass + CALL_FORWARD_REASON_UNCONDITIONAL, // rules.reason + 1, // rules.active + 1 // rulesLength + ]; + context.RIL[REQUEST_QUERY_CALL_FORWARD_STATUS](1, {}); + }; + + context.RIL.queryCallForwardStatus({ + action: CALL_FORWARD_ACTION_QUERY_STATUS, + reason: CALL_FORWARD_REASON_UNCONDITIONAL, + serviceClass: ICC_SERVICE_CLASS_VOICE, + number: "666222333", + timeSeconds: 10 + }); + + let postedMessage = workerHelper.postedMessage; + + equal(postedMessage.errorMsg, undefined); + ok(Array.isArray(postedMessage.rules)); + do_print(postedMessage.rules.length); + equal(postedMessage.rules.length, 1); + ok(postedMessage.rules[0].active); + equal(postedMessage.rules[0].reason, CALL_FORWARD_REASON_UNCONDITIONAL); + equal(postedMessage.rules[0].number, "+34666222333"); + run_next_test(); +}); diff --git a/dom/system/gonk/tests/test_ril_worker_clip.js b/dom/system/gonk/tests/test_ril_worker_clip.js new file mode 100644 index 000000000..d1ce5f617 --- /dev/null +++ b/dom/system/gonk/tests/test_ril_worker_clip.js @@ -0,0 +1,59 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +subscriptLoader.loadSubScript("resource://gre/modules/ril_consts.js", this); + +function run_test() { + run_next_test(); +} + +add_test(function test_queryCLIP_provisioned() { + let workerHelper = newInterceptWorker(); + let worker = workerHelper.worker; + let context = worker.ContextPool._contexts[0]; + + context.Buf.readInt32 = function fakeReadUint32() { + return context.Buf.int32Array.pop(); + }; + + context.RIL.queryCLIP = function fakeQueryCLIP(options) { + context.Buf.int32Array = [ + 1, // CLIP provisioned. + 1 // Length. + ]; + context.RIL[REQUEST_QUERY_CLIP](1, {}); + }; + + context.RIL.queryCLIP({}); + + let postedMessage = workerHelper.postedMessage; + + equal(postedMessage.errorMsg, undefined); + equal(postedMessage.provisioned, 1); + run_next_test(); +}); + +add_test(function test_getCLIP_error_generic_failure_invalid_length() { + let workerHelper = newInterceptWorker(); + let worker = workerHelper.worker; + let context = worker.ContextPool._contexts[0]; + + context.Buf.readInt32 = function fakeReadUint32() { + return context.Buf.int32Array.pop(); + }; + + context.RIL.queryCLIP = function fakeQueryCLIP(options) { + context.Buf.int32Array = [ + 1, // CLIP provisioned. + 0 // Length. + ]; + context.RIL[REQUEST_QUERY_CLIP](1, {}); + }; + + context.RIL.queryCLIP({}); + + let postedMessage = workerHelper.postedMessage; + + equal(postedMessage.errorMsg, GECKO_ERROR_GENERIC_FAILURE); + run_next_test(); +}); diff --git a/dom/system/gonk/tests/test_ril_worker_clir.js b/dom/system/gonk/tests/test_ril_worker_clir.js new file mode 100644 index 000000000..5882a3c4c --- /dev/null +++ b/dom/system/gonk/tests/test_ril_worker_clir.js @@ -0,0 +1,122 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +subscriptLoader.loadSubScript("resource://gre/modules/ril_consts.js", this); + +// Calling line identification restriction constants. + +// Uses subscription default value. +const CLIR_DEFAULT = 0; +// Restricts CLI presentation. +const CLIR_INVOCATION = 1; +// Allows CLI presentation. +const CLIR_SUPPRESSION = 2; + +function run_test() { + run_next_test(); +} + +add_test(function test_setCLIR_success() { + let workerHelper = newInterceptWorker(); + let worker = workerHelper.worker; + let context = worker.ContextPool._contexts[0]; + + context.RIL.setCLIR = function fakeSetCLIR(options) { + context.RIL[REQUEST_SET_CLIR](0, { + rilMessageType: "setCLIR" + }); + }; + + context.RIL.setCLIR({ + clirMode: CLIR_DEFAULT + }); + + let postedMessage = workerHelper.postedMessage; + + equal(postedMessage.errorMsg, undefined); + + run_next_test(); +}); + +add_test(function test_setCLIR_generic_failure() { + let workerHelper = newInterceptWorker(); + let worker = workerHelper.worker; + let context = worker.ContextPool._contexts[0]; + + context.RIL.setCLIR = function fakeSetCLIR(options) { + context.RIL[REQUEST_SET_CLIR](0, { + rilMessageType: "setCLIR", + errorMsg: GECKO_ERROR_GENERIC_FAILURE + }); + }; + + context.RIL.setCLIR({ + clirMode: CLIR_DEFAULT + }); + + let postedMessage = workerHelper.postedMessage; + + equal(postedMessage.errorMsg, GECKO_ERROR_GENERIC_FAILURE); + + run_next_test(); +}); + +add_test(function test_getCLIR_n0_m1() { + let workerHelper = newInterceptWorker(); + let worker = workerHelper.worker; + let context = worker.ContextPool._contexts[0]; + + context.Buf.readInt32 = function fakeReadUint32() { + return context.Buf.int32Array.pop(); + }; + + context.RIL.getCLIR = function fakeGetCLIR(options) { + context.Buf.int32Array = [ + 1, // Presentation indicator is used according to the subscription + // of the CLIR service. + 0, // CLIR provisioned in permanent mode. + 2 // Length. + ]; + context.RIL[REQUEST_GET_CLIR](1, { + rilMessageType: "setCLIR" + }); + }; + + context.RIL.getCLIR({}); + + let postedMessage = workerHelper.postedMessage; + + equal(postedMessage.errorMsg, undefined); + equal(postedMessage.n, 0); + equal(postedMessage.m, 1); + run_next_test(); +}); + +add_test(function test_getCLIR_error_generic_failure_invalid_length() { + let workerHelper = newInterceptWorker(); + let worker = workerHelper.worker; + let context = worker.ContextPool._contexts[0]; + + context.Buf.readInt32 = function fakeReadUint32() { + return context.Buf.int32Array.pop(); + }; + + context.RIL.getCLIR = function fakeGetCLIR(options) { + context.Buf.int32Array = [ + 1, // Presentation indicator is used according to the subscription + // of the CLIR service. + 0, // CLIR provisioned in permanent mode. + 0 // Length (invalid one). + ]; + context.RIL[REQUEST_GET_CLIR](1, { + rilMessageType: "setCLIR" + }); + }; + + context.RIL.getCLIR({}); + + let postedMessage = workerHelper.postedMessage; + + equal(postedMessage.errorMsg, GECKO_ERROR_GENERIC_FAILURE); + run_next_test(); +}); diff --git a/dom/system/gonk/tests/test_ril_worker_cw.js b/dom/system/gonk/tests/test_ril_worker_cw.js new file mode 100644 index 000000000..efa8b5c21 --- /dev/null +++ b/dom/system/gonk/tests/test_ril_worker_cw.js @@ -0,0 +1,104 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +subscriptLoader.loadSubScript("resource://gre/modules/ril_consts.js", this); + +function run_test() { + run_next_test(); +} + +add_test(function test_setCallWaiting_success() { + let workerHelper = newInterceptWorker(); + let worker = workerHelper.worker; + let context = worker.ContextPool._contexts[0]; + + context.RIL.setCallWaiting = function fakeSetCallWaiting(options) { + context.RIL[REQUEST_SET_CALL_WAITING](0, {}); + }; + + context.RIL.setCallWaiting({ + enabled: true + }); + + let postedMessage = workerHelper.postedMessage; + + equal(postedMessage.errorMsg, undefined); + + run_next_test(); +}); + +add_test(function test_setCallWaiting_generic_failure() { + let workerHelper = newInterceptWorker(); + let worker = workerHelper.worker; + let context = worker.ContextPool._contexts[0]; + + context.RIL.setCallWaiting = function fakeSetCallWaiting(options) { + context.RIL[REQUEST_SET_CALL_WAITING](0, { + errorMsg: GECKO_ERROR_GENERIC_FAILURE + }); + }; + + context.RIL.setCallWaiting({ + enabled: true + }); + + let postedMessage = workerHelper.postedMessage; + + equal(postedMessage.errorMsg, GECKO_ERROR_GENERIC_FAILURE); + + run_next_test(); +}); + +add_test(function test_queryCallWaiting_success_enabled_true() { + let workerHelper = newInterceptWorker(); + let worker = workerHelper.worker; + let context = worker.ContextPool._contexts[0]; + + context.Buf.readInt32 = function fakeReadUint32() { + return context.Buf.int32Array.pop(); + }; + + context.RIL.queryCallWaiting = function fakeQueryCallWaiting(options) { + context.Buf.int32Array = [ + 1, // serviceClass + 1, // enabled + 2 // length + ]; + context.RIL[REQUEST_QUERY_CALL_WAITING](1, {}); + }; + + context.RIL.queryCallWaiting({}); + + let postedMessage = workerHelper.postedMessage; + + equal(postedMessage.errorMsg, undefined); + equal(postedMessage.serviceClass, 1); + run_next_test(); +}); + +add_test(function test_queryCallWaiting_success_enabled_false() { + let workerHelper = newInterceptWorker(); + let worker = workerHelper.worker; + let context = worker.ContextPool._contexts[0]; + + context.Buf.readInt32 = function fakeReadUint32() { + return context.Buf.int32Array.pop(); + }; + + context.RIL.queryCallWaiting = function fakeQueryCallWaiting(options) { + context.Buf.int32Array = [ + 1, // serviceClass + 0, // enabled + 2 // length + ]; + context.RIL[REQUEST_QUERY_CALL_WAITING](1, {}); + }; + + context.RIL.queryCallWaiting({}); + + let postedMessage = workerHelper.postedMessage; + + equal(postedMessage.errorMsg, undefined); + equal(postedMessage.serviceClass, ICC_SERVICE_CLASS_NONE); + run_next_test(); +}); diff --git a/dom/system/gonk/tests/test_ril_worker_ecm.js b/dom/system/gonk/tests/test_ril_worker_ecm.js new file mode 100644 index 000000000..d10cba9ec --- /dev/null +++ b/dom/system/gonk/tests/test_ril_worker_ecm.js @@ -0,0 +1,168 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +subscriptLoader.loadSubScript("resource://gre/modules/ril_consts.js", this); + +function run_test() { + run_next_test(); +} + +var timeoutCallback = null; +var timeoutDelayMs = 0; +const TIMER_ID = 1234; +const TIMEOUT_VALUE = 300000; // 5 mins. + +// No window in xpcshell-test. Create our own timer mechanism. + +function setTimeout(callback, timeoutMs) { + timeoutCallback = callback; + timeoutDelayMs = timeoutMs; + equal(timeoutMs, TIMEOUT_VALUE); + return TIMER_ID; +} + +function clearTimeout(timeoutId) { + equal(timeoutId, TIMER_ID); + timeoutCallback = null; +} + +function fireTimeout() { + notEqual(timeoutCallback, null); + if (timeoutCallback) { + timeoutCallback(); + timeoutCallback = null; + } +} + +add_test(function test_enter_emergencyCbMode() { + let workerHelper = newInterceptWorker(); + let worker = workerHelper.worker; + let context = worker.ContextPool._contexts[0]; + + // Do it twice. Should always send the event. + for (let i = 0; i < 2; ++i) { + context.RIL[UNSOLICITED_ENTER_EMERGENCY_CALLBACK_MODE](); + let postedMessage = workerHelper.postedMessage; + + // Should store the mode. + equal(context.RIL._isInEmergencyCbMode, true); + + // Should notify change. + equal(postedMessage.rilMessageType, "emergencyCbModeChange"); + equal(postedMessage.active, true); + equal(postedMessage.timeoutMs, TIMEOUT_VALUE); + + // Should start timer. + equal(context.RIL._exitEmergencyCbModeTimeoutID, TIMER_ID); + } + + run_next_test(); +}); + +add_test(function test_exit_emergencyCbMode() { + let workerHelper = newInterceptWorker(); + let worker = workerHelper.worker; + let context = worker.ContextPool._contexts[0]; + + context.RIL[UNSOLICITED_ENTER_EMERGENCY_CALLBACK_MODE](); + context.RIL[UNSOLICITED_EXIT_EMERGENCY_CALLBACK_MODE](); + let postedMessage = workerHelper.postedMessage; + + // Should store the mode. + equal(context.RIL._isInEmergencyCbMode, false); + + // Should notify change. + equal(postedMessage.rilMessageType, "emergencyCbModeChange"); + equal(postedMessage.active, false); + + // Should clear timer. + equal(context.RIL._exitEmergencyCbModeTimeoutID, null); + + run_next_test(); +}); + +add_test(function test_request_exit_emergencyCbMode_when_timeout() { + let workerHelper = newInterceptWorker(); + let worker = workerHelper.worker; + let context = worker.ContextPool._contexts[0]; + + context.RIL[UNSOLICITED_ENTER_EMERGENCY_CALLBACK_MODE](); + equal(context.RIL._isInEmergencyCbMode, true); + equal(context.RIL._exitEmergencyCbModeTimeoutID, TIMER_ID); + + let parcelTypes = []; + context.Buf.newParcel = function(type, options) { + parcelTypes.push(type); + }; + + // Timeout. + fireTimeout(); + + // Should clear timeout event. + equal(context.RIL._exitEmergencyCbModeTimeoutID, null); + + // Check indeed sent out REQUEST_EXIT_EMERGENCY_CALLBACK_MODE. + notEqual(parcelTypes.indexOf(REQUEST_EXIT_EMERGENCY_CALLBACK_MODE), -1); + + run_next_test(); +}); + +add_test(function test_request_exit_emergencyCbMode_when_dial() { + let workerHelper = newInterceptWorker(); + let worker = workerHelper.worker; + let context = worker.ContextPool._contexts[0]; + + context.RIL[UNSOLICITED_ENTER_EMERGENCY_CALLBACK_MODE](); + equal(context.RIL._isInEmergencyCbMode, true); + equal(context.RIL._exitEmergencyCbModeTimeoutID, TIMER_ID); + + let parcelTypes = []; + context.Buf.newParcel = function(type, options) { + parcelTypes.push(type); + }; + + // Dial non-emergency call. + context.RIL.dial({number: "0912345678", + isEmergency: false, + isDialEmergency: false}); + + // Should clear timeout event. + equal(context.RIL._exitEmergencyCbModeTimeoutID, null); + + // Check indeed sent out REQUEST_EXIT_EMERGENCY_CALLBACK_MODE. + notEqual(parcelTypes.indexOf(REQUEST_EXIT_EMERGENCY_CALLBACK_MODE), -1); + + run_next_test(); +}); + +add_test(function test_request_exit_emergencyCbMode_explicitly() { + let workerHelper = newInterceptWorker(); + let worker = workerHelper.worker; + let context = worker.ContextPool._contexts[0]; + + context.RIL[UNSOLICITED_ENTER_EMERGENCY_CALLBACK_MODE](); + equal(context.RIL._isInEmergencyCbMode, true); + equal(context.RIL._exitEmergencyCbModeTimeoutID, TIMER_ID); + + let parcelTypes = []; + context.Buf.newParcel = function(type, options) { + parcelTypes.push(type); + }; + + context.RIL.handleChromeMessage({rilMessageType: "exitEmergencyCbMode"}); + context.RIL[REQUEST_EXIT_EMERGENCY_CALLBACK_MODE](1, { + rilMessageType: "exitEmergencyCbMode" + }); + let postedMessage = workerHelper.postedMessage; + + // Should clear timeout event. + equal(context.RIL._exitEmergencyCbModeTimeoutID, null); + + // Check indeed sent out REQUEST_EXIT_EMERGENCY_CALLBACK_MODE. + notEqual(parcelTypes.indexOf(REQUEST_EXIT_EMERGENCY_CALLBACK_MODE), -1); + + // Send back the response. + equal(postedMessage.rilMessageType, "exitEmergencyCbMode"); + + run_next_test(); +}); diff --git a/dom/system/gonk/tests/test_ril_worker_icc_BerTlvHelper.js b/dom/system/gonk/tests/test_ril_worker_icc_BerTlvHelper.js new file mode 100644 index 000000000..89fcd874d --- /dev/null +++ b/dom/system/gonk/tests/test_ril_worker_icc_BerTlvHelper.js @@ -0,0 +1,87 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +subscriptLoader.loadSubScript("resource://gre/modules/ril_consts.js", this); + +function run_test() { + run_next_test(); +} + +// Test ICC_COMMAND_GET_RESPONSE with FCP template format. +/** + * Verify transparent structure with FCP template format. + */ +add_test(function test_fcp_template_for_transparent_structure() { + let worker = newUint8Worker(); + let context = worker.ContextPool._contexts[0]; + let pduHelper = context.GsmPDUHelper; + let berHelper = context.BerTlvHelper; + + let tag_test = [ + 0x62, + 0x22, + 0x82, 0x02, 0x41, 0x21, + 0x83, 0x02, 0x2F, 0xE2, + 0xA5, 0x09, 0xC1, 0x04, 0x40, 0x0F, 0xF5, 0x55, 0x92, 0x01, 0x00, + 0x8A, 0x01, 0x05, + 0x8B, 0x03, 0x2F, 0x06, 0x0B, + 0x80, 0x02, 0x00, 0x0A, + 0x88, 0x01, 0x10]; + + for (let i = 0; i < tag_test.length; i++) { + pduHelper.writeHexOctet(tag_test[i]); + } + + let berTlv = berHelper.decode(tag_test.length); + let iter = berTlv.value.values(); + let tlv = berHelper.searchForNextTag(BER_FCP_FILE_DESCRIPTOR_TAG, iter); + equal(tlv.value.fileStructure, UICC_EF_STRUCTURE[EF_STRUCTURE_TRANSPARENT]); + + tlv = berHelper.searchForNextTag(BER_FCP_FILE_IDENTIFIER_TAG, iter); + equal(tlv.value.fileId, 0x2FE2); + + tlv = berHelper.searchForNextTag(BER_FCP_FILE_SIZE_DATA_TAG, iter); + equal(tlv.value.fileSizeData, 0x0A); + + run_next_test(); +}); + +/** + * Verify linear fixed structure with FCP template format. + */ +add_test(function test_fcp_template_for_linear_fixed_structure() { + let worker = newUint8Worker(); + let context = worker.ContextPool._contexts[0]; + let pduHelper = context.GsmPDUHelper; + let berHelper = context.BerTlvHelper; + + let tag_test = [ + 0x62, + 0x1E, + 0x82, 0x05, 0x42, 0x21, 0x00, 0x1A, 0x01, + 0x83, 0x02, 0x6F, 0x40, + 0xA5, 0x03, 0x92, 0x01, 0x00, + 0x8A, 0x01, 0x07, + 0x8B, 0x03, 0x6F, 0x06, 0x02, + 0x80, 0x02, 0x00, 0x1A, + 0x88, 0x00]; + + for (let i = 0; i < tag_test.length; i++) { + pduHelper.writeHexOctet(tag_test[i]); + } + + let berTlv = berHelper.decode(tag_test.length); + let iter = berTlv.value.values(); + let tlv = berHelper.searchForNextTag(BER_FCP_FILE_DESCRIPTOR_TAG, iter); + equal(tlv.value.fileStructure, UICC_EF_STRUCTURE[EF_STRUCTURE_LINEAR_FIXED]); + equal(tlv.value.recordLength, 0x1A); + equal(tlv.value.numOfRecords, 0x01); + + tlv = berHelper.searchForNextTag(BER_FCP_FILE_IDENTIFIER_TAG, iter); + equal(tlv.value.fileId, 0x6F40); + + tlv = berHelper.searchForNextTag(BER_FCP_FILE_SIZE_DATA_TAG, iter); + equal(tlv.value.fileSizeData, 0x1A); + + run_next_test(); +}); diff --git a/dom/system/gonk/tests/test_ril_worker_icc_CardLock.js b/dom/system/gonk/tests/test_ril_worker_icc_CardLock.js new file mode 100644 index 000000000..dc7eb93b9 --- /dev/null +++ b/dom/system/gonk/tests/test_ril_worker_icc_CardLock.js @@ -0,0 +1,282 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +subscriptLoader.loadSubScript("resource://gre/modules/ril_consts.js", this); + +function run_test() { + run_next_test(); +} + +/** + * Verify RIL.iccGetCardLockEnabled + */ +add_test(function test_icc_get_card_lock_enabled() { + let worker = newUint8Worker(); + let context = worker.ContextPool._contexts[0]; + let buf = context.Buf; + let ril = context.RIL; + ril.aid = "123456789"; + + function do_test(aLock) { + const serviceClass = ICC_SERVICE_CLASS_VOICE | + ICC_SERVICE_CLASS_DATA | + ICC_SERVICE_CLASS_FAX; + + buf.sendParcel = function fakeSendParcel() { + // Request Type. + equal(this.readInt32(), REQUEST_QUERY_FACILITY_LOCK) + + // Token : we don't care. + this.readInt32(); + + // Data + let parcel = this.readStringList(); + equal(parcel.length, 4); + equal(parcel[0], GECKO_CARDLOCK_TO_FACILITY[aLock]); + equal(parcel[1], ""); + equal(parcel[2], serviceClass.toString()); + equal(parcel[3], ril.aid); + }; + + ril.iccGetCardLockEnabled({lockType: aLock}); + } + + do_test(GECKO_CARDLOCK_PIN) + do_test(GECKO_CARDLOCK_FDN) + + run_next_test(); +}); + +add_test(function test_path_id_for_spid_and_spn() { + let worker = newWorker({ + postRILMessage: function(data) { + // Do nothing + }, + postMessage: function(message) { + // Do nothing + }}); + let context = worker.ContextPool._contexts[0]; + let RIL = context.RIL; + let ICCFileHelper = context.ICCFileHelper; + + // Test SIM + RIL.appType = CARD_APPTYPE_SIM; + equal(ICCFileHelper.getEFPath(ICC_EF_SPDI), + EF_PATH_MF_SIM + EF_PATH_DF_GSM); + equal(ICCFileHelper.getEFPath(ICC_EF_SPN), + EF_PATH_MF_SIM + EF_PATH_DF_GSM); + + // Test USIM + RIL.appType = CARD_APPTYPE_USIM; + equal(ICCFileHelper.getEFPath(ICC_EF_SPDI), + EF_PATH_MF_SIM + EF_PATH_ADF_USIM); + equal(ICCFileHelper.getEFPath(ICC_EF_SPDI), + EF_PATH_MF_SIM + EF_PATH_ADF_USIM); + run_next_test(); +}); + +/** + * Verify RIL.iccSetCardLockEnabled + */ +add_test(function test_icc_set_card_lock_enabled() { + let worker = newUint8Worker(); + let context = worker.ContextPool._contexts[0]; + let buf = context.Buf; + let ril = context.RIL; + ril.aid = "123456789"; + + function do_test(aLock, aPassword, aEnabled) { + const serviceClass = ICC_SERVICE_CLASS_VOICE | + ICC_SERVICE_CLASS_DATA | + ICC_SERVICE_CLASS_FAX; + + buf.sendParcel = function fakeSendParcel() { + // Request Type. + equal(this.readInt32(), REQUEST_SET_FACILITY_LOCK); + + // Token : we don't care + this.readInt32(); + + // Data + let parcel = this.readStringList(); + equal(parcel.length, 5); + equal(parcel[0], GECKO_CARDLOCK_TO_FACILITY[aLock]); + equal(parcel[1], aEnabled ? "1" : "0"); + equal(parcel[2], aPassword); + equal(parcel[3], serviceClass.toString()); + equal(parcel[4], ril.aid); + }; + + ril.iccSetCardLockEnabled({ + lockType: aLock, + enabled: aEnabled, + password: aPassword}); + } + + do_test(GECKO_CARDLOCK_PIN, "1234", true); + do_test(GECKO_CARDLOCK_PIN, "1234", false); + do_test(GECKO_CARDLOCK_FDN, "4321", true); + do_test(GECKO_CARDLOCK_FDN, "4321", false); + + run_next_test(); +}); + +/** + * Verify RIL.iccChangeCardLockPassword + */ +add_test(function test_icc_change_card_lock_password() { + let worker = newUint8Worker(); + let context = worker.ContextPool._contexts[0]; + let buf = context.Buf; + let ril = context.RIL; + + + function do_test(aLock, aPassword, aNewPassword) { + let GECKO_CARDLOCK_TO_REQUEST = {}; + GECKO_CARDLOCK_TO_REQUEST[GECKO_CARDLOCK_PIN] = REQUEST_CHANGE_SIM_PIN; + GECKO_CARDLOCK_TO_REQUEST[GECKO_CARDLOCK_PIN2] = REQUEST_CHANGE_SIM_PIN2; + + buf.sendParcel = function fakeSendParcel() { + // Request Type. + equal(this.readInt32(), GECKO_CARDLOCK_TO_REQUEST[aLock]); + + // Token : we don't care + this.readInt32(); + + // Data + let parcel = this.readStringList(); + equal(parcel.length, 3); + equal(parcel[0], aPassword); + equal(parcel[1], aNewPassword); + equal(parcel[2], ril.aid); + }; + + ril.iccChangeCardLockPassword({ + lockType: aLock, + password: aPassword, + newPassword: aNewPassword}); + } + + do_test(GECKO_CARDLOCK_PIN, "1234", "4321"); + do_test(GECKO_CARDLOCK_PIN2, "1234", "4321"); + + run_next_test(); +}); + +/** + * Verify RIL.iccUnlockCardLock - PIN + */ +add_test(function test_icc_unlock_card_lock_pin() { + let worker = newUint8Worker(); + let context = worker.ContextPool._contexts[0]; + let ril = context.RIL; + let buf = context.Buf; + ril.aid = "123456789"; + + function do_test(aLock, aPassword) { + let GECKO_CARDLOCK_TO_REQUEST = {}; + GECKO_CARDLOCK_TO_REQUEST[GECKO_CARDLOCK_PIN] = REQUEST_ENTER_SIM_PIN; + GECKO_CARDLOCK_TO_REQUEST[GECKO_CARDLOCK_PIN2] = REQUEST_ENTER_SIM_PIN2; + + buf.sendParcel = function fakeSendParcel() { + // Request Type. + equal(this.readInt32(), GECKO_CARDLOCK_TO_REQUEST[aLock]); + + // Token : we don't care + this.readInt32(); + + // Data + let parcel = this.readStringList(); + equal(parcel.length, 2); + equal(parcel[0], aPassword); + equal(parcel[1], ril.aid); + }; + + ril.iccUnlockCardLock({ + lockType: aLock, + password: aPassword + }); + } + + do_test(GECKO_CARDLOCK_PIN, "1234"); + do_test(GECKO_CARDLOCK_PIN2, "1234"); + + run_next_test(); +}); + +/** + * Verify RIL.iccUnlockCardLock - PUK + */ +add_test(function test_icc_unlock_card_lock_puk() { + let worker = newUint8Worker(); + let context = worker.ContextPool._contexts[0]; + let ril = context.RIL; + let buf = context.Buf; + ril.aid = "123456789"; + + function do_test(aLock, aPassword, aNewPin) { + let GECKO_CARDLOCK_TO_REQUEST = {}; + GECKO_CARDLOCK_TO_REQUEST[GECKO_CARDLOCK_PUK] = REQUEST_ENTER_SIM_PUK; + GECKO_CARDLOCK_TO_REQUEST[GECKO_CARDLOCK_PUK2] = REQUEST_ENTER_SIM_PUK2; + + buf.sendParcel = function fakeSendParcel() { + // Request Type. + equal(this.readInt32(), GECKO_CARDLOCK_TO_REQUEST[aLock]); + + // Token : we don't care + this.readInt32(); + + // Data + let parcel = this.readStringList(); + equal(parcel.length, 3); + equal(parcel[0], aPassword); + equal(parcel[1], aNewPin); + equal(parcel[2], ril.aid); + }; + + ril.iccUnlockCardLock({ + lockType: aLock, + password: aPassword, + newPin: aNewPin + }); + } + + do_test(GECKO_CARDLOCK_PUK, "12345678", "1234"); + do_test(GECKO_CARDLOCK_PUK2, "12345678", "1234"); + + run_next_test(); +}); + +/** + * Verify RIL.iccUnlockCardLock - Depersonalization + */ +add_test(function test_icc_unlock_card_lock_depersonalization() { + let worker = newUint8Worker(); + let context = worker.ContextPool._contexts[0]; + let ril = context.RIL; + let buf = context.Buf; + + function do_test(aPassword) { + buf.sendParcel = function fakeSendParcel() { + // Request Type. + equal(this.readInt32(), REQUEST_ENTER_NETWORK_DEPERSONALIZATION_CODE); + + // Token : we don't care + this.readInt32(); + + // Data + let parcel = this.readStringList(); + equal(parcel.length, 1); + equal(parcel[0], aPassword); + }; + + ril.iccUnlockCardLock({ + lockType: GECKO_CARDLOCK_NCK, + password: aPassword + }); + } + + do_test("12345678"); + + run_next_test(); +}); diff --git a/dom/system/gonk/tests/test_ril_worker_icc_CardState.js b/dom/system/gonk/tests/test_ril_worker_icc_CardState.js new file mode 100644 index 000000000..788df5073 --- /dev/null +++ b/dom/system/gonk/tests/test_ril_worker_icc_CardState.js @@ -0,0 +1,210 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +subscriptLoader.loadSubScript("resource://gre/modules/ril_consts.js", this); + +function run_test() { + run_next_test(); +} + +add_test(function test_personalization_state() { + let worker = newUint8Worker(); + let context = worker.ContextPool._contexts[0]; + let ril = context.RIL; + + context.ICCRecordHelper.readICCID = function fakeReadICCID() {}; + + function testPersonalization(isCdma, cardPersoState, geckoCardState) { + let iccStatus = { + cardState: CARD_STATE_PRESENT, + gsmUmtsSubscriptionAppIndex: (!isCdma) ? 0 : -1, + cdmaSubscriptionAppIndex: (isCdma) ? 0 : -1, + apps: [ + { + app_state: CARD_APPSTATE_SUBSCRIPTION_PERSO, + perso_substate: cardPersoState + }], + }; + + ril._isCdma = isCdma; + ril._processICCStatus(iccStatus); + equal(ril.cardState, geckoCardState); + } + + // Test GSM personalization state. + testPersonalization(false, CARD_PERSOSUBSTATE_SIM_NETWORK, + Ci.nsIIcc.CARD_STATE_NETWORK_LOCKED); + testPersonalization(false, CARD_PERSOSUBSTATE_SIM_NETWORK_SUBSET, + Ci.nsIIcc.CARD_STATE_NETWORK_SUBSET_LOCKED); + testPersonalization(false, CARD_PERSOSUBSTATE_SIM_CORPORATE, + Ci.nsIIcc.CARD_STATE_CORPORATE_LOCKED); + testPersonalization(false, CARD_PERSOSUBSTATE_SIM_SERVICE_PROVIDER, + Ci.nsIIcc.CARD_STATE_SERVICE_PROVIDER_LOCKED); + testPersonalization(false, CARD_PERSOSUBSTATE_SIM_SIM, + Ci.nsIIcc.CARD_STATE_SIM_LOCKED); + testPersonalization(false, CARD_PERSOSUBSTATE_SIM_NETWORK_PUK, + Ci.nsIIcc.CARD_STATE_NETWORK_PUK_REQUIRED); + testPersonalization(false, CARD_PERSOSUBSTATE_SIM_NETWORK_SUBSET_PUK, + Ci.nsIIcc.CARD_STATE_NETWORK_SUBSET_PUK_REQUIRED); + testPersonalization(false, CARD_PERSOSUBSTATE_SIM_CORPORATE_PUK, + Ci.nsIIcc.CARD_STATE_CORPORATE_PUK_REQUIRED); + testPersonalization(false, CARD_PERSOSUBSTATE_SIM_SERVICE_PROVIDER_PUK, + Ci.nsIIcc.CARD_STATE_SERVICE_PROVIDER_PUK_REQUIRED); + testPersonalization(false, CARD_PERSOSUBSTATE_SIM_SIM_PUK, + Ci.nsIIcc.CARD_STATE_SIM_PUK_REQUIRED); + + testPersonalization(false, CARD_PERSOSUBSTATE_UNKNOWN, + Ci.nsIIcc.CARD_STATE_UNKNOWN); + testPersonalization(false, CARD_PERSOSUBSTATE_IN_PROGRESS, + Ci.nsIIcc.CARD_STATE_PERSONALIZATION_IN_PROGRESS); + testPersonalization(false, CARD_PERSOSUBSTATE_READY, + Ci.nsIIcc.CARD_STATE_PERSONALIZATION_READY); + + // Test CDMA personalization state. + testPersonalization(true, CARD_PERSOSUBSTATE_RUIM_NETWORK1, + Ci.nsIIcc.CARD_STATE_NETWORK1_LOCKED); + testPersonalization(true, CARD_PERSOSUBSTATE_RUIM_NETWORK2, + Ci.nsIIcc.CARD_STATE_NETWORK2_LOCKED); + testPersonalization(true, CARD_PERSOSUBSTATE_RUIM_HRPD, + Ci.nsIIcc.CARD_STATE_HRPD_NETWORK_LOCKED); + testPersonalization(true, CARD_PERSOSUBSTATE_RUIM_CORPORATE, + Ci.nsIIcc.CARD_STATE_RUIM_CORPORATE_LOCKED); + testPersonalization(true, CARD_PERSOSUBSTATE_RUIM_SERVICE_PROVIDER, + Ci.nsIIcc.CARD_STATE_RUIM_SERVICE_PROVIDER_LOCKED); + testPersonalization(true, CARD_PERSOSUBSTATE_RUIM_RUIM, + Ci.nsIIcc.CARD_STATE_RUIM_LOCKED); + testPersonalization(true, CARD_PERSOSUBSTATE_RUIM_NETWORK1_PUK, + Ci.nsIIcc.CARD_STATE_NETWORK1_PUK_REQUIRED); + testPersonalization(true, CARD_PERSOSUBSTATE_RUIM_NETWORK2_PUK, + Ci.nsIIcc.CARD_STATE_NETWORK2_PUK_REQUIRED); + testPersonalization(true, CARD_PERSOSUBSTATE_RUIM_HRPD_PUK, + Ci.nsIIcc.CARD_STATE_HRPD_NETWORK_PUK_REQUIRED); + testPersonalization(true, CARD_PERSOSUBSTATE_RUIM_CORPORATE_PUK, + Ci.nsIIcc.CARD_STATE_RUIM_CORPORATE_PUK_REQUIRED); + testPersonalization(true, CARD_PERSOSUBSTATE_RUIM_SERVICE_PROVIDER_PUK, + Ci.nsIIcc.CARD_STATE_RUIM_SERVICE_PROVIDER_PUK_REQUIRED); + testPersonalization(true, CARD_PERSOSUBSTATE_RUIM_RUIM_PUK, + Ci.nsIIcc.CARD_STATE_RUIM_PUK_REQUIRED); + + testPersonalization(true, CARD_PERSOSUBSTATE_UNKNOWN, + Ci.nsIIcc.CARD_STATE_UNKNOWN); + testPersonalization(true, CARD_PERSOSUBSTATE_IN_PROGRESS, + Ci.nsIIcc.CARD_STATE_PERSONALIZATION_IN_PROGRESS); + testPersonalization(true, CARD_PERSOSUBSTATE_READY, + Ci.nsIIcc.CARD_STATE_PERSONALIZATION_READY); + + run_next_test(); +}); + +/** + * Verify SIM app_state in _processICCStatus + */ +add_test(function test_card_app_state() { + let worker = newUint8Worker(); + let context = worker.ContextPool._contexts[0]; + let ril = context.RIL; + + context.ICCRecordHelper.readICCID = function fakeReadICCID() {}; + + function testCardAppState(cardAppState, geckoCardState) { + let iccStatus = { + cardState: CARD_STATE_PRESENT, + gsmUmtsSubscriptionAppIndex: 0, + apps: [ + { + app_state: cardAppState + }], + }; + + ril._processICCStatus(iccStatus); + equal(ril.cardState, geckoCardState); + } + + testCardAppState(CARD_APPSTATE_ILLEGAL, + Ci.nsIIcc.CARD_STATE_ILLEGAL); + testCardAppState(CARD_APPSTATE_PIN, + Ci.nsIIcc.CARD_STATE_PIN_REQUIRED); + testCardAppState(CARD_APPSTATE_PUK, + Ci.nsIIcc.CARD_STATE_PUK_REQUIRED); + testCardAppState(CARD_APPSTATE_READY, + Ci.nsIIcc.CARD_STATE_READY); + testCardAppState(CARD_APPSTATE_UNKNOWN, + Ci.nsIIcc.CARD_STATE_UNKNOWN); + testCardAppState(CARD_APPSTATE_DETECTED, + Ci.nsIIcc.CARD_STATE_UNKNOWN); + + run_next_test(); +}); + +/** + * Verify permanent blocked for ICC. + */ +add_test(function test_icc_permanent_blocked() { + let worker = newUint8Worker(); + let context = worker.ContextPool._contexts[0]; + let ril = context.RIL; + + context.ICCRecordHelper.readICCID = function fakeReadICCID() {}; + + function testPermanentBlocked(pin1_replaced, universalPINState, pin1) { + let iccStatus = { + cardState: CARD_STATE_PRESENT, + gsmUmtsSubscriptionAppIndex: 0, + universalPINState: universalPINState, + apps: [ + { + pin1_replaced: pin1_replaced, + pin1: pin1 + }] + }; + + ril._processICCStatus(iccStatus); + equal(ril.cardState, Ci.nsIIcc.CARD_STATE_PERMANENT_BLOCKED); + } + + testPermanentBlocked(1, + CARD_PINSTATE_ENABLED_PERM_BLOCKED, + CARD_PINSTATE_UNKNOWN); + testPermanentBlocked(1, + CARD_PINSTATE_ENABLED_PERM_BLOCKED, + CARD_PINSTATE_ENABLED_PERM_BLOCKED); + testPermanentBlocked(0, + CARD_PINSTATE_UNKNOWN, + CARD_PINSTATE_ENABLED_PERM_BLOCKED); + + run_next_test(); +}); + +/** + * Verify ICC without app index. + */ +add_test(function test_icc_without_app_index() { + const ICCID = "123456789"; + + let worker = newUint8Worker(); + let context = worker.ContextPool._contexts[0]; + let ril = context.RIL; + + let iccStatus = { + cardState: CARD_STATE_PRESENT, + gsmUmtsSubscriptionAppIndex: -1, + universalPINState: CARD_PINSTATE_DISABLED, + apps: [ + { + app_state: CARD_APPSTATE_READY + }] + }; + + context.ICCRecordHelper.readICCID = function fakeReadICCID() { + ril.iccInfo.iccid = ICCID; + }; + + ril._processICCStatus(iccStatus); + + // Should read icc id event if the app index is -1. + equal(ril.iccInfo.iccid, ICCID); + // cardState is "unknown" if the app index is -1. + equal(ril.cardState, GECKO_CARDSTATE_UNKNOWN); + + run_next_test(); +}); diff --git a/dom/system/gonk/tests/test_ril_worker_icc_GsmPDUHelper.js b/dom/system/gonk/tests/test_ril_worker_icc_GsmPDUHelper.js new file mode 100644 index 000000000..0d074da79 --- /dev/null +++ b/dom/system/gonk/tests/test_ril_worker_icc_GsmPDUHelper.js @@ -0,0 +1,79 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +subscriptLoader.loadSubScript("resource://gre/modules/ril_consts.js", this); + +function run_test() { + run_next_test(); +} + +/** + * Verify GsmPDUHelper.writeTimestamp + */ +add_test(function test_write_timestamp() { + let worker = newUint8Worker(); + let context = worker.ContextPool._contexts[0]; + let helper = context.GsmPDUHelper; + + // current date + let dateInput = new Date(); + let dateOutput = new Date(); + helper.writeTimestamp(dateInput); + dateOutput.setTime(helper.readTimestamp()); + + equal(dateInput.getFullYear(), dateOutput.getFullYear()); + equal(dateInput.getMonth(), dateOutput.getMonth()); + equal(dateInput.getDate(), dateOutput.getDate()); + equal(dateInput.getHours(), dateOutput.getHours()); + equal(dateInput.getMinutes(), dateOutput.getMinutes()); + equal(dateInput.getSeconds(), dateOutput.getSeconds()); + equal(dateInput.getTimezoneOffset(), dateOutput.getTimezoneOffset()); + + // 2034-01-23 12:34:56 -0800 GMT + let time = Date.UTC(2034, 1, 23, 12, 34, 56); + time = time - (8 * 60 * 60 * 1000); + dateInput.setTime(time); + helper.writeTimestamp(dateInput); + dateOutput.setTime(helper.readTimestamp()); + + equal(dateInput.getFullYear(), dateOutput.getFullYear()); + equal(dateInput.getMonth(), dateOutput.getMonth()); + equal(dateInput.getDate(), dateOutput.getDate()); + equal(dateInput.getHours(), dateOutput.getHours()); + equal(dateInput.getMinutes(), dateOutput.getMinutes()); + equal(dateInput.getSeconds(), dateOutput.getSeconds()); + equal(dateInput.getTimezoneOffset(), dateOutput.getTimezoneOffset()); + + run_next_test(); +}); + +/** + * Verify GsmPDUHelper.octectToBCD and GsmPDUHelper.BCDToOctet + */ +add_test(function test_octect_BCD() { + let worker = newUint8Worker(); + let context = worker.ContextPool._contexts[0]; + let helper = context.GsmPDUHelper; + + // 23 + let number = 23; + let octet = helper.BCDToOctet(number); + equal(helper.octetToBCD(octet), number); + + // 56 + number = 56; + octet = helper.BCDToOctet(number); + equal(helper.octetToBCD(octet), number); + + // 0x23 + octet = 0x23; + number = helper.octetToBCD(octet); + equal(helper.BCDToOctet(number), octet); + + // 0x56 + octet = 0x56; + number = helper.octetToBCD(octet); + equal(helper.BCDToOctet(number), octet); + + run_next_test(); +}); diff --git a/dom/system/gonk/tests/test_ril_worker_icc_ICCContactHelper.js b/dom/system/gonk/tests/test_ril_worker_icc_ICCContactHelper.js new file mode 100644 index 000000000..29b83b76a --- /dev/null +++ b/dom/system/gonk/tests/test_ril_worker_icc_ICCContactHelper.js @@ -0,0 +1,1042 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +subscriptLoader.loadSubScript("resource://gre/modules/ril_consts.js", this); + +function run_test() { + run_next_test(); +} + +/** + * Test error message returned in onerror for readICCContacts. + */ +add_test(function test_error_message_read_icc_contact () { + let worker = newUint8Worker(); + let context = worker.ContextPool._contexts[0]; + let ril = context.RIL; + + function do_test(options, expectedErrorMsg) { + ril.sendChromeMessage = function(message) { + equal(message.errorMsg, expectedErrorMsg); + } + ril.readICCContacts(options); + } + + // Error 1, didn't specify correct contactType. + do_test({}, CONTACT_ERR_REQUEST_NOT_SUPPORTED); + + // Error 2, specifying a non-supported contactType. + ril.appType = CARD_APPTYPE_USIM; + do_test({contactType: "foo"}, CONTACT_ERR_CONTACT_TYPE_NOT_SUPPORTED); + + // Error 3, suppose we update the supported PBR fields in USIM_PBR_FIELDS, + // but forget to add implemenetations for it. + USIM_PBR_FIELDS.push("pbc"); + do_test({contactType: GECKO_CARDCONTACT_TYPE_ADN}, + CONTACT_ERR_FIELD_NOT_SUPPORTED); + + run_next_test(); +}); + +/** + * Test error message returned in onerror for updateICCContact. + */ +add_test(function test_error_message_update_icc_contact() { + let worker = newUint8Worker(); + let context = worker.ContextPool._contexts[0]; + let ril = context.RIL; + + const ICCID = "123456789"; + ril.iccInfo.iccid = ICCID; + + function do_test(options, expectedErrorMsg) { + ril.sendChromeMessage = function(message) { + equal(message.errorMsg, expectedErrorMsg); + } + ril.updateICCContact(options); + } + + // Error 1, didn't specify correct contactType. + do_test({}, CONTACT_ERR_REQUEST_NOT_SUPPORTED); + + // Error 2, specifying a correct contactType, but without providing 'contact'. + do_test({contactType: GECKO_CARDCONTACT_TYPE_ADN}, + CONTACT_ERR_REQUEST_NOT_SUPPORTED); + + // Error 3, specifying a non-supported contactType. + ril.appType = CARD_APPTYPE_USIM; + do_test({contactType: GECKO_CARDCONTACT_TYPE_SDN, contact: {}}, + CONTACT_ERR_CONTACT_TYPE_NOT_SUPPORTED); + + // Error 4, without supplying pin2. + do_test({contactType: GECKO_CARDCONTACT_TYPE_FDN, + contact: {contactId: ICCID + "1"}}, + GECKO_ERROR_SIM_PIN2); + + // Error 5, No free record found in EF_ADN. + let record = context.ICCRecordHelper; + record.readPBR = function(onsuccess, onerror) { + onsuccess([{adn: {fileId: 0x4f3a}}]); + }; + + let io = context.ICCIOHelper; + io.loadLinearFixedEF = function(options) { + options.totalRecords = 1; + options.p1 = 1; + options.callback(options); + }; + + do_test({contactType: GECKO_CARDCONTACT_TYPE_ADN, contact: {}}, + CONTACT_ERR_NO_FREE_RECORD_FOUND); + + // Error 6, ICC IO Error. + io.loadLinearFixedEF = function(options) { + ril[REQUEST_SIM_IO](0, { + errorMsg: GECKO_ERROR_GENERIC_FAILURE + }); + }; + do_test({contactType: GECKO_CARDCONTACT_TYPE_ADN, + contact: {contactId: ICCID + "1"}}, + GECKO_ERROR_GENERIC_FAILURE); + + // Error 7, suppose we update the supported PBR fields in USIM_PBR_FIELDS, + // but forget to add implemenetations for it. + USIM_PBR_FIELDS.push("pbc"); + do_test({contactType: GECKO_CARDCONTACT_TYPE_ADN, + contact: {contactId: ICCID + "1"}}, + CONTACT_ERR_FIELD_NOT_SUPPORTED); + + // Error 8, EF_PBR doesn't exist. + record.readPBR = function(onsuccess, onerror) { + onsuccess([]); + }; + + do_test({contactType: GECKO_CARDCONTACT_TYPE_ADN, + contact: {contactId: ICCID + "1"}}, + CONTACT_ERR_CANNOT_ACCESS_PHONEBOOK); + + run_next_test(); +}); + +/** + * Verify ICCContactHelper.readICCContacts + */ +add_test(function test_read_icc_contacts() { + let worker = newUint8Worker(); + let context = worker.ContextPool._contexts[0]; + let record = context.ICCRecordHelper; + let contactHelper = context.ICCContactHelper; + let ril = context.RIL; + let test_data = [ + //Record 1. + { + comment: "Test read SIM adn contact", + rawData: { + simType: CARD_APPTYPE_SIM, + contactType: GECKO_CARDCONTACT_TYPE_ADN, + adnLike: [{recordId: 1, alphaId: "name", number: "111111"}], + }, + expectedContact: [{ + recordId: 1, + alphaId: "name", + number: "111111" + }], + }, + //Record 2. + { + comment: "Test read SIM fdn contact", + rawData: { + simType: CARD_APPTYPE_SIM, + contactType: GECKO_CARDCONTACT_TYPE_FDN, + adnLike: [{recordId: 1, alphaId: "name", number: "111111"}], + }, + expectedContact: [{ + recordId: 1, + alphaId: "name", + number: "111111" + }], + }, + //Record 3. + { + comment: "Test read USIM adn contact", + rawData: { + simType: CARD_APPTYPE_USIM, + contactType: GECKO_CARDCONTACT_TYPE_ADN, + pbrs: [{adn:{fileId: 0x6f3a}, email: {}, anr0: {}}], + adnLike: [{recordId: 1, alphaId: "name", number: "111111"}], + email: "hello@mail.com", + anr: "123456", + }, + expectedContact: [{ + pbrIndex: 0, + recordId: 1, + alphaId: "name", + number: "111111", + email: "hello@mail.com", + anr: ["123456"] + }], + }, + //Record 4. + { + comment: "Test read USIM adn contacts", + rawData: { + simType: CARD_APPTYPE_USIM, + contactType: GECKO_CARDCONTACT_TYPE_ADN, + pbrs: [{adn:{fileId: 0x6f3a}, email: {}, anr0: {}}, + {adn:{fileId: 0x6f3b}, email: {}, anr0: {}}], + adnLike: [{recordId: 1, alphaId: "name1", number: "111111"}, + {recordId: 2, alphaId: "name2", number: "222222"}], + email: "hello@mail.com", + anr: "123456", + }, + expectedContact: [ + { + pbrIndex: 0, + recordId: 1, + alphaId: "name1", + number: "111111", + email: "hello@mail.com", + anr: ["123456"] + }, { + pbrIndex: 0, + recordId: 2, + alphaId: "name2", + number: "222222", + email: "hello@mail.com", + anr: ["123456"] + }, { + pbrIndex: 1, + recordId: 1, + alphaId: "name1", + number: "111111", + email: "hello@mail.com", + anr: ["123456"] + }, { + pbrIndex: 1, + recordId: 2, + alphaId: "name2", + number: "222222", + email: "hello@mail.com", + anr: ["123456"] + } + ], + }, + //Record 5. + { + comment: "Test read USIM fdn contact", + rawData: { + simType: CARD_APPTYPE_USIM, + contactType: GECKO_CARDCONTACT_TYPE_FDN, + adnLike: [{recordId: 1, alphaId: "name", number: "111111"}], + }, + expectedContact: [{ + recordId: 1, + alphaId: "name", + number: "111111" + }], + }, + //Record 6. + { + comment: "Test read RUIM adn contact", + rawData: { + simType: CARD_APPTYPE_RUIM, + contactType: GECKO_CARDCONTACT_TYPE_ADN, + adnLike: [{recordId: 1, alphaId: "name", number: "111111"}], + }, + expectedContact: [{ + recordId: 1, + alphaId: "name", + number: "111111" + }], + }, + //Record 7. + { + comment: "Test read RUIM fdn contact", + rawData: { + simType: CARD_APPTYPE_RUIM, + contactType: GECKO_CARDCONTACT_TYPE_FDN, + adnLike: [{recordId: 1, alphaId: "name", number: "111111"}], + }, + expectedContact: [{ + recordId: 1, + alphaId: "name", + number: "111111" + }], + }, + //Record 8. + { + comment: "Test read RUIM adn contact with enhanced phone book", + rawData: { + simType: CARD_APPTYPE_RUIM, + contactType: GECKO_CARDCONTACT_TYPE_ADN, + pbrs: [{adn:{fileId: 0x6f3a}, email: {}, anr0: {}}], + adnLike: [{recordId: 1, alphaId: "name", number: "111111"}], + email: "hello@mail.com", + anr: "123456", + enhancedPhoneBook: true, + }, + expectedContact: [{ + pbrIndex: 0, + recordId: 1, + alphaId: "name", + number: "111111", + email: "hello@mail.com", + anr: ["123456"] + }], + }, + //Record 9. + { + comment: "Test read RUIM adn contacts with enhanced phone book", + rawData: { + simType: CARD_APPTYPE_RUIM, + contactType: GECKO_CARDCONTACT_TYPE_ADN, + pbrs: [{adn:{fileId: 0x6f3a}, email: {}, anr0: {}}, + {adn:{fileId: 0x6f3b}, email: {}, anr0: {}}], + adnLike: [{recordId: 1, alphaId: "name1", number: "111111"}, + {recordId: 2, alphaId: "name2", number: "222222"}], + email: "hello@mail.com", + anr: "123456", + enhancedPhoneBook: true, + }, + expectedContact: [ + { + pbrIndex: 0, + recordId: 1, + alphaId: "name1", + number: "111111", + email: "hello@mail.com", + anr: ["123456"] + }, { + pbrIndex: 0, + recordId: 2, + alphaId: "name2", + number: "222222", + email: "hello@mail.com", + anr: ["123456"] + }, { + pbrIndex: 1, + recordId: 1, + alphaId: "name1", + number: "111111", + email: "hello@mail.com", + anr: ["123456"] + }, { + pbrIndex: 1, + recordId: 2, + alphaId: "name2", + number: "222222", + email: "hello@mail.com", + anr: ["123456"] + } + ], + }, + ]; + + function do_test(aTestData, aExpectedContact) { + ril.appType = aTestData.simType; + ril._isCdma = (aTestData.simType === CARD_APPTYPE_RUIM); + ril.iccInfoPrivate.cst = (aTestData.enhancedPhoneBook) ? + [0x20, 0x0C, 0x0, 0x0, 0x0]: + [0x20, 0x00, 0x0, 0x0, 0x0]; + + ril.iccInfoPrivate.sst = (aTestData.simType === CARD_APPTYPE_SIM)? + [0x20, 0x0, 0x0, 0x0, 0x0]: + [0x2, 0x0, 0x0, 0x0, 0x0]; + + // Override some functions to test. + contactHelper.getContactFieldRecordId = function(pbr, contact, field, onsuccess, onerror) { + onsuccess(1); + }; + + record.readPBR = function readPBR(onsuccess, onerror) { + onsuccess(JSON.parse(JSON.stringify(aTestData.pbrs))); + }; + + record.readADNLike = function readADNLike(fileId, extFileId, onsuccess, onerror) { + onsuccess(JSON.parse(JSON.stringify(aTestData.adnLike))); + }; + + record.readEmail = function readEmail(fileId, fileType, recordNumber, onsuccess, onerror) { + onsuccess(aTestData.email); + }; + + record.readANR = function readANR(fileId, fileType, recordNumber, onsuccess, onerror) { + onsuccess(aTestData.anr); + }; + + let onsuccess = function onsuccess(contacts) { + for (let i = 0; i < contacts.length; i++) { + do_print("check contacts[" + i + "]:" + JSON.stringify(contacts[i])); + deepEqual(contacts[i], aExpectedContact[i]); + } + }; + + let onerror = function onerror(errorMsg) { + do_print("readICCContacts failed: " + errorMsg); + ok(false); + }; + + contactHelper.readICCContacts(aTestData.simType, aTestData.contactType, onsuccess, onerror); + } + + for (let i = 0; i < test_data.length; i++) { + do_print(test_data[i].comment); + do_test(test_data[i].rawData, test_data[i].expectedContact); + } + + run_next_test(); +}); + +/** + * Verify ICCContactHelper.updateICCContact with appType is CARD_APPTYPE_USIM. + */ +add_test(function test_update_icc_contact() { + const ADN_RECORD_ID = 100; + const ADN_SFI = 1; + const IAP_FILE_ID = 0x4f17; + const EMAIL_FILE_ID = 0x4f50; + const EMAIL_RECORD_ID = 20; + const ANR0_FILE_ID = 0x4f11; + const ANR0_RECORD_ID = 30; + const EXT_RECORD_ID = 0x01; + + let worker = newUint8Worker(); + let context = worker.ContextPool._contexts[0]; + let recordHelper = context.ICCRecordHelper; + let contactHelper = context.ICCContactHelper; + let ril = context.RIL; + + function do_test(aSimType, aContactType, aContact, aPin2, aFileType, aHaveIapIndex, aEnhancedPhoneBook) { + ril.appType = aSimType; + ril._isCdma = (aSimType === CARD_APPTYPE_RUIM); + ril.iccInfoPrivate.cst = (aEnhancedPhoneBook) ? [0x20, 0x0C, 0x28, 0x0, 0x20] + : [0x20, 0x0, 0x28, 0x0, 0x20]; + ril.iccInfoPrivate.sst = (aSimType === CARD_APPTYPE_SIM)? + [0x20, 0x0, 0x28, 0x0, 0x20]: + [0x16, 0x0, 0x0, 0x0, 0x0]; + + recordHelper.readPBR = function(onsuccess, onerror) { + if (aFileType === ICC_USIM_TYPE1_TAG) { + onsuccess([{ + adn: {fileId: ICC_EF_ADN}, + email: {fileId: EMAIL_FILE_ID, + fileType: ICC_USIM_TYPE1_TAG}, + anr0: {fileId: ANR0_FILE_ID, + fileType: ICC_USIM_TYPE1_TAG}, + ext1: {fileId: ICC_EF_EXT1} + + }]); + } else if (aFileType === ICC_USIM_TYPE2_TAG) { + onsuccess([{ + adn: {fileId: ICC_EF_ADN, + sfi: ADN_SFI}, + iap: {fileId: IAP_FILE_ID}, + email: {fileId: EMAIL_FILE_ID, + fileType: ICC_USIM_TYPE2_TAG, + indexInIAP: 0}, + anr0: {fileId: ANR0_FILE_ID, + fileType: ICC_USIM_TYPE2_TAG, + indexInIAP: 1}, + ext1: {fileId: ICC_EF_EXT1} + }]); + } + }; + + recordHelper.updateADNLike = function(fileId, extRecordNumber, contact, pin2, onsuccess, onerror) { + if (aContactType === GECKO_CARDCONTACT_TYPE_FDN) { + equal(fileId, ICC_EF_FDN); + } else if (aContactType === GECKO_CARDCONTACT_TYPE_ADN) { + equal(fileId, ICC_EF_ADN); + } + + if (aContact.number.length > ADN_MAX_NUMBER_DIGITS) { + equal(extRecordNumber, EXT_RECORD_ID); + } else { + equal(extRecordNumber, 0xff); + } + + equal(pin2, aPin2); + equal(contact.alphaId, aContact.alphaId); + equal(contact.number, aContact.number); + onsuccess({alphaId: contact.alphaId, + number: contact.number.substring(0, ADN_MAX_NUMBER_DIGITS)}); + }; + + recordHelper.getADNLikeExtensionRecordNumber = function(fileId, recordNumber, onsuccess, onerror) { + onsuccess(EXT_RECORD_ID); + }; + + recordHelper.updateExtension = function(fileId, recordNumber, number, onsuccess, onerror) { + onsuccess(); + }; + + recordHelper.findFreeRecordId = function(fileId, onsuccess, onerror) { + onsuccess(EXT_RECORD_ID); + }; + + recordHelper.cleanEFRecord = function(fileId, recordNumber, onsuccess, onerror) { + onsuccess(); + } + + recordHelper.readIAP = function(fileId, recordNumber, onsuccess, onerror) { + equal(fileId, IAP_FILE_ID); + equal(recordNumber, ADN_RECORD_ID); + onsuccess((aHaveIapIndex) ? [EMAIL_RECORD_ID, ANR0_RECORD_ID] + : [0xff, 0xff]); + }; + + recordHelper.updateIAP = function(fileId, recordNumber, iap, onsuccess, onerror) { + equal(fileId, IAP_FILE_ID); + equal(recordNumber, ADN_RECORD_ID); + onsuccess(); + }; + + recordHelper.updateEmail = function(pbr, recordNumber, email, adnRecordId, onsuccess, onerror) { + equal(pbr.email.fileId, EMAIL_FILE_ID); + if (pbr.email.fileType === ICC_USIM_TYPE1_TAG) { + equal(recordNumber, ADN_RECORD_ID); + } else if (pbr.email.fileType === ICC_USIM_TYPE2_TAG) { + equal(recordNumber, EMAIL_RECORD_ID); + } + equal(email, aContact.email); + onsuccess(email); + }; + + recordHelper.updateANR = function(pbr, recordNumber, number, adnRecordId, onsuccess, onerror) { + equal(pbr.anr0.fileId, ANR0_FILE_ID); + if (pbr.anr0.fileType === ICC_USIM_TYPE1_TAG) { + equal(recordNumber, ADN_RECORD_ID); + } else if (pbr.anr0.fileType === ICC_USIM_TYPE2_TAG) { + equal(recordNumber, ANR0_RECORD_ID); + } + if (Array.isArray(aContact.anr)) { + equal(number, aContact.anr[0]); + } + onsuccess(number); + }; + + recordHelper.findFreeRecordId = function(fileId, onsuccess, onerror) { + let recordId = 0; + if (fileId === EMAIL_FILE_ID) { + recordId = EMAIL_RECORD_ID; + } else if (fileId === ANR0_FILE_ID) { + recordId = ANR0_RECORD_ID; + } + onsuccess(recordId); + }; + + let isSuccess = false; + let onsuccess = function onsuccess(updatedContact) { + equal(ADN_RECORD_ID, updatedContact.recordId); + equal(aContact.alphaId, updatedContact.alphaId); + equal(aContact.number.substring(0, ADN_MAX_NUMBER_DIGITS + EXT_MAX_NUMBER_DIGITS), + updatedContact.number); + if ((aSimType == CARD_APPTYPE_USIM || aSimType == CARD_APPTYPE_RUIM) && + (aFileType == ICC_USIM_TYPE1_TAG || aFileType == ICC_USIM_TYPE2_TAG)) { + if (aContact.hasOwnProperty('email')) { + equal(aContact.email, updatedContact.email); + } + + if (aContact.hasOwnProperty('anr')) { + equal(aContact.anr[0], updatedContact.anr[0]); + } + } else { + equal(updatedContact.email, null); + equal(updatedContact.anr, null); + } + + do_print("updateICCContact success"); + isSuccess = true; + }; + + let onerror = function onerror(errorMsg) { + do_print("updateICCContact failed: " + errorMsg); + }; + + contactHelper.updateICCContact(aSimType, aContactType, aContact, aPin2, onsuccess, onerror); + ok(isSuccess); + } + + let contacts = [ + { + pbrIndex: 0, + recordId: ADN_RECORD_ID, + alphaId: "test", + number: "123456", + email: "test@mail.com", + anr: ["+654321"] + }, + // a contact without email and anr. + { + pbrIndex: 0, + recordId: ADN_RECORD_ID, + alphaId: "test2", + number: "123456", + }, + // a contact with email but no anr. + { + pbrIndex: 0, + recordId: ADN_RECORD_ID, + alphaId: "test3", + number: "123456", + email: "test@mail.com" + }, + // a contact with anr but no email. + { + pbrIndex: 0, + recordId: ADN_RECORD_ID, + alphaId: "test4", + number: "123456", + anr: ["+654321"] + }, + // a contact number over 20 digits. + { + pbrIndex: 0, + recordId: ADN_RECORD_ID, + alphaId: "test4", + number: "0123456789012345678901234567890123456789", + anr: ["+654321"] + }, + // a contact number over 40 digits. + { + pbrIndex: 0, + recordId: ADN_RECORD_ID, + alphaId: "test5", + number: "01234567890123456789012345678901234567890123456789", + anr: ["+654321"] + }]; + + for (let i = 0; i < contacts.length; i++) { + let contact = contacts[i]; + // SIM + do_print("Test update SIM adn contacts"); + do_test(CARD_APPTYPE_SIM, GECKO_CARDCONTACT_TYPE_ADN, contact); + + do_print("Test update SIM fdn contacts"); + do_test(CARD_APPTYPE_SIM, GECKO_CARDCONTACT_TYPE_FDN, contact, "1234"); + + // USIM + do_print("Test update USIM adn contacts"); + do_test(CARD_APPTYPE_USIM, GECKO_CARDCONTACT_TYPE_ADN, contact, null, + ICC_USIM_TYPE1_TAG); + do_test(CARD_APPTYPE_USIM, GECKO_CARDCONTACT_TYPE_ADN, contact, null, + ICC_USIM_TYPE2_TAG, true); + do_test(CARD_APPTYPE_USIM, GECKO_CARDCONTACT_TYPE_ADN, contact, null, + ICC_USIM_TYPE2_TAG, false); + + do_print("Test update USIM fdn contacts"); + do_test(CARD_APPTYPE_USIM, GECKO_CARDCONTACT_TYPE_FDN, contact, "1234"); + + // RUIM + do_print("Test update RUIM adn contacts"); + do_test(CARD_APPTYPE_RUIM, GECKO_CARDCONTACT_TYPE_ADN, contact); + + do_print("Test update RUIM fdn contacts"); + do_test(CARD_APPTYPE_RUIM, GECKO_CARDCONTACT_TYPE_FDN, contact, "1234"); + + // RUIM with enhanced phone book + do_print("Test update RUIM adn contacts with enhanced phone book"); + do_test(CARD_APPTYPE_RUIM, GECKO_CARDCONTACT_TYPE_ADN, contact, null, + ICC_USIM_TYPE1_TAG, null,true); + do_test(CARD_APPTYPE_RUIM, GECKO_CARDCONTACT_TYPE_ADN, contact, null, + ICC_USIM_TYPE2_TAG, true, true); + do_test(CARD_APPTYPE_RUIM, GECKO_CARDCONTACT_TYPE_ADN, contact, null, + ICC_USIM_TYPE2_TAG, false, true); + + do_print("Test update RUIM fdn contacts with enhanced phone book"); + do_test(CARD_APPTYPE_RUIM, GECKO_CARDCONTACT_TYPE_FDN, contact, "1234", + null, true); + } + + run_next_test(); +}); + +/** + * Verify ICCContactHelper.updateICCContact with appType is CARD_APPTYPE_USIM and + * insufficient space to store Type 2 USIM contact fields. + */ +add_test(function test_update_icc_contact_full_email_and_anr_field() { + const ADN_RECORD_ID = 100; + const ADN_SFI = 1; + const IAP_FILE_ID = 0x4f17; + const EMAIL_FILE_ID = 0x4f50; + const EMAIL_RECORD_ID = 20; + const ANR0_FILE_ID = 0x4f11; + const ANR0_RECORD_ID = 30; + + let worker = newUint8Worker(); + let context = worker.ContextPool._contexts[0]; + let recordHelper = context.ICCRecordHelper; + let contactHelper = context.ICCContactHelper; + let ril = context.RIL; + + function do_test(aSimType, aContactType, aContact, aPin2) { + ril.appType = CARD_APPTYPE_USIM; + ril.iccInfoPrivate.sst = [0x2, 0x0, 0x0, 0x0, 0x0]; + + recordHelper.readPBR = function(onsuccess, onerror) { + onsuccess([{ + adn: {fileId: ICC_EF_ADN, + sfi: ADN_SFI}, + iap: {fileId: IAP_FILE_ID}, + email: {fileId: EMAIL_FILE_ID, + fileType: ICC_USIM_TYPE2_TAG, + indexInIAP: 0}, + anr0: {fileId: ANR0_FILE_ID, + fileType: ICC_USIM_TYPE2_TAG, + indexInIAP: 1} + }]); + }; + + recordHelper.updateADNLike = function(fileId, extRecordNumber, contact, pin2, onsuccess, onerror) { + if (aContactType === GECKO_CARDCONTACT_TYPE_ADN) { + equal(fileId, ICC_EF_ADN); + } + equal(pin2, aPin2); + equal(contact.alphaId, aContact.alphaId); + equal(contact.number, aContact.number); + onsuccess({alphaId: contact.alphaId, + number: contact.number}); + }; + + recordHelper.readIAP = function(fileId, recordNumber, onsuccess, onerror) { + equal(fileId, IAP_FILE_ID); + equal(recordNumber, ADN_RECORD_ID); + onsuccess([0xff, 0xff]); + }; + + recordHelper.updateIAP = function(fileId, recordNumber, iap, onsuccess, onerror) { + equal(fileId, IAP_FILE_ID); + equal(recordNumber, ADN_RECORD_ID); + onsuccess(); + }; + + recordHelper.findFreeRecordId = function(fileId, onsuccess, onerror) { + let recordId = 0; + // emulate email and anr don't have free record. + if (fileId === EMAIL_FILE_ID || fileId === ANR0_FILE_ID) { + onerror(CONTACT_ERR_NO_FREE_RECORD_FOUND); + } else { + onsuccess(recordId); + } + }; + + let isSuccess = false; + let onsuccess = function onsuccess(updatedContact) { + equal(ADN_RECORD_ID, updatedContact.recordId); + equal(aContact.alphaId, updatedContact.alphaId); + equal(updatedContact.email, null); + equal(updatedContact.anr, null); + + do_print("updateICCContact success"); + isSuccess = true; + }; + + let onerror = function onerror(errorMsg) { + do_print("updateICCContact failed: " + errorMsg); + }; + + contactHelper.updateICCContact(aSimType, aContactType, aContact, aPin2, onsuccess, onerror); + ok(isSuccess); + } + + let contact = { + pbrIndex: 0, + recordId: ADN_RECORD_ID, + alphaId: "test", + number: "123456", + email: "test@mail.com", + anr: ["+654321"] + }; + + // USIM + do_print("Test update USIM adn contacts"); + do_test(CARD_APPTYPE_USIM, GECKO_CARDCONTACT_TYPE_ADN, contact, null); + + run_next_test(); +}); + +/** + * Verify updateICCContact with removal of anr and email with File Type 1. + */ +add_test(function test_update_icc_contact_with_remove_type1_attr() { + const ADN_RECORD_ID = 100; + const IAP_FILE_ID = 0x4f17; + const EMAIL_FILE_ID = 0x4f50; + const EMAIL_RECORD_ID = 20; + const ANR0_FILE_ID = 0x4f11; + const ANR0_RECORD_ID = 30; + + let worker = newUint8Worker(); + let context = worker.ContextPool._contexts[0]; + let recordHelper = context.ICCRecordHelper; + let contactHelper = context.ICCContactHelper; + + recordHelper.updateADNLike = function(fileId, extRecordNumber, contact, pin2, onsuccess, onerror) { + onsuccess({alphaId: contact.alphaId, + number: contact.number}); + }; + + let contact = { + pbrIndex: 0, + recordId: ADN_RECORD_ID, + alphaId: "test2", + number: "123456", + }; + + recordHelper.readIAP = function(fileId, recordNumber, onsuccess, onerror) { + onsuccess([EMAIL_RECORD_ID, ANR0_RECORD_ID]); + }; + + recordHelper.updateEmail = function(pbr, recordNumber, email, adnRecordId, onsuccess, onerror) { + ok(email == null); + onsuccess(email); + }; + + recordHelper.updateANR = function(pbr, recordNumber, number, adnRecordId, onsuccess, onerror) { + ok(number == null); + onsuccess(number); + }; + + function do_test(type) { + recordHelper.readPBR = function(onsuccess, onerror) { + if (type == ICC_USIM_TYPE1_TAG) { + onsuccess([{ + adn: {fileId: ICC_EF_ADN}, + email: {fileId: EMAIL_FILE_ID, + fileType: ICC_USIM_TYPE1_TAG}, + anr0: {fileId: ANR0_FILE_ID, + fileType: ICC_USIM_TYPE1_TAG}}]); + } else { + onsuccess([{ + adn: {fileId: ICC_EF_ADN}, + iap: {fileId: IAP_FILE_ID}, + email: {fileId: EMAIL_FILE_ID, + fileType: ICC_USIM_TYPE2_TAG, + indexInIAP: 0}, + anr0: {fileId: ANR0_FILE_ID, + fileType: ICC_USIM_TYPE2_TAG, + indexInIAP: 1}}]); + } + }; + + let successCb = function(updatedContact) { + equal(updatedContact.email, null); + equal(updatedContact.anr, null); + ok(true); + }; + + let errorCb = function(errorMsg) { + do_print(errorMsg); + ok(false); + }; + + contactHelper.updateICCContact(CARD_APPTYPE_USIM, + GECKO_CARDCONTACT_TYPE_ADN, + contact, null, successCb, errorCb); + } + + do_test(ICC_USIM_TYPE1_TAG); + do_test(ICC_USIM_TYPE2_TAG); + + run_next_test(); +}); + +/** + * Verify ICCContactHelper.findFreeICCContact in SIM + */ +add_test(function test_find_free_icc_contact_sim() { + let worker = newUint8Worker(); + let context = worker.ContextPool._contexts[0]; + let recordHelper = context.ICCRecordHelper; + let contactHelper = context.ICCContactHelper; + // Correct record Id starts with 1, so put a null element at index 0. + let records = [null]; + const MAX_RECORDS = 3; + const PBR_INDEX = 0; + + recordHelper.findFreeRecordId = function(fileId, onsuccess, onerror) { + if (records.length > MAX_RECORDS) { + onerror("No free record found."); + return; + } + + onsuccess(records.length); + }; + + let successCb = function(pbrIndex, recordId) { + equal(pbrIndex, PBR_INDEX); + records[recordId] = {}; + }; + + let errorCb = function(errorMsg) { + do_print(errorMsg); + ok(false); + }; + + for (let i = 0; i < MAX_RECORDS; i++) { + contactHelper.findFreeICCContact(CARD_APPTYPE_SIM, + GECKO_CARDCONTACT_TYPE_ADN, + successCb, errorCb); + } + // The 1st element, records[0], is null. + equal(records.length - 1, MAX_RECORDS); + + // Now the EF is full, so finding a free one should result failure. + successCb = function(pbrIndex, recordId) { + ok(false); + }; + + errorCb = function(errorMsg) { + ok(errorMsg === "No free record found."); + }; + contactHelper.findFreeICCContact(CARD_APPTYPE_SIM, GECKO_CARDCONTACT_TYPE_ADN, + successCb, errorCb); + + run_next_test(); +}); + +/** + * Verify ICCContactHelper.findFreeICCContact in USIM + */ +add_test(function test_find_free_icc_contact_usim() { + let worker = newUint8Worker(); + let context = worker.ContextPool._contexts[0]; + let recordHelper = context.ICCRecordHelper; + let contactHelper = context.ICCContactHelper; + const ADN1_FILE_ID = 0x6f3a; + const ADN2_FILE_ID = 0x6f3b; + const MAX_RECORDS = 3; + + // The adn in the first phonebook set has already two records, which means + // only 1 free record remained. + let pbrs = [{adn: {fileId: ADN1_FILE_ID, records: [null, {}, {}]}}, + {adn: {fileId: ADN2_FILE_ID, records: [null]}}]; + + recordHelper.readPBR = function readPBR(onsuccess, onerror) { + onsuccess(pbrs); + }; + + recordHelper.findFreeRecordId = function(fileId, onsuccess, onerror) { + let pbr = (fileId == ADN1_FILE_ID ? pbrs[0]: pbrs[1]); + if (pbr.adn.records.length > MAX_RECORDS) { + onerror("No free record found."); + return; + } + + onsuccess(pbr.adn.records.length); + }; + + let successCb = function(pbrIndex, recordId) { + equal(pbrIndex, 0); + pbrs[pbrIndex].adn.records[recordId] = {}; + }; + + let errorCb = function(errorMsg) { + ok(false); + }; + + contactHelper.findFreeICCContact(CARD_APPTYPE_USIM, + GECKO_CARDCONTACT_TYPE_ADN, + successCb, errorCb); + + // Now the EF_ADN in the 1st phonebook set is full, so the next free contact + // will come from the 2nd phonebook set. + successCb = function(pbrIndex, recordId) { + equal(pbrIndex, 1); + equal(recordId, 1); + } + contactHelper.findFreeICCContact(CARD_APPTYPE_USIM, + GECKO_CARDCONTACT_TYPE_ADN, + successCb, errorCb); + + run_next_test(); +}); + +/** + * Verify ICCContactHelper.updateADNLikeWithExtension + */ +add_test(function test_update_adn_like_with_extension() { + let worker = newUint8Worker(); + let context = worker.ContextPool._contexts[0]; + let ril = context.RIL; + let record = context.ICCRecordHelper; + let contactHelper = context.ICCContactHelper; + ril.appType = CARD_APPTYPE_SIM; + // Correct record Id starts from 1, so put a null element at index 0. + // ext_records contains data at index 1, and it only has 1 free record at index 2. + let notFree = 0x01; + let ext_records = [null, notFree, null]; + + function do_test(contact, extRecordNumber, expectedExtRecordNumber, expectedNumber, expectedCleanEFRecord) { + // Override some functions to test. + record.getADNLikeExtensionRecordNumber = function(fileId, recordNumber, onsuccess, onerror) { + onsuccess(extRecordNumber); + } + + record.updateADNLike = function(fileId, extRecordNumber, contact, pin2, onsuccess, onerror) { + equal(extRecordNumber, expectedExtRecordNumber); + onsuccess({alphaId: contact.alphaId, + number: contact.number.substring(0, ADN_MAX_NUMBER_DIGITS)}); + } + + record.updateExtension = function(fileId, recordNumber, number, onsuccess, onerror) { + if (recordNumber > ext_records.length) { + onerror("updateExtension failed."); + return; + } + ext_records[recordNumber] = number; + onsuccess(); + } + + record.findFreeRecordId = function(fileId, onsuccess, onerror) { + for (let i = 1; i < ext_records.length; i++) { + if (!ext_records[i]) { + onsuccess(i); + return; + } + } + + onerror("No free record found."); + } + + let isCleanEFRecord = false; + record.cleanEFRecord = function(fileId, recordNumber, onsuccess, onerror) { + if (recordNumber > ext_records.length) { + onerror("cleanEFRecord failed."); + return; + } + ext_records[recordNumber] = null; + isCleanEFRecord = true; + onsuccess(); + } + + let successCb = function successCb(updatedContact) { + equal(updatedContact.number, expectedNumber); + }; + + let errorCb = function errorCb(errorMsg) { + do_print("updateADNLikeWithExtension failed, msg = " + errorMsg); + ok(false); + }; + + contactHelper.updateADNLikeWithExtension(ICC_EF_ADN, ICC_EF_EXT1, contact, null, successCb, errorCb); + + if (expectedCleanEFRecord) { + ok(isCleanEFRecord); + } + } + + // Update extension record with previous extension record number. + do_test({recordId: 1, alphaId: "test", number: "001122334455667788991234"}, 0x01, 0x01, "001122334455667788991234"); + // Update extension record and find a free record. + do_test({recordId: 1, alphaId: "test", number: "001122334455667788995678"}, 0xff, 0x02, "001122334455667788995678"); + // Update extension record with no free extension record. + do_test({recordId: 1, alphaId: "test", number: "001122334455667788994321"}, 0xff, 0xff, "00112233445566778899"); + // Update extension record with clean previous extension record. + do_test({recordId: 1, alphaId: "test", number: "00112233445566778899"}, 0x01, 0xff, "00112233445566778899", true); + // Update extension record with no extension record and previous extension record. + do_test({recordId: 1, alphaId: "test", number: "00112233445566778899"}, 0xff, 0xff, "00112233445566778899"); + + run_next_test(); +});
\ No newline at end of file diff --git a/dom/system/gonk/tests/test_ril_worker_icc_ICCIOHelper.js b/dom/system/gonk/tests/test_ril_worker_icc_ICCIOHelper.js new file mode 100644 index 000000000..e690b1206 --- /dev/null +++ b/dom/system/gonk/tests/test_ril_worker_icc_ICCIOHelper.js @@ -0,0 +1,173 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +subscriptLoader.loadSubScript("resource://gre/modules/ril_consts.js", this); + +function run_test() { + run_next_test(); +} + +/** + * Verify ICCIOHelper.loadLinearFixedEF with recordSize. + */ +add_test(function test_load_linear_fixed_ef() { + let worker = newUint8Worker(); + let context = worker.ContextPool._contexts[0]; + let ril = context.RIL; + let io = context.ICCIOHelper; + + io.getResponse = function fakeGetResponse(options) { + // When recordSize is provided, loadLinearFixedEF should call iccIO directly. + ok(false); + run_next_test(); + }; + + ril.iccIO = function fakeIccIO(options) { + ok(true); + run_next_test(); + }; + + io.loadLinearFixedEF({recordSize: 0x20}); +}); + +/** + * Verify ICCIOHelper.loadLinearFixedEF without recordSize. + */ +add_test(function test_load_linear_fixed_ef() { + let worker = newUint8Worker(); + let context = worker.ContextPool._contexts[0]; + let ril = context.RIL; + let io = context.ICCIOHelper; + + io.getResponse = function fakeGetResponse(options) { + ok(true); + run_next_test(); + }; + + ril.iccIO = function fakeIccIO(options) { + // When recordSize is not provided, loadLinearFixedEF should call getResponse. + ok(false); + run_next_test(); + }; + + io.loadLinearFixedEF({}); +}); + +/** + * Verify ICC IO Error. + */ +add_test(function test_process_icc_io_error() { + let worker = newUint8Worker(); + let context = worker.ContextPool._contexts[0]; + let buf = context.Buf; + + function do_test(sw1, sw2, expectedErrorMsg) { + let called = false; + function errorCb(errorMsg) { + called = true; + equal(errorMsg, expectedErrorMsg); + } + + // Write sw1 and sw2 to buffer. + buf.writeInt32(sw1); + buf.writeInt32(sw2); + + context.RIL[REQUEST_SIM_IO](0, {fileId: 0xffff, + command: 0xff, + onerror: errorCb}); + + // onerror callback should be triggered. + ok(called); + } + + let TEST_DATA = [ + // [sw1, sw2, expectError] + [ICC_STATUS_ERROR_COMMAND_NOT_ALLOWED, 0xff, GECKO_ERROR_GENERIC_FAILURE], + [ICC_STATUS_ERROR_WRONG_PARAMETERS, 0xff, GECKO_ERROR_GENERIC_FAILURE], + ]; + + for (let i = 0; i < TEST_DATA.length; i++) { + do_test.apply(null, TEST_DATA[i]); + } + + run_next_test(); +}); + +/** + * Verify ICCIOHelper.processICCIOGetResponse for EF_TYPE_TRANSPARENT. + */ +add_test(function test_icc_io_get_response_for_transparent_structure() { + let worker = newUint8Worker(); + let context = worker.ContextPool._contexts[0]; + let buf = context.Buf; + let iccioHelper = context.ICCIOHelper; + let pduHelper = context.GsmPDUHelper; + + let responseArray = [ + // SIM response. + [0x00, 0x00, 0x00, 0x0A, 0x2F, 0xE2, 0x04, 0x00, 0x0A, 0xA0, 0xAA, 0x00, + 0x02, 0x00, 0x00], + // USIM response. + [0x62, 0x22, 0x82, 0x02, 0x41, 0x21, 0x83, 0x02, 0x2F, 0xE2, 0xA5, 0x09, + 0xC1, 0x04, 0x40, 0x0F, 0xF5, 0x55, 0x92, 0x01, 0x00, 0x8A, 0x01, 0x05, + 0x8B, 0x03, 0x2F, 0x06, 0x0B, 0x80, 0x02, 0x00, 0x0A, 0x88, 0x01, 0x10] + ]; + + for (let i = 0; i < responseArray.length; i++) { + let strLen = responseArray[i].length * 2; + buf.writeInt32(strLen); + for (let j = 0; j < responseArray[i].length; j++) { + pduHelper.writeHexOctet(responseArray[i][j]); + } + buf.writeStringDelimiter(strLen); + + let options = {fileId: ICC_EF_ICCID, + structure: EF_STRUCTURE_TRANSPARENT}; + iccioHelper.processICCIOGetResponse(options); + + equal(options.fileSize, 0x0A); + } + + run_next_test(); +}); + +/** + * Verify ICCIOHelper.processICCIOGetResponse for EF_TYPE_LINEAR_FIXED. + */ +add_test(function test_icc_io_get_response_for_linear_fixed_structure() { + let worker = newUint8Worker(); + let context = worker.ContextPool._contexts[0]; + let buf = context.Buf; + let iccioHelper = context.ICCIOHelper; + let pduHelper = context.GsmPDUHelper; + + let responseArray = [ + // SIM response. + [0x00, 0x00, 0x00, 0x1A, 0x6F, 0x40, 0x04, 0x00, 0x11, 0xA0, 0xAA, 0x00, + 0x02, 0x01, 0x1A], + // USIM response. + [0x62, 0x1E, 0x82, 0x05, 0x42, 0x21, 0x00, 0x1A, 0x01, 0x83, 0x02, 0x6F, + 0x40, 0xA5, 0x03, 0x92, 0x01, 0x00, 0x8A, 0x01, 0x07, 0x8B, 0x03, 0x6F, + 0x06, 0x02, 0x80, 0x02, 0x00, 0x1A, 0x88, 0x00] + ]; + + for (let i = 0; i < responseArray.length; i++) { + let strLen = responseArray[i].length * 2; + buf.writeInt32(strLen); + for (let j = 0; j < responseArray[i].length; j++) { + pduHelper.writeHexOctet(responseArray[i][j]); + } + buf.writeStringDelimiter(strLen); + + let options = {fileId: ICC_EF_MSISDN, + structure: EF_STRUCTURE_LINEAR_FIXED}; + iccioHelper.processICCIOGetResponse(options); + + equal(options.fileSize, 0x1A); + equal(options.recordSize, 0x1A); + equal(options.totalRecords, 0x01); + } + + run_next_test(); +}); + diff --git a/dom/system/gonk/tests/test_ril_worker_icc_ICCPDUHelper.js b/dom/system/gonk/tests/test_ril_worker_icc_ICCPDUHelper.js new file mode 100644 index 000000000..91495b1b7 --- /dev/null +++ b/dom/system/gonk/tests/test_ril_worker_icc_ICCPDUHelper.js @@ -0,0 +1,652 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +subscriptLoader.loadSubScript("resource://gre/modules/ril_consts.js", this); + +function run_test() { + run_next_test(); +} + +/** + * Verify ICCPDUHelper#readICCUCS2String() + */ +add_test(function test_read_icc_ucs2_string() { + let worker = newUint8Worker(); + let context = worker.ContextPool._contexts[0]; + let helper = context.GsmPDUHelper; + let iccHelper = context.ICCPDUHelper; + + // 0x80 + let text = "TEST"; + helper.writeUCS2String(text); + // Also write two unused octets. + let ffLen = 2; + for (let i = 0; i < ffLen; i++) { + helper.writeHexOctet(0xff); + } + equal(iccHelper.readICCUCS2String(0x80, (2 * text.length) + ffLen), text); + + // 0x81 + let array = [0x08, 0xd2, 0x4d, 0x6f, 0x7a, 0x69, 0x6c, 0x6c, 0x61, 0xca, + 0xff, 0xff]; + let len = array.length; + for (let i = 0; i < len; i++) { + helper.writeHexOctet(array[i]); + } + equal(iccHelper.readICCUCS2String(0x81, len), "Mozilla\u694a"); + + // 0x82 + let array2 = [0x08, 0x69, 0x00, 0x4d, 0x6f, 0x7a, 0x69, 0x6c, 0x6c, 0x61, + 0xca, 0xff, 0xff]; + let len2 = array2.length; + for (let i = 0; i < len2; i++) { + helper.writeHexOctet(array2[i]); + } + equal(iccHelper.readICCUCS2String(0x82, len2), "Mozilla\u694a"); + + run_next_test(); +}); + +/** + * Verify ICCPDUHelper#writeICCUCS2String() + */ +add_test(function test_write_icc_ucs2_string() { + let worker = newUint8Worker(); + let context = worker.ContextPool._contexts[0]; + let helper = context.GsmPDUHelper; + let iccHelper = context.ICCPDUHelper; + let alphaLen = 18; + let test_data = [ + { + encode: 0x80, + // string only contain one character. + data: "\u82b3" + }, { + encode: 0x80, + // 2 UCS2 character not located in the same half-page. + data: "Fire \u82b3\u8233" + }, { + encode: 0x80, + // 2 UCS2 character not located in the same half-page. + data: "\u694a\u704a" + }, { + encode: 0x81, + // 2 UCS2 character within same half-page. + data: "Fire \u6901\u697f" + }, { + encode: 0x81, + // 2 UCS2 character within same half-page. + data: "Fire \u6980\u69ff" + }, { + encode: 0x82, + // 2 UCS2 character within same half-page, but bit 8 is different. + data: "Fire \u0514\u0593" + }, { + encode: 0x82, + // 2 UCS2 character over 0x81 can encode range. + data: "Fire \u8000\u8001" + }, { + encode: 0x82, + // 2 UCS2 character over 0x81 can encode range. + data: "Fire \ufffd\ufffe" + }]; + + for (let i = 0; i < test_data.length; i++) { + let test = test_data[i]; + let writtenStr = iccHelper.writeICCUCS2String(alphaLen, test.data); + equal(writtenStr, test.data); + equal(helper.readHexOctet(), test.encode); + equal(iccHelper.readICCUCS2String(test.encode, alphaLen - 1), test.data); + } + + // This string use 0x80 encoded and the maximum capacity is 17 octets. + // Each alphabet takes 2 octets, thus the first 8 alphabets can be written. + let str = "Mozilla \u82b3\u8233 On Fire"; + let writtenStr = iccHelper.writeICCUCS2String(alphaLen, str); + equal(writtenStr, str.substring(0, 8)); + equal(helper.readHexOctet(), 0x80); + equal(iccHelper.readICCUCS2String(0x80, alphaLen - 1), str.substring(0, 8)); + + // This string use 0x81 encoded and the maximum capacity is 15 octets. + // Each alphabet takes 1 octets, thus the first 15 alphabets can be written. + str = "Mozilla \u6901\u697f On Fire"; + writtenStr = iccHelper.writeICCUCS2String(alphaLen, str); + equal(writtenStr, str.substring(0, 15)); + equal(helper.readHexOctet(), 0x81); + equal(iccHelper.readICCUCS2String(0x81, alphaLen - 1), str.substring(0, 15)); + + // This string use 0x82 encoded and the maximum capacity is 14 octets. + // Each alphabet takes 1 octets, thus the first 14 alphabets can be written. + str = "Mozilla \u0514\u0593 On Fire"; + writtenStr = iccHelper.writeICCUCS2String(alphaLen, str); + equal(writtenStr, str.substring(0, 14)); + equal(helper.readHexOctet(), 0x82); + equal(iccHelper.readICCUCS2String(0x82, alphaLen - 1), str.substring(0, 14)); + + run_next_test(); +}); +/** + * Verify ICCPDUHelper#readDiallingNumber + */ +add_test(function test_read_dialling_number() { + let worker = newUint8Worker(); + let context = worker.ContextPool._contexts[0]; + let helper = context.GsmPDUHelper; + let iccHelper = context.ICCPDUHelper; + let str = "123456789"; + + helper.readHexOctet = function() { + return 0x81; + }; + + helper.readSwappedNibbleExtendedBcdString = function(len) { + return str.substring(0, len); + }; + + for (let i = 0; i < str.length; i++) { + equal(str.substring(0, i - 1), // -1 for the TON + iccHelper.readDiallingNumber(i)); + } + + run_next_test(); +}); + +/** + * Verify ICCPDUHelper#read8BitUnpackedToString + */ +add_test(function test_read_8bit_unpacked_to_string() { + let worker = newUint8Worker(); + let context = worker.ContextPool._contexts[0]; + let helper = context.GsmPDUHelper; + let iccHelper = context.ICCPDUHelper; + const langTable = PDU_NL_LOCKING_SHIFT_TABLES[PDU_NL_IDENTIFIER_DEFAULT]; + const langShiftTable = PDU_NL_SINGLE_SHIFT_TABLES[PDU_NL_IDENTIFIER_DEFAULT]; + + // Test 1: Read GSM alphabets. + // Write alphabets before ESCAPE. + for (let i = 0; i < PDU_NL_EXTENDED_ESCAPE; i++) { + helper.writeHexOctet(i); + } + + // Write two ESCAPEs to make it become ' '. + helper.writeHexOctet(PDU_NL_EXTENDED_ESCAPE); + helper.writeHexOctet(PDU_NL_EXTENDED_ESCAPE); + + for (let i = PDU_NL_EXTENDED_ESCAPE + 1; i < langTable.length; i++) { + helper.writeHexOctet(i); + } + + // Also write two unused fields. + let ffLen = 2; + for (let i = 0; i < ffLen; i++) { + helper.writeHexOctet(0xff); + } + + equal(iccHelper.read8BitUnpackedToString(PDU_NL_EXTENDED_ESCAPE), + langTable.substring(0, PDU_NL_EXTENDED_ESCAPE)); + equal(iccHelper.read8BitUnpackedToString(2), " "); + equal(iccHelper.read8BitUnpackedToString(langTable.length - + PDU_NL_EXTENDED_ESCAPE - 1 + ffLen), + langTable.substring(PDU_NL_EXTENDED_ESCAPE + 1)); + + // Test 2: Read GSM extended alphabets. + for (let i = 0; i < langShiftTable.length; i++) { + helper.writeHexOctet(PDU_NL_EXTENDED_ESCAPE); + helper.writeHexOctet(i); + } + + // Read string before RESERVED_CONTROL. + equal(iccHelper.read8BitUnpackedToString(PDU_NL_RESERVED_CONTROL * 2), + langShiftTable.substring(0, PDU_NL_RESERVED_CONTROL)); + // ESCAPE + RESERVED_CONTROL will become ' '. + equal(iccHelper.read8BitUnpackedToString(2), " "); + // Read string between RESERVED_CONTROL and EXTENDED_ESCAPE. + equal(iccHelper.read8BitUnpackedToString( + (PDU_NL_EXTENDED_ESCAPE - PDU_NL_RESERVED_CONTROL - 1) * 2), + langShiftTable.substring(PDU_NL_RESERVED_CONTROL + 1, + PDU_NL_EXTENDED_ESCAPE)); + // ESCAPE + ESCAPE will become ' '. + equal(iccHelper.read8BitUnpackedToString(2), " "); + // Read remaining string. + equal(iccHelper.read8BitUnpackedToString( + (langShiftTable.length - PDU_NL_EXTENDED_ESCAPE - 1) * 2), + langShiftTable.substring(PDU_NL_EXTENDED_ESCAPE + 1)); + + run_next_test(); +}); + +/** + * Verify ICCPDUHelper#writeStringTo8BitUnpacked. + * + * Test writing GSM 8 bit alphabets. + */ +add_test(function test_write_string_to_8bit_unpacked() { + let worker = newUint8Worker(); + let context = worker.ContextPool._contexts[0]; + let helper = context.GsmPDUHelper; + let iccHelper = context.ICCPDUHelper; + const langTable = PDU_NL_LOCKING_SHIFT_TABLES[PDU_NL_IDENTIFIER_DEFAULT]; + const langShiftTable = PDU_NL_SINGLE_SHIFT_TABLES[PDU_NL_IDENTIFIER_DEFAULT]; + // Length of trailing 0xff. + let ffLen = 2; + let str; + + // Test 1, write GSM alphabets. + let writtenStr = iccHelper.writeStringTo8BitUnpacked(langTable.length + ffLen, langTable); + equal(writtenStr, langTable); + + for (let i = 0; i < langTable.length; i++) { + equal(helper.readHexOctet(), i); + } + + for (let i = 0; i < ffLen; i++) { + equal(helper.readHexOctet(), 0xff); + } + + // Test 2, write GSM extended alphabets. + str = "\u000c\u20ac"; + writtenStr = iccHelper.writeStringTo8BitUnpacked(4, str); + equal(writtenStr, str); + equal(iccHelper.read8BitUnpackedToString(4), str); + + // Test 3, write GSM and GSM extended alphabets. + // \u000c, \u20ac are from gsm extended alphabets. + // \u00a3 is from gsm alphabet. + str = "\u000c\u20ac\u00a3"; + + // 2 octets * 2 = 4 octets for 2 gsm extended alphabets, + // 1 octet for 1 gsm alphabet, + // 2 octes for trailing 0xff. + // "Totally 7 octets are to be written." + writtenStr = iccHelper.writeStringTo8BitUnpacked(7, str); + equal(writtenStr, str); + equal(iccHelper.read8BitUnpackedToString(7), str); + + run_next_test(); +}); + +/** + * Verify ICCPDUHelper#writeStringTo8BitUnpacked with maximum octets written. + */ +add_test(function test_write_string_to_8bit_unpacked_with_max_octets_written() { + let worker = newUint8Worker(); + let context = worker.ContextPool._contexts[0]; + let helper = context.GsmPDUHelper; + let iccHelper = context.ICCPDUHelper; + const langTable = PDU_NL_LOCKING_SHIFT_TABLES[PDU_NL_IDENTIFIER_DEFAULT]; + const langShiftTable = PDU_NL_SINGLE_SHIFT_TABLES[PDU_NL_IDENTIFIER_DEFAULT]; + + // The maximum of the number of octets that can be written is 3. + // Only 3 characters shall be written even the length of the string is 4. + let writtenStr = iccHelper.writeStringTo8BitUnpacked(3, langTable.substring(0, 4)); + equal(writtenStr, langTable.substring(0, 3)); + helper.writeHexOctet(0xff); // dummy octet. + for (let i = 0; i < 3; i++) { + equal(helper.readHexOctet(), i); + } + ok(helper.readHexOctet() != 4); + + // \u000c is GSM extended alphabet, 2 octets. + // \u00a3 is GSM alphabet, 1 octet. + let str = "\u000c\u00a3"; + writtenStr = iccHelper.writeStringTo8BitUnpacked(3, str); + equal(writtenStr, str.substring(0, 2)); + equal(iccHelper.read8BitUnpackedToString(3), str); + + str = "\u00a3\u000c"; + writtenStr = iccHelper.writeStringTo8BitUnpacked(3, str); + equal(writtenStr, str.substring(0, 2)); + equal(iccHelper.read8BitUnpackedToString(3), str); + + // 2 GSM extended alphabets cost 4 octets, but maximum is 3, so only the 1st + // alphabet can be written. + str = "\u000c\u000c"; + writtenStr = iccHelper.writeStringTo8BitUnpacked(3, str); + helper.writeHexOctet(0xff); // dummy octet. + equal(writtenStr, str.substring(0, 1)); + equal(iccHelper.read8BitUnpackedToString(4), str.substring(0, 1)); + + run_next_test(); +}); + +/** + * Verify ICCPDUHelper.readAlphaIdentifier + */ +add_test(function test_read_alpha_identifier() { + let worker = newUint8Worker(); + let context = worker.ContextPool._contexts[0]; + let helper = context.GsmPDUHelper; + let iccHelper = context.ICCPDUHelper; + + // UCS2: 0x80 + let text = "TEST"; + helper.writeHexOctet(0x80); + helper.writeUCS2String(text); + // Also write two unused octets. + let ffLen = 2; + for (let i = 0; i < ffLen; i++) { + helper.writeHexOctet(0xff); + } + equal(iccHelper.readAlphaIdentifier(1 + (2 * text.length) + ffLen), text); + + // UCS2: 0x81 + let array = [0x81, 0x08, 0xd2, 0x4d, 0x6f, 0x7a, 0x69, 0x6c, 0x6c, 0x61, 0xca, 0xff, 0xff]; + for (let i = 0; i < array.length; i++) { + helper.writeHexOctet(array[i]); + } + equal(iccHelper.readAlphaIdentifier(array.length), "Mozilla\u694a"); + + // UCS2: 0x82 + let array2 = [0x82, 0x08, 0x69, 0x00, 0x4d, 0x6f, 0x7a, 0x69, 0x6c, 0x6c, 0x61, 0xca, 0xff, 0xff]; + for (let i = 0; i < array2.length; i++) { + helper.writeHexOctet(array2[i]); + } + equal(iccHelper.readAlphaIdentifier(array2.length), "Mozilla\u694a"); + + // GSM 8 Bit Unpacked + const langTable = PDU_NL_LOCKING_SHIFT_TABLES[PDU_NL_IDENTIFIER_DEFAULT]; + for (let i = 0; i < PDU_NL_EXTENDED_ESCAPE; i++) { + helper.writeHexOctet(i); + } + equal(iccHelper.readAlphaIdentifier(PDU_NL_EXTENDED_ESCAPE), + langTable.substring(0, PDU_NL_EXTENDED_ESCAPE)); + + run_next_test(); +}); + +/** + * Verify ICCPDUHelper.writeAlphaIdentifier + */ +add_test(function test_write_alpha_identifier() { + let worker = newUint8Worker(); + let context = worker.ContextPool._contexts[0]; + let helper = context.GsmPDUHelper; + let iccHelper = context.ICCPDUHelper; + // Length of trailing 0xff. + let ffLen = 2; + + // Removal + let writenAlphaId = iccHelper.writeAlphaIdentifier(10, null); + equal(writenAlphaId, ""); + equal(iccHelper.readAlphaIdentifier(10), ""); + + // GSM 8 bit + let str = "Mozilla"; + writenAlphaId = iccHelper.writeAlphaIdentifier(str.length + ffLen, str); + equal(writenAlphaId , str); + equal(iccHelper.readAlphaIdentifier(str.length + ffLen), str); + + // UCS2 + str = "Mozilla\u8000"; + writenAlphaId = iccHelper.writeAlphaIdentifier(str.length * 2 + ffLen, str); + equal(writenAlphaId , str); + // * 2 for each character will be encoded to UCS2 alphabets. + equal(iccHelper.readAlphaIdentifier(str.length * 2 + ffLen), str); + + // Test with maximum octets written. + // 1 coding scheme (0x80) and 1 UCS2 character, total 3 octets. + str = "\u694a"; + writenAlphaId = iccHelper.writeAlphaIdentifier(3, str); + equal(writenAlphaId , str); + equal(iccHelper.readAlphaIdentifier(3), str); + + // 1 coding scheme (0x80) and 2 UCS2 characters, total 5 octets. + // numOctets is limited to 4, so only 1 UCS2 character can be written. + str = "\u694a\u69ca"; + writenAlphaId = iccHelper.writeAlphaIdentifier(4, str); + helper.writeHexOctet(0xff); // dummy octet. + equal(writenAlphaId , str.substring(0, 1)); + equal(iccHelper.readAlphaIdentifier(5), str.substring(0, 1)); + + // Write 0 octet. + writenAlphaId = iccHelper.writeAlphaIdentifier(0, "1"); + helper.writeHexOctet(0xff); // dummy octet. + equal(writenAlphaId, ""); + equal(iccHelper.readAlphaIdentifier(1), ""); + + run_next_test(); +}); + +/** + * Verify ICCPDUHelper.readAlphaIdDiallingNumber + */ +add_test(function test_read_alpha_id_dialling_number() { + let worker = newUint8Worker(); + let context = worker.ContextPool._contexts[0]; + let helper = context.GsmPDUHelper; + let iccHelper = context.ICCPDUHelper; + let buf = context.Buf; + const recordSize = 32; + + function testReadAlphaIdDiallingNumber(contact) { + iccHelper.readAlphaIdentifier = function() { + return contact.alphaId; + }; + + iccHelper.readNumberWithLength = function() { + return contact.number; + }; + + let strLen = recordSize * 2; + buf.writeInt32(strLen); // fake length + helper.writeHexOctet(0xff); // fake CCP + helper.writeHexOctet(0xff); // fake EXT1 + buf.writeStringDelimiter(strLen); + + let contactR = iccHelper.readAlphaIdDiallingNumber(recordSize); + if (contact.alphaId == "" && contact.number == "") { + equal(contactR, null); + } else { + equal(contactR.alphaId, contact.alphaId); + equal(contactR.number, contact.number); + } + } + + testReadAlphaIdDiallingNumber({alphaId: "AlphaId", number: "0987654321"}); + testReadAlphaIdDiallingNumber({alphaId: "", number: ""}); + + run_next_test(); +}); + +/** + * Verify ICCPDUHelper.writeAlphaIdDiallingNumber + */ +add_test(function test_write_alpha_id_dialling_number() { + let worker = newUint8Worker(); + let context = worker.ContextPool._contexts[0]; + let helper = context.ICCPDUHelper; + const recordSize = 32; + + // Write a normal contact. + let contactW = { + alphaId: "Mozilla", + number: "1234567890" + }; + + let writtenContact = helper.writeAlphaIdDiallingNumber(recordSize, + contactW.alphaId, + contactW.number, 0xff); + + let contactR = helper.readAlphaIdDiallingNumber(recordSize); + equal(writtenContact.alphaId, contactR.alphaId); + equal(writtenContact.number, contactR.number); + equal(0xff, contactR.extRecordNumber); + + // Write a contact with alphaId encoded in UCS2 and number has '+'. + let contactUCS2 = { + alphaId: "火狐", + number: "+1234567890" + }; + + writtenContact = helper.writeAlphaIdDiallingNumber(recordSize, + contactUCS2.alphaId, + contactUCS2.number, 0xff); + contactR = helper.readAlphaIdDiallingNumber(recordSize); + equal(writtenContact.alphaId, contactR.alphaId); + equal(writtenContact.number, contactR.number); + equal(0xff, contactR.extRecordNumber); + + // Write a null contact (Removal). + writtenContact = helper.writeAlphaIdDiallingNumber(recordSize); + contactR = helper.readAlphaIdDiallingNumber(recordSize); + equal(contactR, null); + equal(writtenContact.alphaId, ""); + equal(writtenContact.number, ""); + + // Write a longer alphaId/dialling number + // Dialling Number : Maximum 20 digits(10 octets). + // Alpha Identifier: 32(recordSize) - 14 (10 octets for Dialling Number, 1 + // octet for TON/NPI, 1 for number length octet, and 2 for + // Ext) = Maximum 18 octets. + let longContact = { + alphaId: "AAAAAAAAABBBBBBBBBCCCCCCCCC", + number: "123456789012345678901234567890", + }; + + writtenContact = helper.writeAlphaIdDiallingNumber(recordSize, + longContact.alphaId, + longContact.number, 0xff); + contactR = helper.readAlphaIdDiallingNumber(recordSize); + equal(writtenContact.alphaId, contactR.alphaId); + equal(writtenContact.number, contactR.number); + equal(0xff, contactR.extRecordNumber); + + // Add '+' to number and test again. + longContact.number = "+123456789012345678901234567890"; + writtenContact = helper.writeAlphaIdDiallingNumber(recordSize, + longContact.alphaId, + longContact.number, 0xff); + contactR = helper.readAlphaIdDiallingNumber(recordSize); + equal(writtenContact.alphaId, contactR.alphaId); + equal(writtenContact.number, contactR.number); + equal(0xff, contactR.extRecordNumber); + + run_next_test(); +}); + +/** + * Verify ICCPDUHelper.writeDiallingNumber + */ +add_test(function test_write_dialling_number() { + let worker = newUint8Worker(); + let context = worker.ContextPool._contexts[0]; + let helper = context.ICCPDUHelper; + + // with + + let number = "+123456"; + let len = 4; + helper.writeDiallingNumber(number); + equal(helper.readDiallingNumber(len), number); + + // without + + number = "987654"; + len = 4; + helper.writeDiallingNumber(number); + equal(helper.readDiallingNumber(len), number); + + number = "9876543"; + len = 5; + helper.writeDiallingNumber(number); + equal(helper.readDiallingNumber(len), number); + + run_next_test(); +}); + +/** + * Verify ICCPDUHelper.readNumberWithLength + */ +add_test(function test_read_number_with_length() { + let worker = newUint8Worker(); + let context = worker.ContextPool._contexts[0]; + let helper = context.GsmPDUHelper; + let iccHelper = context.ICCPDUHelper; + + let numbers = [ + { + number: "123456789", + expectedNumber: "123456789" + }, + { + number: "", + expectedNumber: "" + }, + // Invalid length of BCD number/SSC contents + { + number: "12345678901234567890123", + expectedNumber: "" + }, + ]; + + // To avoid obtaining wrong buffer content. + context.Buf.seekIncoming = function(offset) { + }; + + function do_test(aNumber, aExpectedNumber) { + iccHelper.readDiallingNumber = function(numLen) { + return aNumber.substring(0, numLen); + }; + + if (aNumber) { + helper.writeHexOctet(aNumber.length + 1); + } else { + helper.writeHexOctet(0xff); + } + + equal(iccHelper.readNumberWithLength(), aExpectedNumber); + } + + for (let i = 0; i < numbers.length; i++) { + do_test(numbers[i].number, numbers[i].expectedNumber); + } + + run_next_test(); +}); + +/** + * Verify ICCPDUHelper.writeNumberWithLength + */ +add_test(function test_write_number_with_length() { + let worker = newUint8Worker(); + let context = worker.ContextPool._contexts[0]; + let helper = context.GsmPDUHelper; + let iccHelper = context.ICCPDUHelper; + + function test(number, expectedNumber) { + expectedNumber = expectedNumber || number; + let writeNumber = iccHelper.writeNumberWithLength(number); + equal(writeNumber, expectedNumber); + let numLen = helper.readHexOctet(); + equal(expectedNumber, iccHelper.readDiallingNumber(numLen)); + for (let i = 0; i < (ADN_MAX_BCD_NUMBER_BYTES - numLen); i++) { + equal(0xff, helper.readHexOctet()); + } + } + + // without + + test("123456789"); + + // with + + test("+987654321"); + + // extended BCD coding + test("1*2#3,4*5#6,"); + + // with + and extended BCD coding + test("+1*2#3,4*5#6,"); + + // non-supported characters should not be written. + test("(1)23-456+789", "123456789"); + + test("++(01)2*3-4#5,6+7(8)9*0#1,", "+012*34#5,6789*0#1,"); + + // over maximum 20 digits should be truncated. + test("012345678901234567890123456789", "01234567890123456789"); + + // null + iccHelper.writeNumberWithLength(null); + for (let i = 0; i < (ADN_MAX_BCD_NUMBER_BYTES + 1); i++) { + equal(0xff, helper.readHexOctet()); + } + + run_next_test(); +}); diff --git a/dom/system/gonk/tests/test_ril_worker_icc_ICCRecordHelper.js b/dom/system/gonk/tests/test_ril_worker_icc_ICCRecordHelper.js new file mode 100644 index 000000000..00c55873d --- /dev/null +++ b/dom/system/gonk/tests/test_ril_worker_icc_ICCRecordHelper.js @@ -0,0 +1,1080 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +subscriptLoader.loadSubScript("resource://gre/modules/ril_consts.js", this); + +function run_test() { + run_next_test(); +} + +/** + * Verify ICCRecordHelper.readPBR + */ +add_test(function test_read_pbr() { + let worker = newUint8Worker(); + let context = worker.ContextPool._contexts[0]; + let helper = context.GsmPDUHelper; + let record = context.ICCRecordHelper; + let buf = context.Buf; + let io = context.ICCIOHelper; + + io.loadLinearFixedEF = function fakeLoadLinearFixedEF(options) { + let pbr_1 = [ + 0xa8, 0x05, 0xc0, 0x03, 0x4f, 0x3a, 0x01 + ]; + + // Write data size + buf.writeInt32(pbr_1.length * 2); + + // Write pbr + for (let i = 0; i < pbr_1.length; i++) { + helper.writeHexOctet(pbr_1[i]); + } + + // Write string delimiter + buf.writeStringDelimiter(pbr_1.length * 2); + + options.totalRecords = 2; + if (options.callback) { + options.callback(options); + } + }; + + io.loadNextRecord = function fakeLoadNextRecord(options) { + let pbr_2 = [ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff + ]; + + options.p1++; + if (options.callback) { + options.callback(options); + } + }; + + let successCb = function successCb(pbrs) { + equal(pbrs[0].adn.fileId, 0x4f3a); + equal(pbrs.length, 1); + }; + + let errorCb = function errorCb(errorMsg) { + do_print("Reading EF_PBR failed, msg = " + errorMsg); + ok(false); + }; + + record.readPBR(successCb, errorCb); + + // Check cache pbrs when 2nd call + let ifLoadEF = false; + io.loadLinearFixedEF = function fakeLoadLinearFixedEF(options) { + ifLoadEF = true; + } + record.readPBR(successCb, errorCb); + ok(!ifLoadEF); + + run_next_test(); +}); + +/** + * Verify ICCRecordHelper.readEmail + */ +add_test(function test_read_email() { + let worker = newUint8Worker(); + let context = worker.ContextPool._contexts[0]; + let helper = context.GsmPDUHelper; + let record = context.ICCRecordHelper; + let buf = context.Buf; + let io = context.ICCIOHelper; + let recordSize; + + io.loadLinearFixedEF = function fakeLoadLinearFixedEF(options) { + let email_1 = [ + 0x65, 0x6D, 0x61, 0x69, 0x6C, + 0x00, 0x6D, 0x6F, 0x7A, 0x69, + 0x6C, 0x6C, 0x61, 0x2E, 0x63, + 0x6F, 0x6D, 0x02, 0x23]; + + // Write data size + buf.writeInt32(email_1.length * 2); + + // Write email + for (let i = 0; i < email_1.length; i++) { + helper.writeHexOctet(email_1[i]); + } + + // Write string delimiter + buf.writeStringDelimiter(email_1.length * 2); + + recordSize = email_1.length; + options.recordSize = recordSize; + if (options.callback) { + options.callback(options); + } + }; + + function doTestReadEmail(type, expectedResult) { + let fileId = 0x6a75; + let recordNumber = 1; + + // fileId and recordNumber are dummy arguments. + record.readEmail(fileId, type, recordNumber, function(email) { + equal(email, expectedResult); + }); + }; + + doTestReadEmail(ICC_USIM_TYPE1_TAG, "email@mozilla.com$#"); + doTestReadEmail(ICC_USIM_TYPE2_TAG, "email@mozilla.com"); + equal(record._emailRecordSize, recordSize); + + run_next_test(); +}); + +/** + * Verify ICCRecordHelper.updateEmail + */ +add_test(function test_update_email() { + const recordSize = 0x20; + const recordNumber = 1; + const fileId = 0x4f50; + const NUM_TESTS = 2; + let worker = newUint8Worker(); + let context = worker.ContextPool._contexts[0]; + let pduHelper = context.GsmPDUHelper; + let iccHelper = context.ICCPDUHelper; + let ril = context.RIL; + ril.appType = CARD_APPTYPE_USIM; + let recordHelper = context.ICCRecordHelper; + let buf = context.Buf; + let ioHelper = context.ICCIOHelper; + let pbr = {email: {fileId: fileId, fileType: ICC_USIM_TYPE1_TAG}, + adn: {sfi: 1}}; + let count = 0; + + // Override. + ioHelper.updateLinearFixedEF = function(options) { + options.pathId = context.ICCFileHelper.getEFPath(options.fileId); + options.command = ICC_COMMAND_UPDATE_RECORD; + options.p1 = options.recordNumber; + options.p2 = READ_RECORD_ABSOLUTE_MODE; + options.p3 = recordSize; + ril.iccIO(options); + }; + + function do_test(pbr, expectedEmail, expectedAdnRecordId) { + buf.sendParcel = function() { + count++; + + // Request Type. + equal(this.readInt32(), REQUEST_SIM_IO); + + // Token : we don't care + this.readInt32(); + + // command. + equal(this.readInt32(), ICC_COMMAND_UPDATE_RECORD); + + // fileId. + equal(this.readInt32(), fileId); + + // pathId. + equal(this.readString(), + EF_PATH_MF_SIM + EF_PATH_DF_TELECOM + EF_PATH_DF_PHONEBOOK); + + // p1. + equal(this.readInt32(), recordNumber); + + // p2. + equal(this.readInt32(), READ_RECORD_ABSOLUTE_MODE); + + // p3. + equal(this.readInt32(), recordSize); + + // data. + let strLen = this.readInt32(); + let email; + if (pbr.email.fileType === ICC_USIM_TYPE1_TAG) { + email = iccHelper.read8BitUnpackedToString(recordSize); + } else { + email = iccHelper.read8BitUnpackedToString(recordSize - 2); + equal(pduHelper.readHexOctet(), pbr.adn.sfi); + equal(pduHelper.readHexOctet(), expectedAdnRecordId); + } + this.readStringDelimiter(strLen); + equal(email, expectedEmail); + + // pin2. + equal(this.readString(), null); + + // AID. Ignore because it's from modem. + this.readInt32(); + + if (count == NUM_TESTS) { + run_next_test(); + } + }; + recordHelper.updateEmail(pbr, recordNumber, expectedEmail, expectedAdnRecordId); + } + + do_test(pbr, "test@mail.com"); + pbr.email.fileType = ICC_USIM_TYPE2_TAG; + do_test(pbr, "test@mail.com", 1); +}); + +/** + * Verify ICCRecordHelper.readANR + */ +add_test(function test_read_anr() { + let worker = newUint8Worker(); + let context = worker.ContextPool._contexts[0]; + let helper = context.GsmPDUHelper; + let record = context.ICCRecordHelper; + let buf = context.Buf; + let io = context.ICCIOHelper; + let recordSize; + + io.loadLinearFixedEF = function fakeLoadLinearFixedEF(options) { + let anr_1 = [ + 0x01, 0x05, 0x81, 0x10, 0x32, + 0x54, 0xF6, 0xFF, 0xFF]; + + // Write data size + buf.writeInt32(anr_1.length * 2); + + // Write anr + for (let i = 0; i < anr_1.length; i++) { + helper.writeHexOctet(anr_1[i]); + } + + // Write string delimiter + buf.writeStringDelimiter(anr_1.length * 2); + + recordSize = anr_1.length; + options.recordSize = recordSize; + if (options.callback) { + options.callback(options); + } + }; + + function doTestReadAnr(fileType, expectedResult) { + let fileId = 0x4f11; + let recordNumber = 1; + + // fileId and recordNumber are dummy arguments. + record.readANR(fileId, fileType, recordNumber, function(anr) { + equal(anr, expectedResult); + }); + }; + + doTestReadAnr(ICC_USIM_TYPE1_TAG, "0123456"); + equal(record._anrRecordSize, recordSize); + + run_next_test(); +}); + +/** + * Verify ICCRecordHelper.updateANR + */ +add_test(function test_update_anr() { + const recordSize = 0x20; + const recordNumber = 1; + const fileId = 0x4f11; + const NUM_TESTS = 2; + let worker = newUint8Worker(); + let context = worker.ContextPool._contexts[0]; + let pduHelper = context.GsmPDUHelper; + let iccHelper = context.ICCPDUHelper; + let ril = context.RIL; + ril.appType = CARD_APPTYPE_USIM; + let recordHelper = context.ICCRecordHelper; + let buf = context.Buf; + let ioHelper = context.ICCIOHelper; + let pbr = {anr0: {fileId: fileId, fileType: ICC_USIM_TYPE1_TAG}, + adn: {sfi: 1}}; + let count = 0; + + // Override. + ioHelper.updateLinearFixedEF = function(options) { + options.pathId = context.ICCFileHelper.getEFPath(options.fileId); + options.command = ICC_COMMAND_UPDATE_RECORD; + options.p1 = options.recordNumber; + options.p2 = READ_RECORD_ABSOLUTE_MODE; + options.p3 = recordSize; + ril.iccIO(options); + }; + + function do_test(pbr, expectedANR, expectedAdnRecordId) { + buf.sendParcel = function() { + count++; + + // Request Type. + equal(this.readInt32(), REQUEST_SIM_IO); + + // Token : we don't care + this.readInt32(); + + // command. + equal(this.readInt32(), ICC_COMMAND_UPDATE_RECORD); + + // fileId. + equal(this.readInt32(), fileId); + + // pathId. + equal(this.readString(), + EF_PATH_MF_SIM + EF_PATH_DF_TELECOM + EF_PATH_DF_PHONEBOOK); + + // p1. + equal(this.readInt32(), recordNumber); + + // p2. + equal(this.readInt32(), READ_RECORD_ABSOLUTE_MODE); + + // p3. + equal(this.readInt32(), recordSize); + + // data. + let strLen = this.readInt32(); + // EF_AAS, ignore. + pduHelper.readHexOctet(); + equal(iccHelper.readNumberWithLength(), expectedANR); + // EF_CCP, ignore. + pduHelper.readHexOctet(); + // EF_EXT1, ignore. + pduHelper.readHexOctet(); + if (pbr.anr0.fileType === ICC_USIM_TYPE2_TAG) { + equal(pduHelper.readHexOctet(), pbr.adn.sfi); + equal(pduHelper.readHexOctet(), expectedAdnRecordId); + } + this.readStringDelimiter(strLen); + + // pin2. + equal(this.readString(), null); + + // AID. Ignore because it's from modem. + this.readInt32(); + + if (count == NUM_TESTS) { + run_next_test(); + } + }; + recordHelper.updateANR(pbr, recordNumber, expectedANR, expectedAdnRecordId); + } + + do_test(pbr, "+123456789"); + pbr.anr0.fileType = ICC_USIM_TYPE2_TAG; + do_test(pbr, "123456789", 1); +}); + +/** + * Verify ICCRecordHelper.readIAP + */ +add_test(function test_read_iap() { + let worker = newUint8Worker(); + let context = worker.ContextPool._contexts[0]; + let helper = context.GsmPDUHelper; + let record = context.ICCRecordHelper; + let buf = context.Buf; + let io = context.ICCIOHelper; + let recordSize; + + io.loadLinearFixedEF = function fakeLoadLinearFixedEF(options) { + let iap_1 = [0x01, 0x02]; + + // Write data size/ + buf.writeInt32(iap_1.length * 2); + + // Write iap. + for (let i = 0; i < iap_1.length; i++) { + helper.writeHexOctet(iap_1[i]); + } + + // Write string delimiter. + buf.writeStringDelimiter(iap_1.length * 2); + + recordSize = iap_1.length; + options.recordSize = recordSize; + if (options.callback) { + options.callback(options); + } + }; + + function doTestReadIAP(expectedIAP) { + const fileId = 0x4f17; + const recordNumber = 1; + + let successCb = function successCb(iap) { + for (let i = 0; i < iap.length; i++) { + equal(expectedIAP[i], iap[i]); + } + run_next_test(); + }.bind(this); + + let errorCb = function errorCb(errorMsg) { + do_print(errorMsg); + ok(false); + run_next_test(); + }.bind(this); + + record.readIAP(fileId, recordNumber, successCb, errorCb); + }; + + doTestReadIAP([1, 2]); +}); + +/** + * Verify ICCRecordHelper.updateIAP + */ +add_test(function test_update_iap() { + const recordSize = 2; + const recordNumber = 1; + const fileId = 0x4f17; + let worker = newUint8Worker(); + let context = worker.ContextPool._contexts[0]; + let pduHelper = context.GsmPDUHelper; + let ril = context.RIL; + ril.appType = CARD_APPTYPE_USIM; + let recordHelper = context.ICCRecordHelper; + let buf = context.Buf; + let ioHelper = context.ICCIOHelper; + let count = 0; + + // Override. + ioHelper.updateLinearFixedEF = function(options) { + options.pathId = context.ICCFileHelper.getEFPath(options.fileId); + options.command = ICC_COMMAND_UPDATE_RECORD; + options.p1 = options.recordNumber; + options.p2 = READ_RECORD_ABSOLUTE_MODE; + options.p3 = recordSize; + ril.iccIO(options); + }; + + function do_test(expectedIAP) { + buf.sendParcel = function() { + // Request Type. + equal(this.readInt32(), REQUEST_SIM_IO); + + // Token : we don't care + this.readInt32(); + + // command. + equal(this.readInt32(), ICC_COMMAND_UPDATE_RECORD); + + // fileId. + equal(this.readInt32(), fileId); + + // pathId. + equal(this.readString(), + EF_PATH_MF_SIM + EF_PATH_DF_TELECOM + EF_PATH_DF_PHONEBOOK); + + // p1. + equal(this.readInt32(), recordNumber); + + // p2. + equal(this.readInt32(), READ_RECORD_ABSOLUTE_MODE); + + // p3. + equal(this.readInt32(), recordSize); + + // data. + let strLen = this.readInt32(); + for (let i = 0; i < recordSize; i++) { + equal(expectedIAP[i], pduHelper.readHexOctet()); + } + this.readStringDelimiter(strLen); + + // pin2. + equal(this.readString(), null); + + // AID. Ignore because it's from modem. + this.readInt32(); + + run_next_test(); + }; + recordHelper.updateIAP(fileId, recordNumber, expectedIAP); + } + + do_test([1, 2]); +}); + +/** + * Verify ICCRecordHelper.readADNLike. + */ +add_test(function test_read_adn_like() { + const RECORD_SIZE = 0x20; + + let worker = newUint8Worker(); + let context = worker.ContextPool._contexts[0]; + let helper = context.GsmPDUHelper; + let record = context.ICCRecordHelper; + let buf = context.Buf; + let io = context.ICCIOHelper; + let ril = context.RIL; + + function do_test(extFileId, rawEF, expectedExtRecordNumber, expectedNumber) { + io.loadLinearFixedEF = function fakeLoadLinearFixedEF(options) { + // Write data size + buf.writeInt32(rawEF.length * 2); + + // Write adn + for (let i = 0; i < rawEF.length; i += 2) { + helper.writeHexOctet(parseInt(rawEF.substr(i, 2), 16)); + } + + // Write string delimiter + buf.writeStringDelimiter(rawEF.length * 2); + + options.p1 = 1; + options.recordSize = RECORD_SIZE; + options.totalRecords = 1; + if (options.callback) { + options.callback(options); + } + }; + + record.readExtension = function(fileId, recordNumber, onsuccess, onerror) { + onsuccess("1234"); + } + + let successCb = function successCb(contacts) { + ok(contacts[0].number == expectedNumber); + }; + + let errorCb = function errorCb(errorMsg) { + do_print("Reading ADNLike failed, msg = " + errorMsg); + ok(false); + }; + + record.readADNLike(ICC_EF_ADN, extFileId, successCb, errorCb); + } + + ril.appType = CARD_APPTYPE_SIM; + // Valid extension + do_test(ICC_EF_EXT1,"436f6e74616374303031ffffffffffffffff0b8199887766554433221100ff01", + 0x01,"998877665544332211001234"); + // Empty extension + do_test(ICC_EF_EXT1,"436f6e74616374303031ffffffffffffffff0b8199887766554433221100ffff", + 0xff, "99887766554433221100"); + // Unsupport extension + do_test(null,"436f6e74616374303031ffffffffffffffff0b8199887766554433221100ffff", + 0xff, "99887766554433221100"); + // Empty dialling number contact + do_test(null,"436f6e74616374303031ffffffffffffffffffffffffffffffffffffffffffff", + 0xff, ""); + + run_next_test(); +}); + +/** + * Verify ICCRecordHelper.updateADNLike. + */ +add_test(function test_update_adn_like() { + let worker = newUint8Worker(); + let context = worker.ContextPool._contexts[0]; + let ril = context.RIL; + let record = context.ICCRecordHelper; + let io = context.ICCIOHelper; + let pdu = context.ICCPDUHelper; + let buf = context.Buf; + + ril.appType = CARD_APPTYPE_SIM; + const recordSize = 0x20; + let fileId; + + // Override. + io.updateLinearFixedEF = function(options) { + options.pathId = context.ICCFileHelper.getEFPath(options.fileId); + options.command = ICC_COMMAND_UPDATE_RECORD; + options.p1 = options.recordNumber; + options.p2 = READ_RECORD_ABSOLUTE_MODE; + options.p3 = recordSize; + ril.iccIO(options); + }; + + buf.sendParcel = function() { + // Request Type. + equal(this.readInt32(), REQUEST_SIM_IO); + + // Token : we don't care + this.readInt32(); + + // command. + equal(this.readInt32(), ICC_COMMAND_UPDATE_RECORD); + + // fileId. + equal(this.readInt32(), fileId); + + // pathId. + equal(this.readString(), EF_PATH_MF_SIM + EF_PATH_DF_TELECOM); + + // p1. + equal(this.readInt32(), 1); + + // p2. + equal(this.readInt32(), READ_RECORD_ABSOLUTE_MODE); + + // p3. + equal(this.readInt32(), 0x20); + + // data. + let contact = pdu.readAlphaIdDiallingNumber(0x20); + equal(contact.alphaId, "test"); + equal(contact.number, "123456"); + equal(contact.extRecordNumber, "0xff"); + + // pin2. + if (fileId == ICC_EF_ADN) { + equal(this.readString(), null); + } else { + equal(this.readString(), "1111"); + } + + // AID. Ignore because it's from modem. + this.readInt32(); + + if (fileId == ICC_EF_FDN) { + run_next_test(); + } + }; + + fileId = ICC_EF_ADN; + record.updateADNLike(fileId, 0xff, + {recordId: 1, alphaId: "test", number: "123456"}); + + fileId = ICC_EF_FDN; + record.updateADNLike(fileId, 0xff, + {recordId: 1, alphaId: "test", number: "123456"}, + "1111"); +}); + +/** + * Verify ICCRecordHelper.findFreeRecordId. + */ +add_test(function test_find_free_record_id() { + let worker = newUint8Worker(); + let context = worker.ContextPool._contexts[0]; + let pduHelper = context.GsmPDUHelper; + let recordHelper = context.ICCRecordHelper; + let buf = context.Buf; + let io = context.ICCIOHelper; + let ril = context.RIL; + + function writeRecord(record) { + // Write data size + buf.writeInt32(record.length * 2); + + for (let i = 0; i < record.length; i++) { + pduHelper.writeHexOctet(record[i]); + } + + // Write string delimiter + buf.writeStringDelimiter(record.length * 2); + } + + io.loadLinearFixedEF = function fakeLoadLinearFixedEF(options) { + // Some random data. + let record = [0x12, 0x34, 0x56, 0x78, 0x90]; + options.p1 = 1; + options.totalRecords = 2; + writeRecord(record); + if (options.callback) { + options.callback(options); + } + }; + + ril.iccIO = function fakeIccIO(options) { + // Unused bytes. + let record = [0xff, 0xff, 0xff, 0xff, 0xff]; + writeRecord(record); + if (options.callback) { + options.callback(options); + } + }; + + let fileId = 0x0000; // Dummy. + recordHelper.findFreeRecordId( + fileId, + function(recordId) { + equal(recordId, 2); + run_next_test(); + }.bind(this), + function(errorMsg) { + do_print(errorMsg); + ok(false); + run_next_test(); + }.bind(this)); +}); + +/** + * Verify ICCRecordHelper.fetchICCRecords. + */ +add_test(function test_fetch_icc_recodes() { + let worker = newWorker(); + let context = worker.ContextPool._contexts[0]; + let RIL = context.RIL; + let iccRecord = context.ICCRecordHelper; + let simRecord = context.SimRecordHelper; + let ruimRecord = context.RuimRecordHelper; + let fetchTag = 0x00; + + simRecord.fetchSimRecords = function() { + fetchTag = 0x01; + }; + + ruimRecord.fetchRuimRecords = function() { + fetchTag = 0x02; + }; + + RIL.appType = CARD_APPTYPE_SIM; + iccRecord.fetchICCRecords(); + equal(fetchTag, 0x01); + + RIL.appType = CARD_APPTYPE_RUIM; + iccRecord.fetchICCRecords(); + equal(fetchTag, 0x02); + + RIL.appType = CARD_APPTYPE_USIM; + iccRecord.fetchICCRecords(); + equal(fetchTag, 0x01); + + run_next_test(); +}); + +/** + * Verify reading EF_ICCID. + */ +add_test(function test_handling_iccid() { + let worker = newUint8Worker(); + let context = worker.ContextPool._contexts[0]; + let record = context.ICCRecordHelper; + let helper = context.GsmPDUHelper; + let ril = context.RIL; + let buf = context.Buf; + let io = context.ICCIOHelper; + + ril.reportStkServiceIsRunning = function fakeReportStkServiceIsRunning() { + }; + + function do_test(rawICCID, expectedICCID) { + io.loadTransparentEF = function fakeLoadTransparentEF(options) { + // Write data size + buf.writeInt32(rawICCID.length); + + // Write data + for (let i = 0; i < rawICCID.length; i += 2) { + helper.writeHexOctet(parseInt(rawICCID.substr(i, 2), 16)); + } + + // Write string delimiter + buf.writeStringDelimiter(rawICCID.length); + + if (options.callback) { + options.callback(options); + } + }; + + record.readICCID(); + + equal(ril.iccInfo.iccid, expectedICCID); + } + + // Invalid value 0xE at high nibbile + low nibbile contains 0xF. + do_test("9868002E90909F001519", "89860020909"); + // Invalid value 0xD at low nibbile. + do_test("986800D2909090001519", "8986002090909005191"); + // Invalid value 0xC at low nibbile. + do_test("986800C2909090001519", "8986002090909005191"); + // Invalid value 0xB at low nibbile. + do_test("986800B2909090001519", "8986002090909005191"); + // Invalid value 0xA at low nibbile. + do_test("986800A2909090001519", "8986002090909005191"); + // Valid ICCID. + do_test("98101430121181157002", "89014103211118510720"); + + run_next_test(); +}); + +/** + * Verify ICCRecordHelper.readExtension + */ +add_test(function test_read_extension() { + let worker = newUint8Worker(); + let context = worker.ContextPool._contexts[0]; + let helper = context.GsmPDUHelper; + let record = context.ICCRecordHelper; + let buf = context.Buf; + let io = context.ICCIOHelper; + + function do_test(rawExtension, expectedExtensionNumber) { + io.loadLinearFixedEF = function fakeLoadLinearFixedEF(options) { + // Write data size + buf.writeInt32(rawExtension.length * 2); + + // Write ext + for (let i = 0; i < rawExtension.length; i += 2) { + helper.writeHexOctet(parseInt(rawExtension.substr(i, 2), 16)); + } + + // Write string delimiter + buf.writeStringDelimiter(rawExtension.length); + + if (options.callback) { + options.callback(options); + } + }; + + let successCb = function successCb(number) { + do_print("extension number:" + number); + equal(number, expectedExtensionNumber); + }; + + let errorCb = function errorCb() { + ok(expectedExtensionNumber == null); + }; + + record.readExtension(0x6f4a, 1, successCb, errorCb); + } + + // Test unsupported record type 0x01 + do_test("010a10325476981032547698ff", ""); + // Test invalid length 0xc1 + do_test("020c10325476981032547698ff", null); + // Test extension chain which we don't support + do_test("020a1032547698103254769802", "01234567890123456789"); + // Test valid Extension + do_test("020a10325476981032547698ff", "01234567890123456789"); + // Test valid Extension + do_test("0209103254769810325476ffff", "012345678901234567"); + // Test empty Extension + do_test("02ffffffffffffffffffffffff", ""); + + run_next_test(); +}); + +/** + * Verify ICCRecordHelper.updateExtension + */ +add_test(function test_update_extension() { + const RECORD_SIZE = 13; + const RECORD_NUMBER = 1; + + let worker = newUint8Worker(); + let context = worker.ContextPool._contexts[0]; + let pduHelper = context.GsmPDUHelper; + let ril = context.RIL; + let recordHelper = context.ICCRecordHelper; + let buf = context.Buf; + let ioHelper = context.ICCIOHelper; + + // Override. + ioHelper.updateLinearFixedEF = function(options) { + options.pathId = context.ICCFileHelper.getEFPath(options.fileId); + options.command = ICC_COMMAND_UPDATE_RECORD; + options.p1 = options.recordNumber; + options.p2 = READ_RECORD_ABSOLUTE_MODE; + options.p3 = RECORD_SIZE; + ril.iccIO(options); + }; + + function do_test(fileId, number, expectedNumber) { + buf.sendParcel = function() { + // Request Type. + equal(this.readInt32(), REQUEST_SIM_IO); + + // Token : we don't care + this.readInt32(); + + // command. + equal(this.readInt32(), ICC_COMMAND_UPDATE_RECORD); + + // fileId. + equal(this.readInt32(), fileId); + + // pathId. + if (ril.appType == CARD_APPTYPE_SIM || ril.appType == CARD_APPTYPE_RUIM) { + equal(this.readString(), + EF_PATH_MF_SIM + EF_PATH_DF_TELECOM); + } else{ + equal(this.readString(), + EF_PATH_MF_SIM + EF_PATH_DF_TELECOM + EF_PATH_DF_PHONEBOOK); + } + + // p1. + equal(this.readInt32(), RECORD_NUMBER); + + // p2. + equal(this.readInt32(), READ_RECORD_ABSOLUTE_MODE); + + // p3. + equal(this.readInt32(), RECORD_SIZE); + + // data. + let strLen = this.readInt32(); + // Extension record + let recordType = pduHelper.readHexOctet(); + + equal(recordType, 0x02); + equal(pduHelper.readHexOctet(), 10); + equal( + pduHelper.readSwappedNibbleExtendedBcdString(EXT_MAX_NUMBER_DIGITS - 1), + expectedNumber); + + this.readStringDelimiter(strLen); + + // pin2. + equal(this.readString(), null); + + // AID. Ignore because it's from modem. + this.readInt32(); + }; + + recordHelper.updateExtension(fileId, RECORD_NUMBER, number); + } + + ril.appType = CARD_APPTYPE_SIM; + do_test(ICC_EF_EXT1, "01234567890123456789", "01234567890123456789"); + // We don't support extension chain. + do_test(ICC_EF_EXT1, "012345678901234567891234", "01234567890123456789"); + + ril.appType = CARD_APPTYPE_USIM; + do_test(ICC_EF_EXT1, "01234567890123456789", "01234567890123456789"); + + ril.appType = CARD_APPTYPE_RUIM; + do_test(ICC_EF_EXT1, "01234567890123456789", "01234567890123456789"); + + run_next_test(); +}); + +/** + * Verify ICCRecordHelper.cleanEFRecord + */ +add_test(function test_clean_ef_record() { + const RECORD_SIZE = 13; + const RECORD_NUMBER = 1; + + let worker = newUint8Worker(); + let context = worker.ContextPool._contexts[0]; + let pduHelper = context.GsmPDUHelper; + let ril = context.RIL; + let recordHelper = context.ICCRecordHelper; + let buf = context.Buf; + let ioHelper = context.ICCIOHelper; + + // Override. + ioHelper.updateLinearFixedEF = function(options) { + options.pathId = context.ICCFileHelper.getEFPath(options.fileId); + options.command = ICC_COMMAND_UPDATE_RECORD; + options.p1 = options.recordNumber; + options.p2 = READ_RECORD_ABSOLUTE_MODE; + options.p3 = RECORD_SIZE; + ril.iccIO(options); + }; + + function do_test(fileId) { + buf.sendParcel = function() { + // Request Type. + equal(this.readInt32(), REQUEST_SIM_IO); + + // Token : we don't care + this.readInt32(); + + // command. + equal(this.readInt32(), ICC_COMMAND_UPDATE_RECORD); + + // fileId. + equal(this.readInt32(), fileId); + + // pathId. + if (ril.appType == CARD_APPTYPE_SIM || ril.appType == CARD_APPTYPE_RUIM) { + equal(this.readString(), + EF_PATH_MF_SIM + EF_PATH_DF_TELECOM); + } else{ + equal(this.readString(), + EF_PATH_MF_SIM + EF_PATH_DF_TELECOM + EF_PATH_DF_PHONEBOOK); + } + + // p1. + equal(this.readInt32(), RECORD_NUMBER); + + // p2. + equal(this.readInt32(), READ_RECORD_ABSOLUTE_MODE); + + // p3. + equal(this.readInt32(), RECORD_SIZE); + + // data. + let strLen = this.readInt32(); + // Extension record + for (let i = 0; i < RECORD_SIZE; i++) { + equal(pduHelper.readHexOctet(), 0xff); + } + + this.readStringDelimiter(strLen); + + // pin2. + equal(this.readString(), null); + + // AID. Ignore because it's from modem. + this.readInt32(); + }; + + recordHelper.cleanEFRecord(fileId, RECORD_NUMBER); + } + + ril.appType = CARD_APPTYPE_SIM; + do_test(ICC_EF_EXT1); + + run_next_test(); +}); + +/** + * Verify ICCRecordHelper.getADNLikeExtensionRecordNumber + */ +add_test(function test_get_adn_like_extension_record_number() { + const RECORD_SIZE = 0x20; + + let worker = newUint8Worker(); + let context = worker.ContextPool._contexts[0]; + let helper = context.GsmPDUHelper; + let record = context.ICCRecordHelper; + let buf = context.Buf; + let io = context.ICCIOHelper; + + function do_test(rawEF, expectedRecordNumber) { + io.loadLinearFixedEF = function fakeLoadLinearFixedEF(options) { + // Write data size + buf.writeInt32(rawEF.length * 2); + + // Write ext + for (let i = 0; i < rawEF.length; i += 2) { + helper.writeHexOctet(parseInt(rawEF.substr(i, 2), 16)); + } + + // Write string delimiter + buf.writeStringDelimiter(rawEF.length); + options.recordSize = RECORD_SIZE; + if (options.callback) { + options.callback(options); + } + }; + + let isSuccess = false; + let successCb = function successCb(number) { + equal(number, expectedRecordNumber); + isSuccess = true; + }; + + let errorCb = function errorCb(errorMsg) { + do_print("Reading ADNLike failed, msg = " + errorMsg); + ok(false); + }; + + record.getADNLikeExtensionRecordNumber(ICC_EF_ADN, 1, successCb, errorCb); + ok(isSuccess); + } + + // Valid Extension, Alpha Id(Encoded with GSM 8 bit): "Contact001", + // Dialling Number: 99887766554433221100, Ext1: 0x01 + do_test("436f6e74616374303031ffffffffffffffff0b8199887766554433221100ff01", 0x01); + // Empty Extension, Alpha Id(Encoded with GSM 8 bit): "Contact001", Ext1: 0xff + do_test("436f6e74616374303031ffffffffffffffffffffffffffffffffffffffffffff", 0xff); + + run_next_test(); +}); diff --git a/dom/system/gonk/tests/test_ril_worker_icc_ICCUtilsHelper.js b/dom/system/gonk/tests/test_ril_worker_icc_ICCUtilsHelper.js new file mode 100644 index 000000000..b23d0b598 --- /dev/null +++ b/dom/system/gonk/tests/test_ril_worker_icc_ICCUtilsHelper.js @@ -0,0 +1,326 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +subscriptLoader.loadSubScript("resource://gre/modules/ril_consts.js", this); + +function run_test() { + run_next_test(); +} + +/** + * Verify ICCUtilsHelper.isICCServiceAvailable. + */ +add_test(function test_is_icc_service_available() { + let worker = newUint8Worker(); + let context = worker.ContextPool._contexts[0]; + let ICCUtilsHelper = context.ICCUtilsHelper; + let RIL = context.RIL; + + function test_table(sst, geckoService, simEnabled, usimEnabled) { + RIL.iccInfoPrivate.sst = sst; + RIL.appType = CARD_APPTYPE_SIM; + equal(ICCUtilsHelper.isICCServiceAvailable(geckoService), simEnabled); + RIL.appType = CARD_APPTYPE_USIM; + equal(ICCUtilsHelper.isICCServiceAvailable(geckoService), usimEnabled); + } + + test_table([0x08], "ADN", true, false); + test_table([0x08], "FDN", false, false); + test_table([0x08], "SDN", false, true); + + run_next_test(); +}); + +/** + * Verify ICCUtilsHelper.isGsm8BitAlphabet + */ +add_test(function test_is_gsm_8bit_alphabet() { + let worker = newUint8Worker(); + let context = worker.ContextPool._contexts[0]; + let ICCUtilsHelper = context.ICCUtilsHelper; + const langTable = PDU_NL_LOCKING_SHIFT_TABLES[PDU_NL_IDENTIFIER_DEFAULT]; + const langShiftTable = PDU_NL_SINGLE_SHIFT_TABLES[PDU_NL_IDENTIFIER_DEFAULT]; + + equal(ICCUtilsHelper.isGsm8BitAlphabet(langTable), true); + equal(ICCUtilsHelper.isGsm8BitAlphabet(langShiftTable), true); + equal(ICCUtilsHelper.isGsm8BitAlphabet("\uaaaa"), false); + + run_next_test(); +}); + +/** + * Verify ICCUtilsHelper.parsePbrTlvs + */ +add_test(function test_parse_pbr_tlvs() { + let worker = newUint8Worker(); + let context = worker.ContextPool._contexts[0]; + let buf = context.Buf; + + let pbrTlvs = [ + {tag: ICC_USIM_TYPE1_TAG, + length: 0x0F, + value: [{tag: ICC_USIM_EFADN_TAG, + length: 0x03, + value: [0x4F, 0x3A, 0x02]}, + {tag: ICC_USIM_EFIAP_TAG, + length: 0x03, + value: [0x4F, 0x25, 0x01]}, + {tag: ICC_USIM_EFPBC_TAG, + length: 0x03, + value: [0x4F, 0x09, 0x04]}] + }, + {tag: ICC_USIM_TYPE2_TAG, + length: 0x05, + value: [{tag: ICC_USIM_EFEMAIL_TAG, + length: 0x03, + value: [0x4F, 0x50, 0x0B]}, + {tag: ICC_USIM_EFANR_TAG, + length: 0x03, + value: [0x4F, 0x11, 0x02]}, + {tag: ICC_USIM_EFANR_TAG, + length: 0x03, + value: [0x4F, 0x12, 0x03]}] + }, + {tag: ICC_USIM_TYPE3_TAG, + length: 0x0A, + value: [{tag: ICC_USIM_EFCCP1_TAG, + length: 0x03, + value: [0x4F, 0x3D, 0x0A]}, + {tag: ICC_USIM_EFEXT1_TAG, + length: 0x03, + value: [0x4F, 0x4A, 0x03]}] + }, + ]; + + let pbr = context.ICCUtilsHelper.parsePbrTlvs(pbrTlvs); + equal(pbr.adn.fileId, 0x4F3a); + equal(pbr.iap.fileId, 0x4F25); + equal(pbr.pbc.fileId, 0x4F09); + equal(pbr.email.fileId, 0x4F50); + equal(pbr.anr0.fileId, 0x4f11); + equal(pbr.anr1.fileId, 0x4f12); + equal(pbr.ccp1.fileId, 0x4F3D); + equal(pbr.ext1.fileId, 0x4F4A); + + run_next_test(); +}); + +/** + * Verify MCC and MNC parsing + */ +add_test(function test_mcc_mnc_parsing() { + let worker = newUint8Worker(); + let context = worker.ContextPool._contexts[0]; + let helper = context.ICCUtilsHelper; + + function do_test(imsi, mncLength, expectedMcc, expectedMnc) { + let result = helper.parseMccMncFromImsi(imsi, mncLength); + + if (!imsi) { + equal(result, null); + return; + } + + equal(result.mcc, expectedMcc); + equal(result.mnc, expectedMnc); + } + + // Test the imsi is null. + do_test(null, null, null, null); + + // Test MCC is Taiwan + do_test("466923202422409", 0x02, "466", "92"); + do_test("466923202422409", 0x03, "466", "923"); + do_test("466923202422409", null, "466", "92"); + + // Test MCC is US + do_test("310260542718417", 0x02, "310", "26"); + do_test("310260542718417", 0x03, "310", "260"); + do_test("310260542718417", null, "310", "260"); + + run_next_test(); +}); + +add_test(function test_get_network_name_from_icc() { + let worker = newUint8Worker(); + let context = worker.ContextPool._contexts[0]; + let RIL = context.RIL; + let ICCUtilsHelper = context.ICCUtilsHelper; + + function testGetNetworkNameFromICC(operatorData, expectedResult) { + let result = ICCUtilsHelper.getNetworkNameFromICC(operatorData.mcc, + operatorData.mnc, + operatorData.lac); + + if (expectedResult == null) { + equal(result, expectedResult); + } else { + equal(result.fullName, expectedResult.longName); + equal(result.shortName, expectedResult.shortName); + } + } + + // Before EF_OPL and EF_PNN have been loaded. + testGetNetworkNameFromICC({mcc: "123", mnc: "456", lac: 0x1000}, null); + testGetNetworkNameFromICC({mcc: "321", mnc: "654", lac: 0x2000}, null); + + // Set HPLMN + RIL.iccInfo.mcc = "123"; + RIL.iccInfo.mnc = "456"; + + RIL.voiceRegistrationState = { + cell: { + gsmLocationAreaCode: 0x1000 + } + }; + RIL.operator = {}; + + // Set EF_PNN + RIL.iccInfoPrivate = { + PNN: [ + {"fullName": "PNN1Long", "shortName": "PNN1Short"}, + {"fullName": "PNN2Long", "shortName": "PNN2Short"}, + {"fullName": "PNN3Long", "shortName": "PNN3Short"}, + {"fullName": "PNN4Long", "shortName": "PNN4Short"}, + {"fullName": "PNN5Long", "shortName": "PNN5Short"}, + {"fullName": "PNN6Long", "shortName": "PNN6Short"}, + {"fullName": "PNN7Long", "shortName": "PNN7Short"}, + {"fullName": "PNN8Long", "shortName": "PNN8Short"} + ] + }; + + // EF_OPL isn't available + ICCUtilsHelper.isICCServiceAvailable = function fakeIsICCServiceAvailable(service) { + return false; + }; + + // EF_OPL isn't available and current isn't in HPLMN, + testGetNetworkNameFromICC({mcc: "321", mnc: "654", lac: 0x1000}, null); + + // EF_OPL isn't available and current is in HPLMN, + // the first record of PNN should be returned. + testGetNetworkNameFromICC({mcc: "123", mnc: "456", lac: 0x1000}, + {longName: "PNN1Long", shortName: "PNN1Short"}); + + // EF_OPL is available + ICCUtilsHelper.isICCServiceAvailable = function fakeIsICCServiceAvailable(service) { + return service === "OPL"; + }; + + // Set EF_OPL + RIL.iccInfoPrivate.OPL = [ + { + "mcc": "123", + "mnc": "456", + "lacTacStart": 0, + "lacTacEnd": 0xFFFE, + "pnnRecordId": 4 + }, + { + "mcc": "321", + "mnc": "654", + "lacTacStart": 0, + "lacTacEnd": 0x0010, + "pnnRecordId": 3 + }, + { + "mcc": "321", + "mnc": "654", + "lacTacStart": 0x0100, + "lacTacEnd": 0x1010, + "pnnRecordId": 2 + }, + { + "mcc": ";;;", + "mnc": "01", + "lacTacStart": 0, + "lacTacEnd": 0xFFFE, + "pnnRecordId": 5 + }, + { + "mcc": "00;", + "mnc": "02", + "lacTacStart": 0, + "lacTacEnd": 0xFFFE, + "pnnRecordId": 6 + }, + { + "mcc": "001", + "mnc": ";;", + "lacTacStart": 0, + "lacTacEnd": 0xFFFE, + "pnnRecordId": 7 + }, + { + "mcc": "002", + "mnc": "0;", + "lacTacStart": 0, + "lacTacEnd": 0xFFFE, + "pnnRecordId": 8 + } + ]; + + // Both EF_PNN and EF_OPL are presented, and current PLMN is HPLMN, + testGetNetworkNameFromICC({mcc: "123", mnc: "456", lac: 0x1000}, + {longName: "PNN4Long", shortName: "PNN4Short"}); + + // Current PLMN is not HPLMN, and according to LAC, we should get + // the second PNN record. + testGetNetworkNameFromICC({mcc: "321", mnc: "654", lac: 0x1000}, + {longName: "PNN2Long", shortName: "PNN2Short"}); + + // Current PLMN is not HPLMN, and according to LAC, we should get + // the thrid PNN record. + testGetNetworkNameFromICC({mcc: "321", mnc: "654", lac: 0x0001}, + {longName: "PNN3Long", shortName: "PNN3Short"}); + + // Current PLMN is not HPLMN, and according to LAC, we should get + // the 5th PNN record after wild char (ie: ';') handling. + testGetNetworkNameFromICC({mcc: "001", mnc: "01", lac: 0x0001}, + {longName: "PNN5Long", shortName: "PNN5Short"}); + + // Current PLMN is not HPLMN, and according to LAC, we should get + // the 6th PNN record after wild char (ie: ';') handling. + testGetNetworkNameFromICC({mcc: "001", mnc: "02", lac: 0x0001}, + {longName: "PNN6Long", shortName: "PNN6Short"}); + + // Current PLMN is not HPLMN, and according to LAC, we should get + // the 7th PNN record after wild char (ie: ';') handling. + testGetNetworkNameFromICC({mcc: "001", mnc: "03", lac: 0x0001}, + {longName: "PNN7Long", shortName: "PNN7Short"}); + + // Current PLMN is not HPLMN, and according to LAC, we should get + // the 8th PNN record after wild char (ie: ';') handling. + testGetNetworkNameFromICC({mcc: "002", mnc: "03", lac: 0x0001}, + {longName: "PNN8Long", shortName: "PNN8Short"}); + + run_next_test(); +}); + +/** + * Verify ICCUtilsHelper.isCphsServiceAvailable. + */ +add_test(function test_is_cphs_service_available() { + let worker = newUint8Worker(); + let context = worker.ContextPool._contexts[0]; + let ICCUtilsHelper = context.ICCUtilsHelper; + let RIL = context.RIL; + RIL.iccInfoPrivate.cphsSt = new Uint8Array(2); + + function test_table(cphsSt, geckoService) { + RIL.iccInfoPrivate.cphsSt.set(cphsSt); + + for (let service in GECKO_ICC_SERVICES.cphs) { + equal(ICCUtilsHelper.isCphsServiceAvailable(service), + geckoService == service); + } + } + + test_table([0x03, 0x00], "CSP"); + test_table([0x0C, 0x00], "SST"); + test_table([0x30, 0x00], "MBN"); + test_table([0xC0, 0x00], "ONSF"); + test_table([0x00, 0x03], "INFO_NUM"); + + run_next_test(); +}); diff --git a/dom/system/gonk/tests/test_ril_worker_icc_IconLoader.js b/dom/system/gonk/tests/test_ril_worker_icc_IconLoader.js new file mode 100644 index 000000000..8bcd26ffe --- /dev/null +++ b/dom/system/gonk/tests/test_ril_worker_icc_IconLoader.js @@ -0,0 +1,771 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +subscriptLoader.loadSubScript("resource://gre/modules/ril_consts.js", this); + +function run_test() { + run_next_test(); +} + +/** + * Verify IconLoader.loadIcons with recordNumbers array length being 1. + * Query images of one record at a time. + */ +add_test(function test_load_icon_basic() { + let worker = newUint8Worker(); + let context = worker.ContextPool._contexts[0]; + let ril = context.RIL; + let iconLoader = context.IconLoader; + let simRecordHelper = context.SimRecordHelper; + + let test_data = [ + {rawData: [ + {codingScheme: ICC_IMG_CODING_SCHEME_BASIC, + width: 0x10, + height: 0x10, + body: [0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, + 0x00, 0x9d, 0xe9, 0xa1, 0x2d, 0xa1, 0x2d, 0xa1, 0x2b, + 0xa1, 0x2b, 0x9d, 0xe9, 0x00, 0x00, 0xff, 0xff, 0xff, + 0xff, 0x00, 0x00, 0xff, 0xff]}], + expected: [ + {width: 0x10, + height: 0x10, + pixels: [/* 1st byte of body: 0xff */ + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, + /* 2nd byte of body: 0xff */ + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, + /* 3rd byte of body: 0x00 */ + 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff, + 0x000000ff, 0x000000ff, 0x000000ff, + /* 4th byte of body: 0x00 */ + 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff, + 0x000000ff, 0x000000ff, 0x000000ff, + /* 5th byte of body: 0xff */ + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, + /* 6th byte of body: 0xff */ + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, + /* 7th byte of body: 0xff */ + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, + /* 8th byte of body: 0xff */ + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, + /* 9th byte of body: 0x00 */ + 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff, + 0x000000ff, 0x000000ff, 0x000000ff, + /* 10th byte of body: 0x00 */ + 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff, + 0x000000ff, 0x000000ff, 0x000000ff, + /* 11th byte of body: 0x9d */ + 0xffffffff, 0x000000ff, 0x000000ff, 0xffffffff, 0xffffffff, + 0xffffffff, 0x000000ff, 0xffffffff, + /* 12th byte of body: 0xe9 */ + 0xffffffff, 0xffffffff, 0xffffffff, 0x000000ff, 0xffffffff, + 0x000000ff, 0x000000ff, 0xffffffff, + /* 13th byte of body: 0xa1 */ + 0xffffffff, 0x000000ff, 0xffffffff, 0x000000ff, 0x000000ff, + 0x000000ff, 0x000000ff, 0xffffffff, + /* 14th byte of body: 0x2d */ + 0x000000ff, 0x000000ff, 0xffffffff, 0x000000ff, 0xffffffff, + 0xffffffff, 0x000000ff, 0xffffffff, + /* 15th byte of body: 0xa1 */ + 0xffffffff, 0x000000ff, 0xffffffff, 0x000000ff, 0x000000ff, + 0x000000ff, 0x000000ff, 0xffffffff, + /* 16th byte of body: 0x2d */ + 0x000000ff, 0x000000ff, 0xffffffff, 0x000000ff, 0xffffffff, + 0xffffffff, 0x000000ff, 0xffffffff, + /* 17th byte of body: 0xa1 */ + 0xffffffff, 0x000000ff, 0xffffffff, 0x000000ff, 0x000000ff, + 0x000000ff, 0x000000ff, 0xffffffff, + /* 18th byte of body: 0x2b */ + 0x000000ff, 0x000000ff, 0xffffffff, 0x000000ff, 0xffffffff, + 0x000000ff, 0xffffffff, 0xffffffff, + /* 19th byte of body: 0xa1 */ + 0xffffffff, 0x000000ff, 0xffffffff, 0x000000ff, 0x000000ff, + 0x000000ff, 0x000000ff, 0xffffffff, + /* 20th byte of body: 0x2b */ + 0x000000ff, 0x000000ff, 0xffffffff, 0x000000ff, 0xffffffff, + 0x000000ff, 0xffffffff, 0xffffffff, + /* 21th byte of body: 0x9d */ + 0xffffffff, 0x000000ff, 0x000000ff, 0xffffffff, 0xffffffff, + 0xffffffff, 0x000000ff, 0xffffffff, + /* 22th byte of body: 0xe9 */ + 0xffffffff, 0xffffffff, 0xffffffff, 0x000000ff, 0xffffffff, + 0x000000ff, 0x000000ff, 0xffffffff, + /* 23th byte of body: 0x00 */ + 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff, + 0x000000ff, 0x000000ff, 0x000000ff, + /* 24th byte of body: 0x00 */ + 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff, + 0x000000ff, 0x000000ff, 0x000000ff, + /* 25th byte of body: 0xff */ + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, + /* 26th byte of body: 0xff */ + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, + /* 27th byte of body: 0xff */ + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, + /* 28th byte of body: 0xff */ + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, + /* 29th byte of body: 0x00 */ + 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff, + 0x000000ff, 0x000000ff, 0x000000ff, + /* 30th byte of body: 0x00 */ + 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff, + 0x000000ff, 0x000000ff, 0x000000ff, + /* 31th byte of body: 0xff */ + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, + /* 32th byte of body: 0xff */ + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff]}]}, + {rawData: [ + {codingScheme: ICC_IMG_CODING_SCHEME_COLOR, + width: 0x10, + height: 0x10, + bitsPerImgPoint: 0x04, + numOfClutEntries: 0x10, + body: [0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xcf, 0xfc, 0xcc, 0xfc, 0xcc, + 0xcf, 0xcf, 0xfc, 0xcf, 0xcf, 0xff, 0xfc, 0xff, 0xcf, + 0xcc, 0xfc, 0xcf, 0xcf, 0xff, 0xfc, 0xff, 0xcf, 0xcc, + 0xfc, 0xcf, 0xcf, 0xff, 0xfc, 0xff, 0xcf, 0xcf, 0xcc, + 0xcf, 0xcf, 0xff, 0xfc, 0xff, 0xcf, 0xcf, 0xcc, 0xcf, + 0xfc, 0xcc, 0xfc, 0xcc, 0xcf, 0xcf, 0xfc, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, + 0x99, 0x99], + clut: [0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, + 0x80, 0x80, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x80, + 0x00, 0x80, 0x80, 0xc0, 0xc0, 0xc0, 0x80, 0x80, 0x80, + 0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0xff, 0xff, 0x00, + 0x00, 0x00, 0xff, 0xff, 0x00, 0xff, 0x00, 0xff, 0xff, + 0xff, 0xff, 0xff]}], + expected: [ + {width: 0x10, + height: 0x10, + pixels: [0xff0000ff, 0xff0000ff, 0xff0000ff, 0xff0000ff, 0xff0000ff, + 0xff0000ff, 0xff0000ff, 0xff0000ff, 0xff0000ff, 0xff0000ff, + 0xff0000ff, 0xff0000ff, 0xff0000ff, 0xff0000ff, 0xff0000ff, + 0xff0000ff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0x00ff00ff, 0x00ff00ff, 0x00ff00ff, + 0x00ff00ff, 0x00ff00ff, 0x00ff00ff, 0x00ff00ff, 0x00ff00ff, + 0x00ff00ff, 0x00ff00ff, 0x00ff00ff, 0x00ff00ff, 0x00ff00ff, + 0x00ff00ff, 0x00ff00ff, 0x00ff00ff, 0x00ff00ff, 0x00ff00ff, + 0x00ff00ff, 0x00ff00ff, 0x00ff00ff, 0x00ff00ff, 0x00ff00ff, + 0x00ff00ff, 0x00ff00ff, 0x00ff00ff, 0x00ff00ff, 0x00ff00ff, + 0x00ff00ff, 0x00ff00ff, 0x00ff00ff, 0x00ff00ff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0x0000ffff, 0xffffffff, 0xffffffff, 0x0000ffff, 0x0000ffff, + 0x0000ffff, 0xffffffff, 0x0000ffff, 0x0000ffff, 0x0000ffff, + 0x0000ffff, 0xffffffff, 0x0000ffff, 0xffffffff, 0xffffffff, + 0x0000ffff, 0x0000ffff, 0xffffffff, 0x0000ffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0x0000ffff, 0xffffffff, + 0xffffffff, 0x0000ffff, 0xffffffff, 0x0000ffff, 0x0000ffff, + 0xffffffff, 0x0000ffff, 0x0000ffff, 0xffffffff, 0x0000ffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x0000ffff, + 0xffffffff, 0xffffffff, 0x0000ffff, 0xffffffff, 0x0000ffff, + 0x0000ffff, 0xffffffff, 0x0000ffff, 0x0000ffff, 0xffffffff, + 0x0000ffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0x0000ffff, 0xffffffff, 0xffffffff, 0x0000ffff, 0xffffffff, + 0x0000ffff, 0xffffffff, 0x0000ffff, 0x0000ffff, 0x0000ffff, + 0xffffffff, 0x0000ffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0x0000ffff, 0xffffffff, 0xffffffff, 0x0000ffff, + 0xffffffff, 0x0000ffff, 0xffffffff, 0x0000ffff, 0x0000ffff, + 0x0000ffff, 0xffffffff, 0xffffffff, 0x0000ffff, 0x0000ffff, + 0x0000ffff, 0xffffffff, 0x0000ffff, 0x0000ffff, 0x0000ffff, + 0x0000ffff, 0xffffffff, 0x0000ffff, 0xffffffff, 0xffffffff, + 0x0000ffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0x00ff00ff, 0x00ff00ff, 0x00ff00ff, + 0x00ff00ff, 0x00ff00ff, 0x00ff00ff, 0x00ff00ff, 0x00ff00ff, + 0x00ff00ff, 0x00ff00ff, 0x00ff00ff, 0x00ff00ff, 0x00ff00ff, + 0x00ff00ff, 0x00ff00ff, 0x00ff00ff, 0x00ff00ff, 0x00ff00ff, + 0x00ff00ff, 0x00ff00ff, 0x00ff00ff, 0x00ff00ff, 0x00ff00ff, + 0x00ff00ff, 0x00ff00ff, 0x00ff00ff, 0x00ff00ff, 0x00ff00ff, + 0x00ff00ff, 0x00ff00ff, 0x00ff00ff, 0x00ff00ff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xff0000ff, 0xff0000ff, 0xff0000ff, 0xff0000ff, 0xff0000ff, + 0xff0000ff, 0xff0000ff, 0xff0000ff, 0xff0000ff, 0xff0000ff, + 0xff0000ff, 0xff0000ff, 0xff0000ff, 0xff0000ff, 0xff0000ff, + 0xff0000ff]}]}, + {rawData: [ + {codingScheme: ICC_IMG_CODING_SCHEME_COLOR, + width: 0x03, + height: 0x03, + bitsPerImgPoint: 0x05, + numOfClutEntries: 0x20, + body: [0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88], + clut: [0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, + 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, + 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, + 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, + 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, + 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, + 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, + 0x3f, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x50, + 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, + 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f]}, + {codingScheme: ICC_IMG_CODING_SCHEME_BASIC, + width: 0x10, + height: 0x10, + body: [0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, + 0x00, 0x9d, 0xe9, 0xa1, 0x2d, 0xa1, 0x2d, 0xa1, 0x2b, + 0xa1, 0x2b, 0x9d, 0xe9, 0x00, 0x00, 0xff, 0xff, 0xff, + 0xff, 0x00, 0x00, 0xff, 0xff]}], + expected: [ + {width: 0x03, + height: 0x03, + pixels: [0x000102ff, 0x060708ff, 0x0c0d0eff, 0x121314ff, 0x18191aff, + 0x1e1f20ff, 0x242526ff, 0x2a2b2cff, 0x333435ff]}, + {width: 0x10, + height: 0x10, + pixels: [/* 1st byte of body: 0xff */ + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, + /* 2nd byte of body: 0xff */ + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, + /* 3rd byte of body: 0x00 */ + 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff, + 0x000000ff, 0x000000ff, 0x000000ff, + /* 4th byte of body: 0x00 */ + 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff, + 0x000000ff, 0x000000ff, 0x000000ff, + /* 5th byte of body: 0xff */ + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, + /* 6th byte of body: 0xff */ + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, + /* 7th byte of body: 0xff */ + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, + /* 8th byte of body: 0xff */ + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, + /* 9th byte of body: 0x00 */ + 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff, + 0x000000ff, 0x000000ff, 0x000000ff, + /* 10th byte of body: 0x00 */ + 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff, + 0x000000ff, 0x000000ff, 0x000000ff, + /* 11th byte of body: 0x9d */ + 0xffffffff, 0x000000ff, 0x000000ff, 0xffffffff, 0xffffffff, + 0xffffffff, 0x000000ff, 0xffffffff, + /* 12th byte of body: 0xe9 */ + 0xffffffff, 0xffffffff, 0xffffffff, 0x000000ff, 0xffffffff, + 0x000000ff, 0x000000ff, 0xffffffff, + /* 13th byte of body: 0xa1 */ + 0xffffffff, 0x000000ff, 0xffffffff, 0x000000ff, 0x000000ff, + 0x000000ff, 0x000000ff, 0xffffffff, + /* 14th byte of body: 0x2d */ + 0x000000ff, 0x000000ff, 0xffffffff, 0x000000ff, 0xffffffff, + 0xffffffff, 0x000000ff, 0xffffffff, + /* 15th byte of body: 0xa1 */ + 0xffffffff, 0x000000ff, 0xffffffff, 0x000000ff, 0x000000ff, + 0x000000ff, 0x000000ff, 0xffffffff, + /* 16th byte of body: 0x2d */ + 0x000000ff, 0x000000ff, 0xffffffff, 0x000000ff, 0xffffffff, + 0xffffffff, 0x000000ff, 0xffffffff, + /* 17th byte of body: 0xa1 */ + 0xffffffff, 0x000000ff, 0xffffffff, 0x000000ff, 0x000000ff, + 0x000000ff, 0x000000ff, 0xffffffff, + /* 18th byte of body: 0x2b */ + 0x000000ff, 0x000000ff, 0xffffffff, 0x000000ff, 0xffffffff, + 0x000000ff, 0xffffffff, 0xffffffff, + /* 19th byte of body: 0xa1 */ + 0xffffffff, 0x000000ff, 0xffffffff, 0x000000ff, 0x000000ff, + 0x000000ff, 0x000000ff, 0xffffffff, + /* 20th byte of body: 0x2b */ + 0x000000ff, 0x000000ff, 0xffffffff, 0x000000ff, 0xffffffff, + 0x000000ff, 0xffffffff, 0xffffffff, + /* 21th byte of body: 0x9d */ + 0xffffffff, 0x000000ff, 0x000000ff, 0xffffffff, 0xffffffff, + 0xffffffff, 0x000000ff, 0xffffffff, + /* 22th byte of body: 0xe9 */ + 0xffffffff, 0xffffffff, 0xffffffff, 0x000000ff, 0xffffffff, + 0x000000ff, 0x000000ff, 0xffffffff, + /* 23th byte of body: 0x00 */ + 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff, + 0x000000ff, 0x000000ff, 0x000000ff, + /* 24th byte of body: 0x00 */ + 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff, + 0x000000ff, 0x000000ff, 0x000000ff, + /* 25th byte of body: 0xff */ + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, + /* 26th byte of body: 0xff */ + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, + /* 27th byte of body: 0xff */ + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, + /* 28th byte of body: 0xff */ + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, + /* 29th byte of body: 0x00 */ + 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff, + 0x000000ff, 0x000000ff, 0x000000ff, + /* 30th byte of body: 0x00 */ + 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff, + 0x000000ff, 0x000000ff, 0x000000ff, + /* 31th byte of body: 0xff */ + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, + /* 32th byte of body: 0xff */ + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff]}]}, + {rawData: [ + {codingScheme: ICC_IMG_CODING_SCHEME_COLOR_TRANSPARENCY, + width: 0x04, + height: 0x04, + bitsPerImgPoint: 0x04, + numOfClutEntries: 0x10, + body: [0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88], + clut: [0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, + 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, + 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, + 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, + 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, + 0x2d, 0x2e, 0x2f]}], + expected: [ + {width: 0x04, + height: 0x04, + pixels: [0x00000000, 0x00000000, 0x2a2b2cff, 0x2a2b2cff, 0x272829ff, + 0x272829ff, 0x242526ff, 0x242526ff, 0x212223ff, 0x212223ff, + 0x1e1f20ff, 0x1e1f20ff, 0x1b1c1dff, 0x1b1c1dff, 0x18191aff, + 0x18191aff]}]}]; + + function do_test(test_data, expected) { + simRecordHelper.readIMG = function fakeReadIMG(recordNumber, onsuccess, onerror) { + onsuccess(test_data); + }; + + let onsuccess = function(icons) { + // Query one record at a time. + equal(icons.length, 1); + equal(icons[0].length, expected.length); + for (let i = 0; i < icons[0].length; i++) { + // Read the i_th image of the record. + let icon = icons[0][i]; + let exp = expected[i]; + equal(icon.width, exp.width); + equal(icon.height, exp.height); + equal(icon.pixels.length, exp.pixels.length); + for (let j = 0; j < icon.pixels.length; j++) { + equal(icon.pixels[j], exp.pixels[j]); + } + } + }; + + iconLoader.loadIcons([0], onsuccess); + } + + for (let i = 0; i < test_data.length; i++) { + do_test(test_data[i].rawData, test_data[i].expected); + } + + run_next_test(); +}); + +/** + * Verify IconLoader.loadIcons. + */ +add_test(function test_load_icons() { + let worker = newUint8Worker(); + let context = worker.ContextPool._contexts[0]; + let ril = context.RIL; + let iconLoader = context.IconLoader; + let simRecordHelper = context.SimRecordHelper; + + let test_data = { + rawData: [ + // Record 1. + [{codingScheme: ICC_IMG_CODING_SCHEME_BASIC, + width: 0x10, + height: 0x10, + body: [0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, + 0x00, 0x9d, 0xe9, 0xa1, 0x2d, 0xa1, 0x2d, 0xa1, 0x2b, + 0xa1, 0x2b, 0x9d, 0xe9, 0x00, 0x00, 0xff, 0xff, 0xff, + 0xff, 0x00, 0x00, 0xff, 0xff]}], + // Record 2. + [{codingScheme: ICC_IMG_CODING_SCHEME_COLOR, + width: 0x10, + height: 0x10, + bitsPerImgPoint: 0x04, + numOfClutEntries: 0x10, + body: [0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xcf, 0xfc, 0xcc, 0xfc, 0xcc, + 0xcf, 0xcf, 0xfc, 0xcf, 0xcf, 0xff, 0xfc, 0xff, 0xcf, + 0xcc, 0xfc, 0xcf, 0xcf, 0xff, 0xfc, 0xff, 0xcf, 0xcc, + 0xfc, 0xcf, 0xcf, 0xff, 0xfc, 0xff, 0xcf, 0xcf, 0xcc, + 0xcf, 0xcf, 0xff, 0xfc, 0xff, 0xcf, 0xcf, 0xcc, 0xcf, + 0xfc, 0xcc, 0xfc, 0xcc, 0xcf, 0xcf, 0xfc, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, + 0x99, 0x99], + clut: [0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, + 0x80, 0x80, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x80, + 0x00, 0x80, 0x80, 0xc0, 0xc0, 0xc0, 0x80, 0x80, 0x80, + 0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0xff, 0xff, 0x00, + 0x00, 0x00, 0xff, 0xff, 0x00, 0xff, 0x00, 0xff, 0xff, + 0xff, 0xff, 0xff]}], + // Record 3. + [{codingScheme: ICC_IMG_CODING_SCHEME_COLOR, + width: 0x03, + height: 0x03, + bitsPerImgPoint: 0x05, + numOfClutEntries: 0x20, + body: [0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88], + clut: [0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, + 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, + 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, + 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, + 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, + 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, + 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, + 0x3f, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x50, + 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, + 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f]}, + {codingScheme: ICC_IMG_CODING_SCHEME_BASIC, + width: 0x10, + height: 0x10, + body: [0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, + 0x00, 0x9d, 0xe9, 0xa1, 0x2d, 0xa1, 0x2d, 0xa1, 0x2b, + 0xa1, 0x2b, 0x9d, 0xe9, 0x00, 0x00, 0xff, 0xff, 0xff, + 0xff, 0x00, 0x00, 0xff, 0xff]}], + // Record 4. + [{codingScheme: ICC_IMG_CODING_SCHEME_COLOR_TRANSPARENCY, + width: 0x04, + height: 0x04, + bitsPerImgPoint: 0x04, + numOfClutEntries: 0x10, + body: [0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88], + clut: [0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, + 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, + 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, + 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, + 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, + 0x2d, 0x2e, 0x2f]}]], + expected: [ + // Record 1. + [{width: 0x10, + height: 0x10, + pixels: [/* 1st byte of body: 0xff */ + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, + /* 2nd byte of body: 0xff */ + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, + /* 3rd byte of body: 0x00 */ + 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff, + 0x000000ff, 0x000000ff, 0x000000ff, + /* 4th byte of body: 0x00 */ + 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff, + 0x000000ff, 0x000000ff, 0x000000ff, + /* 5th byte of body: 0xff */ + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, + /* 6th byte of body: 0xff */ + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, + /* 7th byte of body: 0xff */ + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, + /* 8th byte of body: 0xff */ + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, + /* 9th byte of body: 0x00 */ + 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff, + 0x000000ff, 0x000000ff, 0x000000ff, + /* 10th byte of body: 0x00 */ + 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff, + 0x000000ff, 0x000000ff, 0x000000ff, + /* 11th byte of body: 0x9d */ + 0xffffffff, 0x000000ff, 0x000000ff, 0xffffffff, 0xffffffff, + 0xffffffff, 0x000000ff, 0xffffffff, + /* 12th byte of body: 0xe9 */ + 0xffffffff, 0xffffffff, 0xffffffff, 0x000000ff, 0xffffffff, + 0x000000ff, 0x000000ff, 0xffffffff, + /* 13th byte of body: 0xa1 */ + 0xffffffff, 0x000000ff, 0xffffffff, 0x000000ff, 0x000000ff, + 0x000000ff, 0x000000ff, 0xffffffff, + /* 14th byte of body: 0x2d */ + 0x000000ff, 0x000000ff, 0xffffffff, 0x000000ff, 0xffffffff, + 0xffffffff, 0x000000ff, 0xffffffff, + /* 15th byte of body: 0xa1 */ + 0xffffffff, 0x000000ff, 0xffffffff, 0x000000ff, 0x000000ff, + 0x000000ff, 0x000000ff, 0xffffffff, + /* 16th byte of body: 0x2d */ + 0x000000ff, 0x000000ff, 0xffffffff, 0x000000ff, 0xffffffff, + 0xffffffff, 0x000000ff, 0xffffffff, + /* 17th byte of body: 0xa1 */ + 0xffffffff, 0x000000ff, 0xffffffff, 0x000000ff, 0x000000ff, + 0x000000ff, 0x000000ff, 0xffffffff, + /* 18th byte of body: 0x2b */ + 0x000000ff, 0x000000ff, 0xffffffff, 0x000000ff, 0xffffffff, + 0x000000ff, 0xffffffff, 0xffffffff, + /* 19th byte of body: 0xa1 */ + 0xffffffff, 0x000000ff, 0xffffffff, 0x000000ff, 0x000000ff, + 0x000000ff, 0x000000ff, 0xffffffff, + /* 20th byte of body: 0x2b */ + 0x000000ff, 0x000000ff, 0xffffffff, 0x000000ff, 0xffffffff, + 0x000000ff, 0xffffffff, 0xffffffff, + /* 21th byte of body: 0x9d */ + 0xffffffff, 0x000000ff, 0x000000ff, 0xffffffff, 0xffffffff, + 0xffffffff, 0x000000ff, 0xffffffff, + /* 22th byte of body: 0xe9 */ + 0xffffffff, 0xffffffff, 0xffffffff, 0x000000ff, 0xffffffff, + 0x000000ff, 0x000000ff, 0xffffffff, + /* 23th byte of body: 0x00 */ + 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff, + 0x000000ff, 0x000000ff, 0x000000ff, + /* 24th byte of body: 0x00 */ + 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff, + 0x000000ff, 0x000000ff, 0x000000ff, + /* 25th byte of body: 0xff */ + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, + /* 26th byte of body: 0xff */ + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, + /* 27th byte of body: 0xff */ + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, + /* 28th byte of body: 0xff */ + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, + /* 29th byte of body: 0x00 */ + 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff, + 0x000000ff, 0x000000ff, 0x000000ff, + /* 30th byte of body: 0x00 */ + 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff, + 0x000000ff, 0x000000ff, 0x000000ff, + /* 31th byte of body: 0xff */ + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, + /* 32th byte of body: 0xff */ + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff]}], + // Record 2. + [{width: 0x10, + height: 0x10, + pixels: [0xff0000ff, 0xff0000ff, 0xff0000ff, 0xff0000ff, 0xff0000ff, + 0xff0000ff, 0xff0000ff, 0xff0000ff, 0xff0000ff, 0xff0000ff, + 0xff0000ff, 0xff0000ff, 0xff0000ff, 0xff0000ff, 0xff0000ff, + 0xff0000ff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0x00ff00ff, 0x00ff00ff, 0x00ff00ff, + 0x00ff00ff, 0x00ff00ff, 0x00ff00ff, 0x00ff00ff, 0x00ff00ff, + 0x00ff00ff, 0x00ff00ff, 0x00ff00ff, 0x00ff00ff, 0x00ff00ff, + 0x00ff00ff, 0x00ff00ff, 0x00ff00ff, 0x00ff00ff, 0x00ff00ff, + 0x00ff00ff, 0x00ff00ff, 0x00ff00ff, 0x00ff00ff, 0x00ff00ff, + 0x00ff00ff, 0x00ff00ff, 0x00ff00ff, 0x00ff00ff, 0x00ff00ff, + 0x00ff00ff, 0x00ff00ff, 0x00ff00ff, 0x00ff00ff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0x0000ffff, 0xffffffff, 0xffffffff, 0x0000ffff, 0x0000ffff, + 0x0000ffff, 0xffffffff, 0x0000ffff, 0x0000ffff, 0x0000ffff, + 0x0000ffff, 0xffffffff, 0x0000ffff, 0xffffffff, 0xffffffff, + 0x0000ffff, 0x0000ffff, 0xffffffff, 0x0000ffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0x0000ffff, 0xffffffff, + 0xffffffff, 0x0000ffff, 0xffffffff, 0x0000ffff, 0x0000ffff, + 0xffffffff, 0x0000ffff, 0x0000ffff, 0xffffffff, 0x0000ffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x0000ffff, + 0xffffffff, 0xffffffff, 0x0000ffff, 0xffffffff, 0x0000ffff, + 0x0000ffff, 0xffffffff, 0x0000ffff, 0x0000ffff, 0xffffffff, + 0x0000ffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0x0000ffff, 0xffffffff, 0xffffffff, 0x0000ffff, 0xffffffff, + 0x0000ffff, 0xffffffff, 0x0000ffff, 0x0000ffff, 0x0000ffff, + 0xffffffff, 0x0000ffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0x0000ffff, 0xffffffff, 0xffffffff, 0x0000ffff, + 0xffffffff, 0x0000ffff, 0xffffffff, 0x0000ffff, 0x0000ffff, + 0x0000ffff, 0xffffffff, 0xffffffff, 0x0000ffff, 0x0000ffff, + 0x0000ffff, 0xffffffff, 0x0000ffff, 0x0000ffff, 0x0000ffff, + 0x0000ffff, 0xffffffff, 0x0000ffff, 0xffffffff, 0xffffffff, + 0x0000ffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0x00ff00ff, 0x00ff00ff, 0x00ff00ff, + 0x00ff00ff, 0x00ff00ff, 0x00ff00ff, 0x00ff00ff, 0x00ff00ff, + 0x00ff00ff, 0x00ff00ff, 0x00ff00ff, 0x00ff00ff, 0x00ff00ff, + 0x00ff00ff, 0x00ff00ff, 0x00ff00ff, 0x00ff00ff, 0x00ff00ff, + 0x00ff00ff, 0x00ff00ff, 0x00ff00ff, 0x00ff00ff, 0x00ff00ff, + 0x00ff00ff, 0x00ff00ff, 0x00ff00ff, 0x00ff00ff, 0x00ff00ff, + 0x00ff00ff, 0x00ff00ff, 0x00ff00ff, 0x00ff00ff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xff0000ff, 0xff0000ff, 0xff0000ff, 0xff0000ff, 0xff0000ff, + 0xff0000ff, 0xff0000ff, 0xff0000ff, 0xff0000ff, 0xff0000ff, + 0xff0000ff, 0xff0000ff, 0xff0000ff, 0xff0000ff, 0xff0000ff, + 0xff0000ff]}], + // Record 3. + [{width: 0x03, + height: 0x03, + pixels: [0x000102ff, 0x060708ff, 0x0c0d0eff, 0x121314ff, 0x18191aff, + 0x1e1f20ff, 0x242526ff, 0x2a2b2cff, 0x333435ff]}, + {width: 0x10, + height: 0x10, + pixels: [/* 1st byte of body: 0xff */ + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, + /* 2nd byte of body: 0xff */ + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, + /* 3rd byte of body: 0x00 */ + 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff, + 0x000000ff, 0x000000ff, 0x000000ff, + /* 4th byte of body: 0x00 */ + 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff, + 0x000000ff, 0x000000ff, 0x000000ff, + /* 5th byte of body: 0xff */ + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, + /* 6th byte of body: 0xff */ + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, + /* 7th byte of body: 0xff */ + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, + /* 8th byte of body: 0xff */ + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, + /* 9th byte of body: 0x00 */ + 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff, + 0x000000ff, 0x000000ff, 0x000000ff, + /* 10th byte of body: 0x00 */ + 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff, + 0x000000ff, 0x000000ff, 0x000000ff, + /* 11th byte of body: 0x9d */ + 0xffffffff, 0x000000ff, 0x000000ff, 0xffffffff, 0xffffffff, + 0xffffffff, 0x000000ff, 0xffffffff, + /* 12th byte of body: 0xe9 */ + 0xffffffff, 0xffffffff, 0xffffffff, 0x000000ff, 0xffffffff, + 0x000000ff, 0x000000ff, 0xffffffff, + /* 13th byte of body: 0xa1 */ + 0xffffffff, 0x000000ff, 0xffffffff, 0x000000ff, 0x000000ff, + 0x000000ff, 0x000000ff, 0xffffffff, + /* 14th byte of body: 0x2d */ + 0x000000ff, 0x000000ff, 0xffffffff, 0x000000ff, 0xffffffff, + 0xffffffff, 0x000000ff, 0xffffffff, + /* 15th byte of body: 0xa1 */ + 0xffffffff, 0x000000ff, 0xffffffff, 0x000000ff, 0x000000ff, + 0x000000ff, 0x000000ff, 0xffffffff, + /* 16th byte of body: 0x2d */ + 0x000000ff, 0x000000ff, 0xffffffff, 0x000000ff, 0xffffffff, + 0xffffffff, 0x000000ff, 0xffffffff, + /* 17th byte of body: 0xa1 */ + 0xffffffff, 0x000000ff, 0xffffffff, 0x000000ff, 0x000000ff, + 0x000000ff, 0x000000ff, 0xffffffff, + /* 18th byte of body: 0x2b */ + 0x000000ff, 0x000000ff, 0xffffffff, 0x000000ff, 0xffffffff, + 0x000000ff, 0xffffffff, 0xffffffff, + /* 19th byte of body: 0xa1 */ + 0xffffffff, 0x000000ff, 0xffffffff, 0x000000ff, 0x000000ff, + 0x000000ff, 0x000000ff, 0xffffffff, + /* 20th byte of body: 0x2b */ + 0x000000ff, 0x000000ff, 0xffffffff, 0x000000ff, 0xffffffff, + 0x000000ff, 0xffffffff, 0xffffffff, + /* 21th byte of body: 0x9d */ + 0xffffffff, 0x000000ff, 0x000000ff, 0xffffffff, 0xffffffff, + 0xffffffff, 0x000000ff, 0xffffffff, + /* 22th byte of body: 0xe9 */ + 0xffffffff, 0xffffffff, 0xffffffff, 0x000000ff, 0xffffffff, + 0x000000ff, 0x000000ff, 0xffffffff, + /* 23th byte of body: 0x00 */ + 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff, + 0x000000ff, 0x000000ff, 0x000000ff, + /* 24th byte of body: 0x00 */ + 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff, + 0x000000ff, 0x000000ff, 0x000000ff, + /* 25th byte of body: 0xff */ + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, + /* 26th byte of body: 0xff */ + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, + /* 27th byte of body: 0xff */ + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, + /* 28th byte of body: 0xff */ + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, + /* 29th byte of body: 0x00 */ + 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff, + 0x000000ff, 0x000000ff, 0x000000ff, + /* 30th byte of body: 0x00 */ + 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff, + 0x000000ff, 0x000000ff, 0x000000ff, + /* 31th byte of body: 0xff */ + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, + /* 32th byte of body: 0xff */ + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff]}], + // Record 4. + [{width: 0x04, + height: 0x04, + pixels: [0x00000000, 0x00000000, 0x2a2b2cff, 0x2a2b2cff, 0x272829ff, + 0x272829ff, 0x242526ff, 0x242526ff, 0x212223ff, 0x212223ff, + 0x1e1f20ff, 0x1e1f20ff, 0x1b1c1dff, 0x1b1c1dff, 0x18191aff, + 0x18191aff]}]]}; + + function do_test() { + simRecordHelper.readIMG = function fakeReadIMG(recordNumber, onsuccess, onerror) { + onsuccess(test_data.rawData[recordNumber]); + }; + + let onsuccess = function(icons) { + equal(icons.length, test_data.expected.length); + for (let i = 0; i < icons.length; i++) { + for (let j = 0; j < icons[i].length; j++) { + // Read the j_th image from the i_th record. + let icon = icons[i][j]; + let expected = test_data.expected[i][j]; + equal(icon.width, expected.width); + equal(icon.height, expected.height); + equal(icon.pixels.length, expected.pixels.length); + for (let k = 0; k < icon.pixels.length; k++) { + equal(icon.pixels[k], expected.pixels[k]); + } + } + } + }; + + let recordNumbers = [0, 1, 2, 3]; + iconLoader.loadIcons(recordNumbers, onsuccess); + } + + do_test(); + run_next_test(); +}); diff --git a/dom/system/gonk/tests/test_ril_worker_icc_SimRecordHelper.js b/dom/system/gonk/tests/test_ril_worker_icc_SimRecordHelper.js new file mode 100644 index 000000000..6500cc663 --- /dev/null +++ b/dom/system/gonk/tests/test_ril_worker_icc_SimRecordHelper.js @@ -0,0 +1,1648 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +subscriptLoader.loadSubScript("resource://gre/modules/ril_consts.js", this); + +function run_test() { + run_next_test(); +} + +/** + * Verify reading EF_AD and parsing MCC/MNC + */ +add_test(function test_reading_ad_and_parsing_mcc_mnc() { + let worker = newUint8Worker(); + let context = worker.ContextPool._contexts[0]; + let record = context.SimRecordHelper; + let helper = context.GsmPDUHelper; + let ril = context.RIL; + let buf = context.Buf; + let io = context.ICCIOHelper; + + function do_test(mncLengthInEf, imsi, expectedMcc, expectedMnc) { + ril.iccInfoPrivate.imsi = imsi; + + io.loadTransparentEF = function fakeLoadTransparentEF(options) { + let ad = [0x00, 0x00, 0x00]; + if (typeof mncLengthInEf === 'number') { + ad.push(mncLengthInEf); + } + + // Write data size + buf.writeInt32(ad.length * 2); + + // Write data + for (let i = 0; i < ad.length; i++) { + helper.writeHexOctet(ad[i]); + } + + // Write string delimiter + buf.writeStringDelimiter(ad.length * 2); + + if (options.callback) { + options.callback(options); + } + }; + + record.readAD(); + + equal(ril.iccInfo.mcc, expectedMcc); + equal(ril.iccInfo.mnc, expectedMnc); + } + + do_test(undefined, "466923202422409", "466", "92" ); + do_test(0x00, "466923202422409", "466", "92" ); + do_test(0x01, "466923202422409", "466", "92" ); + do_test(0x02, "466923202422409", "466", "92" ); + do_test(0x03, "466923202422409", "466", "923"); + do_test(0x04, "466923202422409", "466", "92" ); + do_test(0xff, "466923202422409", "466", "92" ); + + do_test(undefined, "310260542718417", "310", "260"); + do_test(0x00, "310260542718417", "310", "260"); + do_test(0x01, "310260542718417", "310", "260"); + do_test(0x02, "310260542718417", "310", "26" ); + do_test(0x03, "310260542718417", "310", "260"); + do_test(0x04, "310260542718417", "310", "260"); + do_test(0xff, "310260542718417", "310", "260"); + + run_next_test(); +}); + +add_test(function test_reading_optional_efs() { + let worker = newUint8Worker(); + let context = worker.ContextPool._contexts[0]; + let record = context.SimRecordHelper; + let gsmPdu = context.GsmPDUHelper; + let ril = context.RIL; + let buf = context.Buf; + let io = context.ICCIOHelper; + + function buildSST(supportedEf) { + let sst = []; + let len = supportedEf.length; + for (let i = 0; i < len; i++) { + let index, bitmask, iccService; + if (ril.appType === CARD_APPTYPE_SIM) { + iccService = GECKO_ICC_SERVICES.sim[supportedEf[i]]; + iccService -= 1; + index = Math.floor(iccService / 4); + bitmask = 2 << ((iccService % 4) << 1); + } else if (ril.appType === CARD_APPTYPE_USIM){ + iccService = GECKO_ICC_SERVICES.usim[supportedEf[i]]; + iccService -= 1; + index = Math.floor(iccService / 8); + bitmask = 1 << ((iccService % 8) << 0); + } + + if (sst) { + sst[index] |= bitmask; + } + } + return sst; + } + + ril.updateCellBroadcastConfig = function fakeUpdateCellBroadcastConfig() { + // Ignore updateCellBroadcastConfig after reading SST + }; + + function do_test(sst, supportedEf) { + // Clone supportedEf to local array for testing + let testEf = supportedEf.slice(0); + + record.readMSISDN = function fakeReadMSISDN() { + testEf.splice(testEf.indexOf("MSISDN"), 1); + }; + + record.readMBDN = function fakeReadMBDN() { + testEf.splice(testEf.indexOf("MDN"), 1); + }; + + record.readMWIS = function fakeReadMWIS() { + testEf.splice(testEf.indexOf("MWIS"), 1); + }; + + io.loadTransparentEF = function fakeLoadTransparentEF(options) { + // Write data size + buf.writeInt32(sst.length * 2); + + // Write data + for (let i = 0; i < sst.length; i++) { + gsmPdu.writeHexOctet(sst[i] || 0); + } + + // Write string delimiter + buf.writeStringDelimiter(sst.length * 2); + + if (options.callback) { + options.callback(options); + } + + if (testEf.length !== 0) { + do_print("Un-handled EF: " + JSON.stringify(testEf)); + ok(false); + } + }; + + record.readSST(); + } + + // TODO: Add all necessary optional EFs eventually + let supportedEf = ["MSISDN", "MDN", "MWIS"]; + ril.appType = CARD_APPTYPE_SIM; + do_test(buildSST(supportedEf), supportedEf); + ril.appType = CARD_APPTYPE_USIM; + do_test(buildSST(supportedEf), supportedEf); + + run_next_test(); +}); + +/** + * Verify fetchSimRecords. + */ +add_test(function test_fetch_sim_records() { + let worker = newWorker(); + let context = worker.ContextPool._contexts[0]; + let RIL = context.RIL; + let iccRecord = context.ICCRecordHelper; + let simRecord = context.SimRecordHelper; + + function testFetchSimRecordes(expectCalled, expectCphsSuccess) { + let ifCalled = []; + + RIL.getIMSI = function() { + ifCalled.push("getIMSI"); + }; + + simRecord.readAD = function() { + ifCalled.push("readAD"); + }; + + simRecord.readCphsInfo = function(onsuccess, onerror) { + ifCalled.push("readCphsInfo"); + if (expectCphsSuccess) { + onsuccess(); + } else { + onerror(); + } + }; + + simRecord.readSST = function() { + ifCalled.push("readSST"); + }; + + simRecord.fetchSimRecords(); + + for (let i = 0; i < expectCalled.length; i++ ) { + if (ifCalled[i] != expectCalled[i]) { + do_print(expectCalled[i] + " is not called."); + ok(false); + } + } + } + + let expectCalled = ["getIMSI", "readAD", "readCphsInfo", "readSST"]; + testFetchSimRecordes(expectCalled, true); + testFetchSimRecordes(expectCalled, false); + + run_next_test(); +}); + +/** + * Verify SimRecordHelper.readMWIS + */ +add_test(function test_read_mwis() { + let worker = newUint8Worker(); + let context = worker.ContextPool._contexts[0]; + let helper = context.GsmPDUHelper; + let recordHelper = context.SimRecordHelper; + let buf = context.Buf; + let io = context.ICCIOHelper; + let mwisData; + let postedMessage; + + worker.postMessage = function fakePostMessage(message) { + postedMessage = message; + }; + + io.loadLinearFixedEF = function fakeLoadLinearFixedEF(options) { + if (mwisData) { + // Write data size + buf.writeInt32(mwisData.length * 2); + + // Write MWIS + for (let i = 0; i < mwisData.length; i++) { + helper.writeHexOctet(mwisData[i]); + } + + // Write string delimiter + buf.writeStringDelimiter(mwisData.length * 2); + + options.recordSize = mwisData.length; + if (options.callback) { + options.callback(options); + } + } else { + do_print("mwisData[] is not set."); + } + }; + + function buildMwisData(isActive, msgCount) { + if (msgCount < 0 || msgCount === GECKO_VOICEMAIL_MESSAGE_COUNT_UNKNOWN) { + msgCount = 0; + } else if (msgCount > 255) { + msgCount = 255; + } + + mwisData = [ (isActive) ? 0x01 : 0x00, + msgCount, + 0xFF, 0xFF, 0xFF ]; + } + + function do_test(isActive, msgCount) { + buildMwisData(isActive, msgCount); + recordHelper.readMWIS(); + + equal("iccmwis", postedMessage.rilMessageType); + equal(isActive, postedMessage.mwi.active); + equal((isActive) ? msgCount : 0, postedMessage.mwi.msgCount); + } + + do_test(true, GECKO_VOICEMAIL_MESSAGE_COUNT_UNKNOWN); + do_test(true, 1); + do_test(true, 255); + + do_test(false, 0); + do_test(false, 255); // Test the corner case when mwi is disable with incorrect msgCount. + + run_next_test(); +}); + +/** + * Verify SimRecordHelper.updateMWIS + */ +add_test(function test_update_mwis() { + let worker = newUint8Worker(); + let context = worker.ContextPool._contexts[0]; + let pduHelper = context.GsmPDUHelper; + let ril = context.RIL; + ril.appType = CARD_APPTYPE_USIM; + ril.iccInfoPrivate.mwis = [0x00, 0x00, 0x00, 0x00, 0x00]; + let recordHelper = context.SimRecordHelper; + let buf = context.Buf; + let ioHelper = context.ICCIOHelper; + let recordSize = ril.iccInfoPrivate.mwis.length; + let recordNum = 1; + + ioHelper.updateLinearFixedEF = function(options) { + options.pathId = context.ICCFileHelper.getEFPath(options.fileId); + options.command = ICC_COMMAND_UPDATE_RECORD; + options.p1 = options.recordNumber; + options.p2 = READ_RECORD_ABSOLUTE_MODE; + options.p3 = recordSize; + ril.iccIO(options); + }; + + function do_test(isActive, count) { + let mwis = ril.iccInfoPrivate.mwis; + let isUpdated = false; + + function buildMwisData() { + let result = mwis.slice(0); + result[0] = isActive? (mwis[0] | 0x01) : (mwis[0] & 0xFE); + result[1] = (count === GECKO_VOICEMAIL_MESSAGE_COUNT_UNKNOWN) ? 0 : count; + + return result; + } + + buf.sendParcel = function() { + isUpdated = true; + + // Request Type. + equal(this.readInt32(), REQUEST_SIM_IO); + + // Token : we don't care + this.readInt32(); + + // command. + equal(this.readInt32(), ICC_COMMAND_UPDATE_RECORD); + + // fileId. + equal(this.readInt32(), ICC_EF_MWIS); + + // pathId. + equal(this.readString(), + EF_PATH_MF_SIM + ((ril.appType === CARD_APPTYPE_USIM) ? EF_PATH_ADF_USIM : EF_PATH_DF_GSM)); + + // p1. + equal(this.readInt32(), recordNum); + + // p2. + equal(this.readInt32(), READ_RECORD_ABSOLUTE_MODE); + + // p3. + equal(this.readInt32(), recordSize); + + // data. + let strLen = this.readInt32(); + equal(recordSize * 2, strLen); + let expectedMwis = buildMwisData(); + for (let i = 0; i < recordSize; i++) { + equal(expectedMwis[i], pduHelper.readHexOctet()); + } + this.readStringDelimiter(strLen); + + // pin2. + equal(this.readString(), null); + + // AID. Ignore because it's from modem. + this.readInt32(); + }; + + ok(!isUpdated); + + recordHelper.updateMWIS({ active: isActive, + msgCount: count }); + + ok((ril.iccInfoPrivate.mwis) ? isUpdated : !isUpdated); + } + + do_test(true, GECKO_VOICEMAIL_MESSAGE_COUNT_UNKNOWN); + do_test(true, 1); + do_test(true, 255); + + do_test(false, 0); + + // Test if Path ID is correct for SIM. + ril.appType = CARD_APPTYPE_SIM; + do_test(false, 0); + + // Test if loadLinearFixedEF() is not invoked in updateMWIS() when + // EF_MWIS is not loaded/available. + delete ril.iccInfoPrivate.mwis; + do_test(false, 0); + + run_next_test(); +}); + +/** + * Verify the call flow of receiving Class 2 SMS stored in SIM: + * 1. UNSOLICITED_RESPONSE_NEW_SMS_ON_SIM. + * 2. SimRecordHelper.readSMS(). + * 3. sendChromeMessage() with rilMessageType == "sms-received". + */ +add_test(function test_read_new_sms_on_sim() { + // Instead of reusing newUint8Worker defined in this file, + // we define our own worker to fake the methods in WorkerBuffer dynamically. + function newSmsOnSimWorkerHelper() { + let _postedMessage; + let _worker = newWorker({ + postRILMessage: function(data) { + }, + postMessage: function(message) { + _postedMessage = message; + } + }); + + _worker.debug = do_print; + + return { + get postedMessage() { + return _postedMessage; + }, + get worker() { + return _worker; + }, + fakeWokerBuffer: function() { + let context = _worker.ContextPool._contexts[0]; + let index = 0; // index for read + let buf = []; + context.Buf.writeUint8 = function(value) { + buf.push(value); + }; + context.Buf.readUint8 = function() { + return buf[index++]; + }; + context.Buf.seekIncoming = function(offset) { + index += offset; + }; + context.Buf.getReadAvailable = function() { + return buf.length - index; + }; + } + }; + } + + let workerHelper = newSmsOnSimWorkerHelper(); + let worker = workerHelper.worker; + let context = worker.ContextPool._contexts[0]; + + context.ICCIOHelper.loadLinearFixedEF = function fakeLoadLinearFixedEF(options) { + // SimStatus: Unread, SMSC:+0123456789, Sender: +9876543210, Text: How are you? + let SimSmsPduHex = "0306911032547698040A9189674523010000208062917314080CC8F71D14969741F977FD07" + // In 4.2.25 EF_SMS Short Messages of 3GPP TS 31.102: + // 1. Record length == 176 bytes. + // 2. Any bytes in the record following the TPDU shall be filled with 'FF'. + + "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + + "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + + "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + + "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"; + + workerHelper.fakeWokerBuffer(); + + context.Buf.writeString(SimSmsPduHex); + + options.recordSize = 176; // Record length is fixed to 176 bytes. + if (options.callback) { + options.callback(options); + } + }; + + function newSmsOnSimParcel() { + let data = new Uint8Array(4 + 4); // Int32List with 1 element. + let offset = 0; + + function writeInt(value) { + data[offset++] = value & 0xFF; + data[offset++] = (value >> 8) & 0xFF; + data[offset++] = (value >> 16) & 0xFF; + data[offset++] = (value >> 24) & 0xFF; + } + + writeInt(1); // Length of Int32List + writeInt(1); // RecordNum = 1. + + return newIncomingParcel(-1, + RESPONSE_TYPE_UNSOLICITED, + UNSOLICITED_RESPONSE_NEW_SMS_ON_SIM, + data); + } + + function do_test() { + worker.onRILMessage(0, newSmsOnSimParcel()); + + let postedMessage = workerHelper.postedMessage; + + equal("sms-received", postedMessage.rilMessageType); + equal("+0123456789", postedMessage.SMSC); + equal("+9876543210", postedMessage.sender); + equal("How are you?", postedMessage.body); + } + + do_test(); + + run_next_test(); +}); + +/** + * Verify the result of updateDisplayCondition after reading EF_SPDI | EF_SPN. + */ +add_test(function test_update_display_condition() { + let worker = newUint8Worker(); + let context = worker.ContextPool._contexts[0]; + let record = context.SimRecordHelper; + let helper = context.GsmPDUHelper; + let ril = context.RIL; + let buf = context.Buf; + let io = context.ICCIOHelper; + + function do_test_spdi() { + // No EF_SPN, but having EF_SPDI. + // It implies "ril.iccInfoPrivate.spnDisplayCondition = undefined;". + io.loadTransparentEF = function fakeLoadTransparentEF(options) { + // PLMN lists are : 234-136 and 466-92. + let spdi = [0xA3, 0x0B, 0x80, 0x09, 0x32, 0x64, 0x31, 0x64, 0x26, 0x9F, + 0xFF, 0xFF, 0xFF]; + + // Write data size. + buf.writeInt32(spdi.length * 2); + + // Write data. + for (let i = 0; i < spdi.length; i++) { + helper.writeHexOctet(spdi[i]); + } + + // Write string delimiter. + buf.writeStringDelimiter(spdi.length * 2); + + if (options.callback) { + options.callback(options); + } + }; + + record.readSPDI(); + + equal(ril.iccInfo.isDisplayNetworkNameRequired, true); + equal(ril.iccInfo.isDisplaySpnRequired, false); + } + + function do_test_spn(displayCondition, + expectedPlmnNameDisplay, + expectedSpnDisplay) { + io.loadTransparentEF = function fakeLoadTransparentEF(options) { + // "Android" as Service Provider Name. + let spn = [0x41, 0x6E, 0x64, 0x72, 0x6F, 0x69, 0x64]; + if (typeof displayCondition === 'number') { + spn.unshift(displayCondition); + } + + // Write data size. + buf.writeInt32(spn.length * 2); + + // Write data. + for (let i = 0; i < spn.length; i++) { + helper.writeHexOctet(spn[i]); + } + + // Write string delimiter. + buf.writeStringDelimiter(spn.length * 2); + + if (options.callback) { + options.callback(options); + } + }; + + record.readSPN(); + + equal(ril.iccInfo.isDisplayNetworkNameRequired, expectedPlmnNameDisplay); + equal(ril.iccInfo.isDisplaySpnRequired, expectedSpnDisplay); + } + + // Create empty operator object. + ril.operator = {}; + // Setup SIM MCC-MNC to 310-260 as home network. + ril.iccInfo.mcc = 310; + ril.iccInfo.mnc = 260; + + do_test_spdi(); + + // No network. + do_test_spn(0x00, true, true); + do_test_spn(0x01, true, true); + do_test_spn(0x02, true, false); + do_test_spn(0x03, true, false); + + // Home network. + ril.operator.mcc = 310; + ril.operator.mnc = 260; + do_test_spn(0x00, false, true); + do_test_spn(0x01, true, true); + do_test_spn(0x02, false, true); + do_test_spn(0x03, true, true); + + // Not HPLMN but in PLMN list. + ril.iccInfoPrivate.SPDI = [{"mcc":"234","mnc":"136"},{"mcc":"466","mnc":"92"}]; + ril.operator.mcc = 466; + ril.operator.mnc = 92; + do_test_spn(0x00, false, true); + do_test_spn(0x01, true, true); + do_test_spn(0x02, false, true); + do_test_spn(0x03, true, true); + ril.iccInfoPrivate.SPDI = null; // reset SPDI to null; + + // Non-Home network. + ril.operator.mcc = 466; + ril.operator.mnc = 01; + do_test_spn(0x00, true, true); + do_test_spn(0x01, true, true); + do_test_spn(0x02, true, false); + do_test_spn(0x03, true, false); + + run_next_test(); +}); + +/** + * Verify reading EF_IMG and EF_IIDF with ICC_IMG_CODING_SCHEME_BASIC + */ +add_test(function test_reading_img_basic() { + let worker = newUint8Worker(); + let context = worker.ContextPool._contexts[0]; + let record = context.SimRecordHelper; + let helper = context.GsmPDUHelper; + let ril = context.RIL; + let buf = context.Buf; + let io = context.ICCIOHelper; + + let test_data = [ + {img: [0x01, 0x05, 0x05, 0x11, 0x4f, 0x00, 0x00, 0x00, 0x00, 0x06], + iidf: [ + [/* Header */ + 0x05, 0x05, + /* Image body */ + 0x11, 0x33, 0x55, 0xfe]], + expected: [ + {width: 0x05, + height: 0x05, + codingScheme: ICC_IMG_CODING_SCHEME_BASIC, + body: [0x11, 0x33, 0x55, 0xfe]}]}, + {img: [0x01, 0x05, 0x05, 0x11, 0x4f, 0x00, 0x00, 0x00, 0x00, 0x06, + /* Padding */ + 0xff, 0xff], + iidf: [ + [/* Header */ + 0x05, 0x05, + /* Image body */ + 0x11, 0x33, 0x55, 0xfe]], + expected: [ + {width: 0x05, + height: 0x05, + codingScheme: ICC_IMG_CODING_SCHEME_BASIC, + body: [0x11, 0x33, 0x55, 0xfe]}]}, + {img: [0x02, 0x10, 0x01, 0x11, 0x4f, 0x04, 0x00, 0x05, 0x00, 0x04, 0x10, + 0x01, 0x11, 0x4f, 0x05, 0x00, 0x05, 0x00, 0x04], + iidf: [ + [/* Data offset */ + 0xff, 0xff, 0xff, 0xff, 0xff, + /* Header */ + 0x10, 0x01, + /* Image body */ + 0x11, 0x99, + /* Trailing data */ + 0xff, 0xff, 0xff], + [/* Data offset */ + 0xff, 0xff, 0xff, 0xff, 0xff, + /* Header */ + 0x10, 0x01, + /* Image body */ + 0x99, 0x11]], + expected: [ + {width: 0x10, + height: 0x01, + codingScheme: ICC_IMG_CODING_SCHEME_BASIC, + body: [0x11, 0x99]}, + {width: 0x10, + height: 0x01, + codingScheme: ICC_IMG_CODING_SCHEME_BASIC, + body: [0x99, 0x11]}]}, + {img: [0x01, 0x28, 0x20, 0x11, 0x4f, 0xac, 0x00, 0x0b, 0x00, 0xa2], + iidf: [ + [/* Data offset */ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + /* Header */ + 0x28, 0x20, + /* Image body */ + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, + 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, + 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, + 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, + 0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, + 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x40, 0x41, + 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, + 0x4d, 0x4e, 0x4f, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, 0x00, 0x01, 0x02, + 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, + 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, + 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, + 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, + 0x2f, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, + 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f]], + expected: [ + {width: 0x28, + height: 0x20, + codingScheme: ICC_IMG_CODING_SCHEME_BASIC, + body: [0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, + 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, + 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, + 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31, + 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, + 0x3c, 0x3d, 0x3e, 0x3f, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, + 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, + 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, 0x00, 0x01, 0x02, 0x03, + 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, + 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, + 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, + 0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, + 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f]}]}]; + + function do_test(img, iidf, expected) { + io.loadLinearFixedEF = function fakeLoadLinearFixedEF(options) { + // Write data size + buf.writeInt32(img.length * 2); + + // Write data + for (let i = 0; i < img.length; i++) { + helper.writeHexOctet(img[i]); + } + + // Write string delimiter + buf.writeStringDelimiter(img.length * 2); + + if (options.callback) { + options.callback(options); + } + }; + + let instanceIndex = 0; + io.loadTransparentEF = function fakeLoadTransparentEF(options) { + // Write data size + buf.writeInt32(iidf[instanceIndex].length * 2); + + // Write data + for (let i = 0; i < iidf[instanceIndex].length; i++) { + helper.writeHexOctet(iidf[instanceIndex][i]); + } + + // Write string delimiter + buf.writeStringDelimiter(iidf[instanceIndex].length * 2); + + instanceIndex++; + + if (options.callback) { + options.callback(options); + } + }; + + let onsuccess = function(icons) { + equal(icons.length, expected.length); + for (let i = 0; i < icons.length; i++) { + let icon = icons[i]; + let exp = expected[i]; + equal(icon.width, exp.width); + equal(icon.height, exp.height); + equal(icon.codingScheme, exp.codingScheme); + + equal(icon.body.length, exp.body.length); + for (let j = 0; j < icon.body.length; j++) { + equal(icon.body[j], exp.body[j]); + } + } + }; + record.readIMG(0, onsuccess); + } + + for (let i = 0; i< test_data.length; i++) { + do_test(test_data[i].img, test_data[i].iidf, test_data[i].expected); + } + run_next_test(); +}); + +/** + * Verify reading EF_IMG and EF_IIDF with the case data length is not enough + */ +add_test(function test_reading_img_length_error() { + let worker = newUint8Worker(); + let context = worker.ContextPool._contexts[0]; + let record = context.SimRecordHelper; + let helper = context.GsmPDUHelper; + let ril = context.RIL; + let buf = context.Buf; + let io = context.ICCIOHelper; + + let test_data = [ + {/* Offset length not enough, should be 4. */ + img: [0x01, 0x05, 0x05, 0x11, 0x4f, 0x00, 0x00, 0x04, 0x00, 0x06], + iidf: [0xff, 0xff, 0xff, // Offset. + 0x05, 0x05, 0x11, 0x22, 0x33, 0xfe]}, + {/* iidf data length not enough, should be 6. */ + img: [0x01, 0x05, 0x05, 0x11, 0x4f, 0x00, 0x00, 0x00, 0x00, 0x06], + iidf: [0x05, 0x05, 0x11, 0x22, 0x33]}]; + + function do_test(img, iidf) { + io.loadLinearFixedEF = function fakeLoadLinearFixedEF(options) { + // Write data size + buf.writeInt32(img.length * 2); + + // Write data + for (let i = 0; i < img.length; i++) { + helper.writeHexOctet(img[i]); + } + + // Write string delimiter + buf.writeStringDelimiter(img.length * 2); + + if (options.callback) { + options.callback(options); + } + }; + + io.loadTransparentEF = function fakeLoadTransparentEF(options) { + // Write data size + buf.writeInt32(iidf.length * 2); + + // Write data + for (let i = 0; i < iidf.length; i++) { + helper.writeHexOctet(iidf[i]); + } + + // Write string delimiter + buf.writeStringDelimiter(iidf.length * 2); + + if (options.callback) { + options.callback(options); + } + }; + + let onsuccess = function() { + do_print("onsuccess shouldn't be called."); + ok(false); + }; + + let onerror = function() { + do_print("onerror called as expected."); + ok(true); + }; + + record.readIMG(0, onsuccess, onerror); + } + + for (let i = 0; i < test_data.length; i++) { + do_test(test_data[i].img, test_data[i].iidf); + } + run_next_test(); +}); + +/** + * Verify reading EF_IMG and EF_IIDF with an invalid fileId + */ +add_test(function test_reading_img_invalid_fileId() { + let worker = newUint8Worker(); + let context = worker.ContextPool._contexts[0]; + let record = context.SimRecordHelper; + let helper = context.GsmPDUHelper; + let ril = context.RIL; + let buf = context.Buf; + let io = context.ICCIOHelper; + + // Test invalid fileId: 0x5f00. + let img_test = [0x01, 0x05, 0x05, 0x11, 0x5f, 0x00, 0x00, 0x00, 0x00, 0x06]; + let iidf_test = [0x05, 0x05, 0x11, 0x22, 0x33, 0xfe]; + + io.loadLinearFixedEF = function fakeLoadLinearFixedEF(options) { + // Write data size + buf.writeInt32(img_test.length * 2); + + // Write data + for (let i = 0; i < img_test.length; i++) { + helper.writeHexOctet(img_test[i]); + } + + // Write string delimiter + buf.writeStringDelimiter(img_test.length * 2); + + if (options.callback) { + options.callback(options); + } + }; + + io.loadTransparentEF = function fakeLoadTransparentEF(options) { + // Write data size + buf.writeInt32(iidf_test.length * 2); + + // Write data + for (let i = 0; i < iidf_test.length; i++) { + helper.writeHexOctet(iidf_test[i]); + } + + // Write string delimiter + buf.writeStringDelimiter(iidf_test.length * 2); + + if (options.callback) { + options.callback(options); + } + }; + + let onsuccess = function() { + do_print("onsuccess shouldn't be called."); + ok(false); + }; + + let onerror = function() { + do_print("onerror called as expected."); + ok(true); + }; + + record.readIMG(0, onsuccess, onerror); + + run_next_test(); +}); + +/** + * Verify reading EF_IMG with a wrong record length + */ +add_test(function test_reading_img_wrong_record_length() { + let worker = newUint8Worker(); + let context = worker.ContextPool._contexts[0]; + let record = context.SimRecordHelper; + let helper = context.GsmPDUHelper; + let ril = context.RIL; + let buf = context.Buf; + let io = context.ICCIOHelper; + + let test_data = [ + [0x01, 0x05, 0x05, 0x11, 0x4f, 0x00, 0x00, 0x00], + [0x02, 0x05, 0x05, 0x11, 0x4f, 0x00, 0x00, 0x00, 0x00, 0x06]]; + + function do_test(img) { + io.loadLinearFixedEF = function fakeLoadLinearFixedEF(options) { + // Write data size + buf.writeInt32(img.length * 2); + + // Write data + for (let i = 0; i < img.length; i++) { + helper.writeHexOctet(img[i]); + } + + // Write string delimiter + buf.writeStringDelimiter(img.length * 2); + + if (options.callback) { + options.callback(options); + } + }; + + let onsuccess = function() { + do_print("onsuccess shouldn't be called."); + ok(false); + }; + + let onerror = function() { + do_print("onerror called as expected."); + ok(true); + }; + + record.readIMG(0, onsuccess, onerror); + } + + for (let i = 0; i < test_data.length; i++) { + do_test(test_data[i]); + } + run_next_test(); +}); + +/** + * Verify reading EF_IMG and EF_IIDF with ICC_IMG_CODING_SCHEME_COLOR + */ +add_test(function test_reading_img_color() { + let worker = newUint8Worker(); + let context = worker.ContextPool._contexts[0]; + let record = context.SimRecordHelper; + let helper = context.GsmPDUHelper; + let ril = context.RIL; + let buf = context.Buf; + let io = context.ICCIOHelper; + + let test_data = [ + {img: [0x01, 0x05, 0x05, 0x21, 0x4f, 0x11, 0x00, 0x00, 0x00, 0x13], + iidf: [ + [/* Header */ + 0x05, 0x05, 0x03, 0x08, 0x00, 0x13, + /* Image body */ + 0x10, 0x20, 0x30, 0x40, 0x50, 0x60, 0x70, 0x80, 0x90, 0xa0, + 0xb0, 0xc0, 0xd0, + /* Clut entries */ + 0x00, 0x01, 0x02, + 0x10, 0x11, 0x12, + 0x20, 0x21, 0x22, + 0x30, 0x31, 0x32, + 0x40, 0x41, 0x42, + 0x50, 0x51, 0x52, + 0x60, 0x61, 0x62, + 0x70, 0x71, 0x72]], + expected: [ + {width: 0x05, + height: 0x05, + codingScheme: ICC_IMG_CODING_SCHEME_COLOR, + bitsPerImgPoint: 0x03, + numOfClutEntries: 0x08, + body: [0x10, 0x20, 0x30, 0x40, 0x50, 0x60, 0x70, 0x80, 0x90, 0xa0, 0xb0, + 0xc0, 0xd0], + clut: [0x00, 0x01, 0x02, + 0x10, 0x11, 0x12, + 0x20, 0x21, 0x22, + 0x30, 0x31, 0x32, + 0x40, 0x41, 0x42, + 0x50, 0x51, 0x52, + 0x60, 0x61, 0x62, + 0x70, 0x71, 0x72]}]}, + {img: [0x02, 0x01, 0x06, 0x21, 0x4f, 0x33, 0x00, 0x02, 0x00, 0x08, 0x01, + 0x06, 0x21, 0x4f, 0x44, 0x00, 0x02, 0x00, 0x08], + iidf: [ + [/* Data offset */ + 0xff, 0xff, + /* Header */ + 0x01, 0x06, 0x02, 0x04, 0x00, 0x0d, + /* Image body */ + 0x40, 0x50, + /* Clut offset */ + 0xaa, 0xbb, 0xcc, + /* Clut entries */ + 0x01, 0x03, 0x05, + 0x21, 0x23, 0x25, + 0x41, 0x43, 0x45, + 0x61, 0x63, 0x65], + [/* Data offset */ + 0xff, 0xff, + /* Header */ + 0x01, 0x06, 0x02, 0x04, 0x00, 0x0d, + /* Image body */ + 0x4f, 0x5f, + /* Clut offset */ + 0xaa, 0xbb, 0xcc, + /* Clut entries */ + 0x11, 0x13, 0x15, + 0x21, 0x23, 0x25, + 0x41, 0x43, 0x45, + 0x61, 0x63, 0x65]], + expected: [ + {width: 0x01, + height: 0x06, + codingScheme: ICC_IMG_CODING_SCHEME_COLOR, + bitsPerImgPoint: 0x02, + numOfClutEntries: 0x04, + body: [0x40, 0x50], + clut: [0x01, 0x03, 0x05, + 0x21, 0x23, 0x25, + 0x41, 0x43, 0x45, + 0x61, 0x63, 0x65]}, + {width: 0x01, + height: 0x06, + codingScheme: ICC_IMG_CODING_SCHEME_COLOR, + bitsPerImgPoint: 0x02, + numOfClutEntries: 0x04, + body: [0x4f, 0x5f], + clut: [0x11, 0x13, 0x15, + 0x21, 0x23, 0x25, + 0x41, 0x43, 0x45, + 0x61, 0x63, 0x65]}]}]; + + function do_test(img, iidf, expected) { + io.loadLinearFixedEF = function fakeLoadLinearFixedEF(options) { + // Write data size + buf.writeInt32(img.length * 2); + + // Write data + for (let i = 0; i < img.length; i++) { + helper.writeHexOctet(img[i]); + } + + // Write string delimiter + buf.writeStringDelimiter(img.length * 2); + + if (options.callback) { + options.callback(options); + } + }; + + let instanceIndex = 0; + io.loadTransparentEF = function fakeLoadTransparentEF(options) { + // Write data size + buf.writeInt32(iidf[instanceIndex].length * 2); + + // Write data + for (let i = 0; i < iidf[instanceIndex].length; i++) { + helper.writeHexOctet(iidf[instanceIndex][i]); + } + + // Write string delimiter + buf.writeStringDelimiter(iidf[instanceIndex].length * 2); + + instanceIndex++; + + if (options.callback) { + options.callback(options); + } + }; + + let onsuccess = function(icons) { + equal(icons.length, expected.length); + for (let i = 0; i < icons.length; i++) { + let icon = icons[i]; + let exp = expected[i]; + equal(icon.width, exp.width); + equal(icon.height, exp.height); + equal(icon.codingScheme, exp.codingScheme); + + equal(icon.body.length, exp.body.length); + for (let j = 0; j < icon.body.length; j++) { + equal(icon.body[j], exp.body[j]); + } + + equal(icon.clut.length, exp.clut.length); + for (let j = 0; j < icon.clut.length; j++) { + equal(icon.clut[j], exp.clut[j]); + } + } + }; + + record.readIMG(0, onsuccess); + } + + for (let i = 0; i< test_data.length; i++) { + do_test(test_data[i].img, test_data[i].iidf, test_data[i].expected); + } + run_next_test(); +}); + +/** + * Verify reading EF_IMG and EF_IIDF with + * ICC_IMG_CODING_SCHEME_COLOR_TRANSPARENCY + */ +add_test(function test_reading_img_color() { + let worker = newUint8Worker(); + let context = worker.ContextPool._contexts[0]; + let record = context.SimRecordHelper; + let helper = context.GsmPDUHelper; + let ril = context.RIL; + let buf = context.Buf; + let io = context.ICCIOHelper; + + let test_data = [ + {img: [0x01, 0x05, 0x05, 0x22, 0x4f, 0x11, 0x00, 0x00, 0x00, 0x13], + iidf: [ + [/* Header */ + 0x05, 0x05, 0x03, 0x08, 0x00, 0x13, + /* Image body */ + 0x10, 0x20, 0x30, 0x40, 0x50, 0x60, 0x70, 0x80, 0x90, 0xa0, + 0xb0, 0xc0, 0xd0, + /* Clut entries */ + 0x00, 0x01, 0x02, + 0x10, 0x11, 0x12, + 0x20, 0x21, 0x22, + 0x30, 0x31, 0x32, + 0x40, 0x41, 0x42, + 0x50, 0x51, 0x52, + 0x60, 0x61, 0x62, + 0x70, 0x71, 0x72]], + expected: [ + {width: 0x05, + height: 0x05, + codingScheme: ICC_IMG_CODING_SCHEME_COLOR_TRANSPARENCY, + bitsPerImgPoint: 0x03, + numOfClutEntries: 0x08, + body: [0x10, 0x20, 0x30, 0x40, 0x50, 0x60, 0x70, 0x80, 0x90, + 0xa0, 0xb0, 0xc0, 0xd0], + clut: [0x00, 0x01, 0x02, + 0x10, 0x11, 0x12, + 0x20, 0x21, 0x22, + 0x30, 0x31, 0x32, + 0x40, 0x41, 0x42, + 0x50, 0x51, 0x52, + 0x60, 0x61, 0x62, + 0x70, 0x71, 0x72]}]}, + {img: [0x02, 0x01, 0x06, 0x22, 0x4f, 0x33, 0x00, 0x02, 0x00, 0x08, 0x01, + 0x06, 0x22, 0x4f, 0x33, 0x00, 0x02, 0x00, 0x08], + iidf: [ + [/* Data offset */ + 0xff, 0xff, + /* Header */ + 0x01, 0x06, 0x02, 0x04, 0x00, 0x0d, + /* Image body */ + 0x40, 0x50, + /* Clut offset */ + 0x0a, 0x0b, 0x0c, + /* Clut entries */ + 0x01, 0x03, 0x05, + 0x21, 0x23, 0x25, + 0x41, 0x43, 0x45, + 0x61, 0x63, 0x65], + [/* Data offset */ + 0xff, 0xff, + /* Header */ + 0x01, 0x06, 0x02, 0x04, 0x00, 0x0d, + /* Image body */ + 0x4f, 0x5f, + /* Clut offset */ + 0x0a, 0x0b, 0x0c, + /* Clut entries */ + 0x11, 0x13, 0x15, + 0x21, 0x23, 0x25, + 0x41, 0x43, 0x45, + 0x61, 0x63, 0x65]], + expected: [ + {width: 0x01, + height: 0x06, + codingScheme: ICC_IMG_CODING_SCHEME_COLOR_TRANSPARENCY, + bitsPerImgPoint: 0x02, + numOfClutEntries: 0x04, + body: [0x40, 0x50], + clut: [0x01, 0x03, 0x05, + 0x21, 0x23, 0x25, + 0x41, 0x43, 0x45, + 0x61, 0x63, 0x65]}, + {width: 0x01, + height: 0x06, + codingScheme: ICC_IMG_CODING_SCHEME_COLOR_TRANSPARENCY, + bitsPerImgPoint: 0x02, + numOfClutEntries: 0x04, + body: [0x4f, 0x5f], + clut: [0x11, 0x13, 0x15, + 0x21, 0x23, 0x25, + 0x41, 0x43, 0x45, + 0x61, 0x63, 0x65]}]}]; + + function do_test(img, iidf, expected) { + io.loadLinearFixedEF = function fakeLoadLinearFixedEF(options) { + // Write data size + buf.writeInt32(img.length * 2); + + // Write data + for (let i = 0; i < img.length; i++) { + helper.writeHexOctet(img[i]); + } + + // Write string delimiter + buf.writeStringDelimiter(img.length * 2); + + if (options.callback) { + options.callback(options); + } + }; + + let instanceIndex = 0; + io.loadTransparentEF = function fakeLoadTransparentEF(options) { + // Write data size + buf.writeInt32(iidf[instanceIndex].length * 2); + + // Write data + for (let i = 0; i < iidf[instanceIndex].length; i++) { + helper.writeHexOctet(iidf[instanceIndex][i]); + } + + // Write string delimiter + buf.writeStringDelimiter(iidf[instanceIndex].length * 2); + + instanceIndex++; + + if (options.callback) { + options.callback(options); + } + }; + + let onsuccess = function(icons) { + equal(icons.length, expected.length); + for (let i = 0; i < icons.length; i++) { + let icon = icons[i]; + let exp = expected[i]; + equal(icon.width, exp.width); + equal(icon.height, exp.height); + equal(icon.codingScheme, exp.codingScheme); + + equal(icon.body.length, exp.body.length); + for (let j = 0; j < icon.body.length; j++) { + equal(icon.body[j], exp.body[j]); + } + + equal(icon.clut.length, exp.clut.length); + for (let j = 0; j < icon.clut.length; j++) { + equal(icon.clut[j], exp.clut[j]); + } + } + }; + + record.readIMG(0, onsuccess); + } + + for (let i = 0; i< test_data.length; i++) { + do_test(test_data[i].img, test_data[i].iidf, test_data[i].expected); + } + run_next_test(); +}); + +/** + * Verify SimRecordHelper.readCphsInfo + */ +add_test(function test_read_cphs_info() { + let worker = newUint8Worker(); + let context = worker.ContextPool._contexts[0]; + let RIL = context.RIL; + let pduHelper = context.GsmPDUHelper; + let recordHelper = context.SimRecordHelper; + let buf = context.Buf; + let io = context.ICCIOHelper; + let cphsPDU = new Uint8Array(3); + + io.loadTransparentEF = function(options) { + if (cphsPDU) { + // Write data size + buf.writeInt32(cphsPDU.length * 2); + + // Write CPHS INFO + for (let i = 0; i < cphsPDU.length; i++) { + pduHelper.writeHexOctet(cphsPDU[i]); + } + + // Write string delimiter + buf.writeStringDelimiter(cphsPDU.length * 2); + + if (options.callback) { + options.callback(options); + } + } else { + do_print("cphsPDU[] is not set."); + } + }; + + function do_test(cphsInfo, cphsSt) { + let onsuccess = false; + let onerror = false; + + delete RIL.iccInfoPrivate.cphsSt; + cphsPDU.set(cphsInfo); + recordHelper.readCphsInfo(() => { onsuccess = true; }, + () => { onerror = true; }); + + ok((cphsSt) ? onsuccess : onerror); + ok(!((cphsSt) ? onerror : onsuccess)); + if (cphsSt) { + equal(RIL.iccInfoPrivate.cphsSt.length, cphsSt.length); + for (let i = 0; i < cphsSt.length; i++) { + equal(RIL.iccInfoPrivate.cphsSt[i], cphsSt[i]); + } + } else { + equal(RIL.iccInfoPrivate.cphsSt, cphsSt); + } + } + + do_test([ + 0x01, // Phase 1 + 0xFF, // All available & activated + 0x03 // All available & activated + ], + [ + 0x3F, // All services except ONSF(bit 8-7) are available and activated. + 0x00 // INFO_NUM shall not be available & activated. + ]); + + do_test([ + 0x02, // Phase 2 + 0xFF, // All available & activated + 0x03 // All available & activated + ], + [ + 0xF3, // All services except ONSF are available and activated. + 0x03 // INFO_NUM shall not be available & activated. + ]); + + do_test([ + 0x03, // Phase 3 + 0xFF, // All available & activated + 0x03 // All available & activated + ], + undefined); // RIL.iccInfoPrivate.cphsSt shall be remained as 'undefined'. + + run_next_test(); +}); + +/** + * Verify SimRecordHelper.readMBDN/SimRecordHelper.readCphsMBN + */ +add_test(function test_read_voicemail_number() { + let worker = newUint8Worker(); + let context = worker.ContextPool._contexts[0]; + let RIL = context.RIL; + let pduHelper = context.GsmPDUHelper; + let recordHelper = context.SimRecordHelper; + let buf = context.Buf; + let io = context.ICCIOHelper; + let postedMessage; + + worker.postMessage = function(message) { + postedMessage = message; + }; + + io.loadLinearFixedEF = function(options) { + let mbnData = [ + 0x56, 0x6F, 0x69, 0x63, 0x65, 0x6D, 0x61, 0x69, + 0x6C, 0xFF, // Alpha Identifier: Voicemail + 0x03, // Length of BCD number: 3 + 0x80, // TOA: Unknown + 0x11, 0xF1, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, // Dialing Number: 111 + 0xFF, // Capability/Configuration Record Identifier + 0xFF // Extension Record Identifier + ]; + + // Write data size + buf.writeInt32(mbnData.length * 2); + + // Write MBN + for (let i = 0; i < mbnData.length; i++) { + pduHelper.writeHexOctet(mbnData[i]); + } + + // Write string delimiter + buf.writeStringDelimiter(mbnData.length * 2); + + options.recordSize = mbnData.length; + if (options.callback) { + options.callback(options); + } + }; + + function do_test(funcName, msgCount) { + postedMessage = null; + delete RIL.iccInfoPrivate.mbdn; + recordHelper[funcName](); + + equal("iccmbdn", postedMessage.rilMessageType); + equal("Voicemail", postedMessage.alphaId); + equal("111", postedMessage.number); + } + + do_test("readMBDN"); + do_test("readCphsMBN"); + + run_next_test(); +}); + +/** + * Verify the recovery from SimRecordHelper.readCphsMBN() if MBDN is not valid + * or is empty after SimRecordHelper.readMBDN(). + */ +add_test(function test_read_mbdn_recovered_from_cphs_mbn() { + let worker = newUint8Worker(); + let context = worker.ContextPool._contexts[0]; + let RIL = context.RIL; + let pduHelper = context.GsmPDUHelper; + let recordHelper = context.SimRecordHelper; + let iccUtilsHelper = context.ICCUtilsHelper; + let buf = context.Buf; + let io = context.ICCIOHelper; + + io.loadLinearFixedEF = function(options) { + let mbnData = [ + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF + ]; + + // Write data size + buf.writeInt32(mbnData.length * 2); + + // Write MBN + for (let i = 0; i < mbnData.length; i++) { + pduHelper.writeHexOctet(mbnData[i]); + } + + // Write string delimiter + buf.writeStringDelimiter(mbnData.length * 2); + + options.recordSize = mbnData.length; + if (options.callback) { + options.callback(options); + } + }; + + iccUtilsHelper.isCphsServiceAvailable = function(geckoService) { + return geckoService == "MBN"; + }; + + let isRecovered = false; + recordHelper.readCphsMBN = function(onComplete) { + isRecovered = true; + }; + + recordHelper.readMBDN(); + + equal(RIL.iccInfoPrivate.mbdn, undefined); + ok(isRecovered); + + run_next_test(); +}); + +/** + * Verify reading EF_PNN with different coding scheme. + */ +add_test(function test_pnn_with_different_coding_scheme() { + let worker = newUint8Worker(); + let context = worker.ContextPool._contexts[0]; + let record = context.SimRecordHelper; + let pduHelper = context.GsmPDUHelper; + let ril = context.RIL; + let buf = context.Buf; + let io = context.ICCIOHelper; + + let test_data = [{ + // Cell Broadcast data coding scheme - "Test1" + pnn: [0x43, 0x06, 0x85, 0xD4, 0xF2, 0x9C, 0x1E, 0x03], + expectedResult: "Test1" + },{ + // UCS2 with 0x80 - "Test1" + pnn: [0x43, 0x0C, 0x90, 0x80, 0x00, 0x54, 0x00, 0x65, 0x00, 0x73, 0x00, 0x74, 0x00, 0x31], + expectedResult: "Test1" + },{ + // UCS2 with 0x81 - "Mozilla\u694a" + pnn: [0x43, 0x0E, 0x90, 0x81, 0x08, 0xd2, 0x4d, 0x6f, 0x7a, 0x69, 0x6c, 0x6c, 0x61, 0xca, 0xff, 0xff], + expectedResult: "Mozilla\u694a" + },{ + // UCS2 with 0x82 - "Mozilla\u694a" + pnn: [0x43, 0x0F, 0x90, 0x82, 0x08, 0x69, 0x00, 0x4d, 0x6f, 0x7a, 0x69, 0x6c, 0x6c, 0x61, 0xca, 0xff, 0xff], + expectedResult: "Mozilla\u694a" + }]; + + function do_test_pnn(pnn, expectedResult) { + io.loadLinearFixedEF = function fakeLoadLinearFixedEF(options) { + // Write data size. + buf.writeInt32(pnn.length * 2); + + // Write data. + for (let i = 0; i < pnn.length; i++) { + pduHelper.writeHexOctet(pnn[i]); + } + + // Write string delimiter. + buf.writeStringDelimiter(pnn.length * 2); + + if (options.callback) { + options.callback(options); + } + }; + + record.readPNN(); + + equal(ril.iccInfoPrivate.PNN[0].fullName, expectedResult); + // Reset PNN info for next test + ril.iccInfoPrivate.PNN = null; + } + + ril.appType = CARD_APPTYPE_SIM; + for (let i = 0; i < test_data.length; i++) { + do_test_pnn(test_data[i].pnn, test_data[i].expectedResult); + } + + run_next_test(); +}); + +/** + * Verify reading EF_PNN with different content. + */ +add_test(function test_pnn_with_different_content() { + let worker = newUint8Worker(); + let context = worker.ContextPool._contexts[0]; + let record = context.SimRecordHelper; + let pduHelper = context.GsmPDUHelper; + let ril = context.RIL; + let buf = context.Buf; + let io = context.ICCIOHelper; + + let test_data = [{ + // [0]: {"fullName":"Test1","shortName":"Test1"} + pnn: [0x43, 0x06, 0x85, 0xD4, 0xF2, 0x9C, 0x1E, 0x03, + 0x45, 0x06, 0x85, 0xD4, 0xF2, 0x9C, 0x1E, 0x03], + expectedResult: {"fullName": "Test1","shortName": "Test1"} + },{ + // [1]: {"fullName":"Test2"} + pnn: [0x43, 0x06, 0x85, 0xD4, 0xF2, 0x9C, 0x2E, 0x03], + expectedResult: {"fullName": "Test2"} + },{ + // [2]: undefined + pnn: [0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF], + },{ + // [3]: {"fullName": "Test4"} + pnn: [0x43, 0x06, 0x85, 0xD4, 0xF2, 0x9C, 0x4E, 0x03], + expectedResult: {"fullName": "Test4"} + },{ + // [4]: undefined + pnn: [0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF], + }]; + + function do_test_pnn() { + ril.iccIO = function fakeIccIO(options) { + let index = options.p1 - 1; + let pnn = test_data[index].pnn; + + // Write data size. + buf.writeInt32(pnn.length * 2); + + // Write data. + for (let i = 0; i < pnn.length; i++) { + pduHelper.writeHexOctet(pnn[i]); + } + + // Write string delimiter. + buf.writeStringDelimiter(pnn.length * 2); + + if (options.callback) { + options.callback(options); + } + }; + + io.loadLinearFixedEF = function fakeLoadLinearFixedEF(options) { + options.p1 = 1; + options.totalRecords = test_data.length; + + ril.iccIO(options); + }; + + record.readPNN(); + + equal(test_data.length, ril.iccInfoPrivate.PNN.length); + for (let i = 0; i < test_data.length; i++) { + if (test_data[i].expectedResult) { + equal(test_data[i].expectedResult.fullName, + ril.iccInfoPrivate.PNN[i].fullName); + equal(test_data[i].expectedResult.shortName, + ril.iccInfoPrivate.PNN[i].shortName); + } else { + equal(test_data[i].expectedResult, ril.iccInfoPrivate.PNN[i]); + } + } + } + + ril.appType = CARD_APPTYPE_SIM; + do_test_pnn(); + + run_next_test(); +}); diff --git a/dom/system/gonk/tests/test_ril_worker_ruim.js b/dom/system/gonk/tests/test_ril_worker_ruim.js new file mode 100644 index 000000000..0ddc10f29 --- /dev/null +++ b/dom/system/gonk/tests/test_ril_worker_ruim.js @@ -0,0 +1,328 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +subscriptLoader.loadSubScript("resource://gre/modules/ril_consts.js", this); + +function run_test() { + run_next_test(); +} + +/** + * Verify RUIM Service. + */ +add_test(function test_is_ruim_service_available() { + let worker = newWorker(); + let context = worker.ContextPool._contexts[0]; + context.RIL._isCdma = true; + context.RIL.appType = CARD_APPTYPE_RUIM; + + function test_table(cst, geckoService, enabled) { + context.RIL.iccInfoPrivate.cst = cst; + equal(context.ICCUtilsHelper.isICCServiceAvailable(geckoService), + enabled); + } + + test_table([0x0, 0x0, 0x0, 0x0, 0x03], "SPN", true); + test_table([0x0, 0x0, 0x0, 0x03, 0x0], "SPN", false); + test_table([0x0, 0x0C, 0x0, 0x0, 0x0], "ENHANCED_PHONEBOOK", true); + test_table([0x0, 0x0, 0x0, 0x0, 0x0], "ENHANCED_PHONEBOOK", false); + + run_next_test(); +}); + +/** + * Verify EF_PATH for RUIM file. + */ +add_test(function test_ruim_file_path_id() { + let worker = newWorker(); + let context = worker.ContextPool._contexts[0]; + let RIL = context.RIL; + let ICCFileHelper = context.ICCFileHelper; + + RIL.appType = CARD_APPTYPE_RUIM; + equal(ICCFileHelper.getEFPath(ICC_EF_CSIM_CST), + EF_PATH_MF_SIM + EF_PATH_DF_CDMA); + + run_next_test(); +}); + +add_test(function test_fetch_ruim_recodes() { + let worker = newWorker(); + let context = worker.ContextPool._contexts[0]; + let RIL = context.RIL; + let ruimHelper = context.RuimRecordHelper; + + function testFetchRuimRecordes(expectCalled) { + let ifCalled = []; + + ruimHelper.getIMSI_M = function() { + ifCalled.push("getIMSI_M"); + }; + + ruimHelper.readCST = function() { + ifCalled.push("readCST"); + }; + + ruimHelper.readCDMAHome = function() { + ifCalled.push("readCDMAHome"); + }; + + RIL.getCdmaSubscription = function() { + ifCalled.push("getCdmaSubscription"); + }; + + ruimHelper.fetchRuimRecords(); + + for (let i = 0; i < expectCalled.length; i++ ) { + if (ifCalled[i] != expectCalled[i]) { + do_print(expectCalled[i] + " is not called."); + ok(false); + } + } + } + + let expectCalled = ["getIMSI_M", "readCST", "readCDMAHome", + "getCdmaSubscription"]; + testFetchRuimRecordes(expectCalled); + + run_next_test(); +}); + +/** + * Verify RuimRecordHelper.decodeIMSIValue + */ +add_test(function test_decode_imsi_value() { + let worker = newUint8Worker(); + let context = worker.ContextPool._contexts[0]; + + function testDecodeImsiValue(encoded, length, expect) { + let decoded = context.RuimRecordHelper.decodeIMSIValue(encoded, length); + + equal(expect, decoded); + } + + testDecodeImsiValue( 99, 2, "00"); + testDecodeImsiValue( 90, 2, "01"); + testDecodeImsiValue( 19, 2, "20"); + testDecodeImsiValue( 23, 2, "34"); + testDecodeImsiValue(999, 3, "000"); + testDecodeImsiValue(990, 3, "001"); + testDecodeImsiValue(909, 3, "010"); + testDecodeImsiValue( 99, 3, "100"); + testDecodeImsiValue(901, 3, "012"); + testDecodeImsiValue( 19, 3, "120"); + testDecodeImsiValue( 91, 3, "102"); + testDecodeImsiValue(199, 3, "200"); + testDecodeImsiValue(123, 3, "234"); + testDecodeImsiValue(578, 3, "689"); + + run_next_test(); +}); + +/** + * Verify RuimRecordHelper.getIMSI_M + */ +add_test(function test_get_imsi_m() { + let worker = newUint8Worker(); + let context = worker.ContextPool._contexts[0]; + let helper = context.GsmPDUHelper; + let buf = context.Buf; + let io = context.ICCIOHelper; + + function testDecodeImsi(encodedImsi, expectedImsi) { + io.loadTransparentEF = function fakeLoadTransparentEF(options) { + // Write data size + buf.writeInt32(encodedImsi.length * 2); + + // Write imsi + for (let i = 0; i < encodedImsi.length; i++) { + helper.writeHexOctet(encodedImsi[i]); + } + + // Write string delimiter + buf.writeStringDelimiter(encodedImsi.length * 2); + + if (options.callback) { + options.callback(options); + } + }; + + context.RuimRecordHelper.getIMSI_M(); + let imsi = context.RIL.iccInfoPrivate.imsi; + + equal(expectedImsi, imsi) + } + + let imsi_1 = "466050081062861"; + testDecodeImsi([0x0, 0xe5, 0x03, 0xee, 0xca, 0x17, 0x5e, 0x80, 0x63, 0x01], imsi_1); + + let imsi_2 = "460038351175976"; + testDecodeImsi([0x0, 0xd4, 0x02, 0x61, 0x97, 0x01, 0x5c, 0x80, 0x67, 0x01], imsi_2); + + run_next_test(); +}); + +/** + * Verify RuimRecordHelper.readCDMAHome + */ +add_test(function test_read_cdmahome() { + let worker = newUint8Worker(); + let context = worker.ContextPool._contexts[0]; + let helper = context.GsmPDUHelper; + let buf = context.Buf; + let io = context.ICCIOHelper; + + io.loadLinearFixedEF = function fakeLoadLinearFixedEF(options) { + let cdmaHome = [0xc1, 0x34, 0xff, 0xff, 0x00]; + + // Write data size + buf.writeInt32(cdmaHome.length * 2); + + // Write cdma home file. + for (let i = 0; i < cdmaHome.length; i++) { + helper.writeHexOctet(cdmaHome[i]); + } + + // Write string delimiter + buf.writeStringDelimiter(cdmaHome.length * 2); + + // We just have 1 test record. + + options.totalRecords = 1; + if (options.callback) { + options.callback(options); + } + }; + + function testCdmaHome(expectedSystemIds, expectedNetworkIds) { + context.RuimRecordHelper.readCDMAHome(); + let cdmaHome = context.RIL.cdmaHome; + for (let i = 0; i < expectedSystemIds.length; i++) { + equal(cdmaHome.systemId[i], expectedSystemIds[i]); + equal(cdmaHome.networkId[i], expectedNetworkIds[i]); + } + equal(cdmaHome.systemId.length, expectedSystemIds.length); + equal(cdmaHome.networkId.length, expectedNetworkIds.length); + } + + testCdmaHome([13505], [65535]); + + run_next_test(); +}); + +/** + * Verify reading CDMA EF_SPN + */ +add_test(function test_read_cdmaspn() { + let worker = newUint8Worker(); + let context = worker.ContextPool._contexts[0]; + let helper = context.GsmPDUHelper; + let buf = context.Buf; + let io = context.ICCIOHelper; + + function testReadSpn(file, expectedSpn, expectedDisplayCondition) { + io.loadTransparentEF = function fakeLoadTransparentEF(options) { + // Write data size + buf.writeInt32(file.length * 2); + + // Write file. + for (let i = 0; i < file.length; i++) { + helper.writeHexOctet(file[i]); + } + + // Write string delimiter + buf.writeStringDelimiter(file.length * 2); + + if (options.callback) { + options.callback(options); + } + }; + + context.RuimRecordHelper.readSPN(); + equal(context.RIL.iccInfo.spn, expectedSpn); + equal(context.RIL.iccInfoPrivate.spnDisplayCondition, + expectedDisplayCondition); + } + + testReadSpn([0x01, 0x04, 0x06, 0x4e, 0x9e, 0x59, 0x2a, 0x96, + 0xfb, 0x4f, 0xe1, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff], + String.fromCharCode(0x4e9e) + + String.fromCharCode(0x592a) + + String.fromCharCode(0x96fb) + + String.fromCharCode(0x4fe1), + 0x1); + + // Test when there's no tailing 0xff in spn string. + testReadSpn([0x01, 0x04, 0x06, 0x4e, 0x9e, 0x59, 0x2a, 0x96, + 0xfb, 0x4f, 0xe1], + String.fromCharCode(0x4e9e) + + String.fromCharCode(0x592a) + + String.fromCharCode(0x96fb) + + String.fromCharCode(0x4fe1), + 0x1); + + run_next_test(); +}); + +/** + * Verify display condition for CDMA. + */ +add_test(function test_cdma_spn_display_condition() { + let worker = newWorker({ + postRILMessage: function(data) { + // Do nothing + }, + postMessage: function(message) { + // Do nothing + } + }); + let context = worker.ContextPool._contexts[0]; + let RIL = context.RIL; + let ICCUtilsHelper = context.ICCUtilsHelper; + + // Set cdma. + RIL._isCdma = true; + + // Test updateDisplayCondition runs before any of SIM file is ready. + equal(ICCUtilsHelper.updateDisplayCondition(), true); + equal(RIL.iccInfo.isDisplayNetworkNameRequired, true); + equal(RIL.iccInfo.isDisplaySpnRequired, false); + + // Test with value. + function testDisplayCondition(ruimDisplayCondition, + homeSystemIds, homeNetworkIds, + currentSystemId, currentNetworkId, + expectUpdateDisplayCondition, + expectIsDisplaySPNRequired) { + RIL.iccInfoPrivate.spnDisplayCondition = ruimDisplayCondition; + RIL.cdmaHome = { + systemId: homeSystemIds, + networkId: homeNetworkIds + }; + RIL.voiceRegistrationState.cell = { + cdmaSystemId: currentSystemId, + cdmaNetworkId: currentNetworkId + }; + + equal(ICCUtilsHelper.updateDisplayCondition(), expectUpdateDisplayCondition); + equal(RIL.iccInfo.isDisplayNetworkNameRequired, false); + equal(RIL.iccInfo.isDisplaySpnRequired, expectIsDisplaySPNRequired); + }; + + // SPN is not required when ruimDisplayCondition is false. + testDisplayCondition(0x0, [123], [345], 123, 345, true, false); + + // System id and network id are all match. + testDisplayCondition(0x1, [123], [345], 123, 345, true, true); + + // Network is 65535, we should only need to match system id. + testDisplayCondition(0x1, [123], [65535], 123, 345, false, true); + + // Not match. + testDisplayCondition(0x1, [123], [456], 123, 345, true, false); + + run_next_test(); +}); diff --git a/dom/system/gonk/tests/test_ril_worker_sms.js b/dom/system/gonk/tests/test_ril_worker_sms.js new file mode 100644 index 000000000..7c1b972a7 --- /dev/null +++ b/dom/system/gonk/tests/test_ril_worker_sms.js @@ -0,0 +1,273 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +subscriptLoader.loadSubScript("resource://gre/modules/ril_consts.js", this); + +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); + +XPCOMUtils.defineLazyGetter(this, "gSmsSegmentHelper", function() { + let ns = {}; + Cu.import("resource://gre/modules/SmsSegmentHelper.jsm", ns); + return ns.SmsSegmentHelper; +}); + +const ESCAPE = "\uffff"; +const RESCTL = "\ufffe"; + +function run_test() { + run_next_test(); +} + +/** + * Verify receiving SMS-DELIVERY messages + */ + +function hexToNibble(nibble) { + nibble &= 0x0f; + if (nibble < 10) { + nibble += 48; // ASCII '0' + } else { + nibble += 55; // ASCII 'A' + } + return nibble; +} + +function pduToParcelData(pdu) { + let dataLength = 4 + pdu.length * 4 + 4; + let data = new Uint8Array(dataLength); + let offset = 0; + + // String length + data[offset++] = pdu.length & 0xFF; + data[offset++] = (pdu.length >> 8) & 0xFF; + data[offset++] = (pdu.length >> 16) & 0xFF; + data[offset++] = (pdu.length >> 24) & 0xFF; + + // PDU data + for (let i = 0; i < pdu.length; i++) { + let hi = (pdu[i] >>> 4) & 0x0F; + let lo = pdu[i] & 0x0F; + + data[offset++] = hexToNibble(hi); + data[offset++] = 0; + data[offset++] = hexToNibble(lo); + data[offset++] = 0; + } + + // String delimitor + data[offset++] = 0; + data[offset++] = 0; + data[offset++] = 0; + data[offset++] = 0; + + return data; +} + +function compose7bitPdu(lst, sst, data, septets) { + if ((lst == 0) && (sst == 0)) { + return [0x00, // SMSC + PDU_MTI_SMS_DELIVER, // firstOctet + 1, 0x00, 0, // senderAddress + 0x00, // protocolIdentifier + PDU_DCS_MSG_CODING_7BITS_ALPHABET, // dataCodingScheme + 0, 0, 0, 0, 0, 0, 0, // y m d h m s tz + septets] // userDataLength + .concat(data); + } + + return [0x00, // SMSC + PDU_MTI_SMS_DELIVER | PDU_UDHI, // firstOctet + 1, 0x00, 0, // senderAddress + 0x00, // protocolIdentifier + PDU_DCS_MSG_CODING_7BITS_ALPHABET, // dataCodingScheme + 0, 0, 0, 0, 0, 0, 0, // y m d h m s tz + 8 + septets, // userDataLength + 6, // user data header length + PDU_IEI_NATIONAL_LANGUAGE_LOCKING_SHIFT, 1, lst, // PDU_IEI_NATIONAL_LANGUAGE_LOCKING_SHIFT + PDU_IEI_NATIONAL_LANGUAGE_SINGLE_SHIFT, 1, sst] // PDU_IEI_NATIONAL_LANGUAGE_SINGLE_SHIFT + .concat(data); +} + +function composeUcs2Pdu(rawBytes) { + return [0x00, // SMSC + PDU_MTI_SMS_DELIVER, // firstOctet + 1, 0x00, 0, // senderAddress + 0x00, // protocolIdentifier + PDU_DCS_MSG_CODING_16BITS_ALPHABET, // dataCodingScheme + 0, 0, 0, 0, 0, 0, 0, // y m d h m s tz + rawBytes.length] // userDataLength + .concat(rawBytes); +} + +function newSmsParcel(pdu) { + return newIncomingParcel(-1, + RESPONSE_TYPE_UNSOLICITED, + UNSOLICITED_RESPONSE_NEW_SMS, + pduToParcelData(pdu)); +} + +function removeSpecialChar(str, needle) { + for (let i = 0; i < needle.length; i++) { + let pos; + while ((pos = str.indexOf(needle[i])) >= 0) { + str = str.substring(0, pos) + str.substring(pos + 1); + } + } + return str; +} + +function newWriteHexOctetAsUint8Worker() { + let worker = newWorker({ + postRILMessage: function(data) { + // Do nothing + }, + postMessage: function(message) { + // Do nothing + } + }); + + let context = worker.ContextPool._contexts[0]; + context.GsmPDUHelper.writeHexOctet = function(value) { + context.Buf.writeUint8(value); + }; + + return worker; +} + +function add_test_receiving_sms(expected, pdu) { + add_test(function test_receiving_sms() { + let worker = newWorker({ + postRILMessage: function(data) { + // Do nothing + }, + postMessage: function(message) { + do_print("fullBody: " + message.fullBody); + equal(expected, message.fullBody) + } + }); + + do_print("expect: " + expected); + do_print("pdu: " + pdu); + worker.onRILMessage(0, newSmsParcel(pdu)); + + run_next_test(); + }); +} + +var test_receiving_7bit_alphabets__worker; +function test_receiving_7bit_alphabets(lst, sst) { + if (!test_receiving_7bit_alphabets__worker) { + test_receiving_7bit_alphabets__worker = newWriteHexOctetAsUint8Worker(); + } + let worker = test_receiving_7bit_alphabets__worker; + let context = worker.ContextPool._contexts[0]; + let helper = context.GsmPDUHelper; + let buf = context.Buf; + + function get7bitRawBytes(expected) { + buf.outgoingIndex = 0; + helper.writeStringAsSeptets(expected, 0, lst, sst); + + let subArray = buf.outgoingBytes.subarray(0, buf.outgoingIndex); + return Array.slice(subArray); + } + + let langTable = PDU_NL_LOCKING_SHIFT_TABLES[lst]; + let langShiftTable = PDU_NL_SINGLE_SHIFT_TABLES[sst]; + + let text = removeSpecialChar(langTable + langShiftTable, ESCAPE + RESCTL); + for (let i = 0; i < text.length;) { + let len = Math.min(70, text.length - i); + let expected = text.substring(i, i + len); + let septets = + gSmsSegmentHelper.countGsm7BitSeptets(expected, langTable, langShiftTable); + let rawBytes = get7bitRawBytes(expected); + let pdu = compose7bitPdu(lst, sst, rawBytes, septets); + add_test_receiving_sms(expected, pdu); + + i += len; + } +} + +function test_receiving_ucs2_alphabets(text) { + let worker = test_receiving_7bit_alphabets__worker; + let context = worker.ContextPool._contexts[0]; + let buf = context.Buf; + + function getUCS2RawBytes(expected) { + buf.outgoingIndex = 0; + context.GsmPDUHelper.writeUCS2String(expected); + + let subArray = buf.outgoingBytes.subarray(0, buf.outgoingIndex); + return Array.slice(subArray); + } + + for (let i = 0; i < text.length;) { + let len = Math.min(70, text.length - i); + let expected = text.substring(i, i + len); + let rawBytes = getUCS2RawBytes(expected); + let pdu = composeUcs2Pdu(rawBytes); + add_test_receiving_sms(expected, pdu); + + i += len; + } +} + +var ucs2str = ""; +for (let lst = 0; lst < PDU_NL_LOCKING_SHIFT_TABLES.length; lst++) { + ucs2str += PDU_NL_LOCKING_SHIFT_TABLES[lst]; + for (let sst = 0; sst < PDU_NL_SINGLE_SHIFT_TABLES.length; sst++) { + test_receiving_7bit_alphabets(lst, sst); + + if (lst == 0) { + ucs2str += PDU_NL_SINGLE_SHIFT_TABLES[sst]; + } + } +} +test_receiving_ucs2_alphabets(ucs2str); + +// Bug 820220: B2G SMS: wrong order and truncated content in multi-part messages +add_test(function test_sendSMS_UCS2_without_langIndex_langShiftIndex_defined() { + let worker = newWriteHexOctetAsUint8Worker(); + let context = worker.ContextPool._contexts[0]; + + context.Buf.sendParcel = function() { + // Each sendParcel() call represents one outgoing segment of a multipart + // SMS message. Here, we have the first segment send, so it's "Hello " + // only. + // + // 4(parcel size) + 4(request type) + 4(token) + // + 4(two messages) + 4(null SMSC) + 4(message string length) + // + 1(first octet) + 1(message reference) + // + 2(DA len, TOA) + 4(addr) + // + 1(pid) + 1(dcs) + // + 1(UDL) + 6(UDHL, type, len, ref, max, seq) + // + 12(2 * strlen("Hello ")) + // + 4(two delimitors) = 57 + // + // If we have additional 6(type, len, langIndex, type len, langShiftIndex) + // octets here, then bug 809553 is not fixed. + equal(this.outgoingIndex, 57); + + run_next_test(); + }; + + context.RIL.sendSMS({ + number: "1", + segmentMaxSeq: 2, + fullBody: "Hello World!", + dcs: PDU_DCS_MSG_CODING_16BITS_ALPHABET, + segmentRef16Bit: false, + userDataHeaderLength: 5, + requestStatusReport: true, + segments: [ + { + body: "Hello ", + encodedBodyLength: 12, + }, { + body: "World!", + encodedBodyLength: 12, + } + ], + }); +}); diff --git a/dom/system/gonk/tests/test_ril_worker_sms_cdma.js b/dom/system/gonk/tests/test_ril_worker_sms_cdma.js new file mode 100644 index 000000000..85d0b6e0c --- /dev/null +++ b/dom/system/gonk/tests/test_ril_worker_sms_cdma.js @@ -0,0 +1,298 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +subscriptLoader.loadSubScript("resource://gre/modules/ril_consts.js", this); + +function run_test() { + run_next_test(); +} + +/* + * Helper function to covert a HEX string to a byte array. + * + * @param hexString + * A hexadecimal string of which the length is even. + */ +function hexStringToBytes(hexString) { + let bytes = []; + + let length = hexString.length; + + for (let i = 0; i < length; i += 2) { + bytes.push(Number.parseInt(hexString.substr(i, 2), 16)); + } + + return bytes; +} + +/* + * Helper function to covert a byte array to a HEX string. + * + * @param bytes + * Could be a regular byte array or Uint8Array. + */ +function bytesToHexString(bytes) { + let hexString = ""; + let hex; + + for (let i = 0; i < bytes.length; i++) { + hex = bytes[i].toString(16).toUpperCase(); + if (hex.length === 1) { + hexString += "0"; + } + hexString += hex; + } + + return hexString; +} + +/* + * Helper function to ecode Opaque UserData + * + * @param msg_type + * PDU_CDMA_MSG_TYPE_SUBMIT or PDU_CDMA_MSG_TYPE_DELIVER + * @param data + * The byte array of opaque data to be encoded. + */ +function encodeOpaqueUserData(bitBufferHelper, options) { + let bearerDataBuffer = []; + bitBufferHelper.startWrite(bearerDataBuffer); + + // Msg-Id + bitBufferHelper.writeBits(PDU_CDMA_MSG_USERDATA_MSG_ID, 8); + bitBufferHelper.writeBits(3, 8); + bitBufferHelper.writeBits(options.msg_type, 4); // MSG_TYPE + bitBufferHelper.writeBits(1, 16); // MSG_ID + bitBufferHelper.flushWithPadding(); // HEADER_IND (1) + RESERVED (3) + + // User Data + bitBufferHelper.writeBits(PDU_CDMA_MSG_USERDATA_BODY, 8); + let dataLength = options.data.length; + bitBufferHelper.writeBits(2 + dataLength, 8); // 2 bytes for MSG_ENCODING, NUM_FIELDS + bitBufferHelper.writeBits(PDU_CDMA_MSG_CODING_OCTET, 5); //MSG_ENCODING + // MSG_TYPE is omitted if MSG_ENCODING is CODING_OCTET + bitBufferHelper.writeBits(dataLength, 8); // NUM_FIELDS + for (let i = 0; i < dataLength; i++) { // CHARi + bitBufferHelper.writeBits(options.data[i], 8); + } + bitBufferHelper.flushWithPadding(); // RESERVED (3 filling bits) + + return bearerDataBuffer; +} + +function newSmsParcel(cdmaPduHelper, pdu) { + return newIncomingParcel(-1, + RESPONSE_TYPE_UNSOLICITED, + UNSOLICITED_RESPONSE_CDMA_NEW_SMS, + pduToParcelData(cdmaPduHelper, pdu)); +} + +/* + * Helper function to encode PDU into Parcel. + * See ril_cdma_sms.h for the structure definition of RIL_CDMA_SMS_Message + * + * @param teleservice + * The Teleservice-Id of this PDU. + * See PDU_CDMA_MSG_TELESERIVCIE_ID_XXX in ril_const.js. + * @param address + * The Orginating or Destinating address. + * @param bearerData + * The byte array of the encoded bearer data. + */ +function pduToParcelData(cdmaPduHelper, pdu) { + + let addrInfo = cdmaPduHelper.encodeAddr(pdu.address); + // Teleservice, isServicePresent, ServiceCategory, + // addrInfo {digitMode, numberMode, numberType, numberPlan, address.length, address} + // Sub Address + // bearerData length, bearerData. + let dataLength = 4 + 4 + 4 + + (5 + addrInfo.address.length) * 4 + + 3 * 4 + + 4 + pdu.bearerData.length * 4; + + let data = new Uint8Array(dataLength); + let offset = 0; + + function writeInt(value) { + data[offset++] = value & 0xFF; + data[offset++] = (value >> 8) & 0xFF; + data[offset++] = (value >> 16) & 0xFF; + data[offset++] = (value >> 24) & 0xFF; + } + + function writeByte(value) { + data[offset++] = value & 0xFF; + data[offset++] = 0; + data[offset++] = 0; + data[offset++] = 0; + } + + // Teleservice + writeInt(pdu.teleservice); + + // isServicePresent + writeByte(0); + + // ServiceCategory + writeInt(PDU_CDMA_MSG_CATEGORY_UNSPEC); + + // AddrInfo + writeByte(addrInfo.digitMode); + writeByte(addrInfo.numberMode); + writeByte(addrInfo.numberType); + writeByte(addrInfo.numberPlan); + let addressLength = addrInfo.address.length; + writeByte(addressLength); + for (let i = 0; i < addressLength; i++) { + writeByte(addrInfo.address[i]); + } + + // Subaddress + writeByte(0); + writeByte(0); + writeByte(0); + + // Bearer Data Length + dataLength = pdu.bearerData.length; + writeByte(dataLength); + + // Bearer Data + for (let i = 0; i < dataLength; i++) { + writeByte(pdu.bearerData[i]); + } + + return data; +} + +/** + * Verify CDMA SMS Delivery ACK Message. + */ +add_test(function test_processCdmaSmsStatusReport() { + let workerHelper = newInterceptWorker(); + let worker = workerHelper.worker; + let context = worker.ContextPool._contexts[0]; + + function test_StatusReport(errorClass, msgStatus) { + let msgId = 0; + let sentSmsMap = context.RIL._pendingSentSmsMap; + + sentSmsMap[msgId] = {}; + + let message = { + SMSC: "", + mti: 0, + udhi: 0, + sender: "0987654321", + recipient: null, + pid: PDU_PID_DEFAULT, + epid: PDU_PID_DEFAULT, + dcs: 0, + mwi: null, + replace: false, + header: null, + body: "Status: Sent, Dest: 0987654321", + data: null, + timestamp: new Date().valueOf(), + language: null, + status: null, + scts: null, + dt: null, + encoding: PDU_CDMA_MSG_CODING_7BITS_ASCII, + messageClass: GECKO_SMS_MESSAGE_CLASSES[PDU_DCS_MSG_CLASS_NORMAL], + messageType: PDU_CDMA_MSG_TYPE_P2P, + serviceCategory: 0, + subMsgType: PDU_CDMA_MSG_TYPE_DELIVER_ACK, + msgId: msgId, + errorClass: errorClass, + msgStatus: msgStatus + }; + + context.RIL._processCdmaSmsStatusReport(message); + + let postedMessage = workerHelper.postedMessage; + + // Check if pending token is removed. + ok((errorClass === 2) ? !!sentSmsMap[msgId] : !sentSmsMap[msgId]); + + // Check the response message accordingly. + if (errorClass === -1) { + // Check if the report is treated as normal incoming SMS + equal("sms-received", postedMessage.rilMessageType); + } else if (errorClass === 2) { + // Do nothing. + } else { + // Check Delivery Status + if (errorClass === 0) { + equal(postedMessage.deliveryStatus, GECKO_SMS_DELIVERY_STATUS_SUCCESS); + } else { + equal(postedMessage.deliveryStatus, GECKO_SMS_DELIVERY_STATUS_ERROR); + } + } + } + + test_StatusReport(-1, -1); // Message Status Sub-parameter is absent. + test_StatusReport(0, 0); // 00|000000: no error|Message accepted + test_StatusReport(2, 4); // 10|000100: temporary condition|Network congestion + test_StatusReport(3, 5); // 11|000101: permanent condition|Network error + + run_next_test(); +}); + +/** + * Verify WAP Push over CDMA SMS Message. + */ +add_test(function test_processCdmaSmsWapPush() { + let workerHelper = newInterceptWorker(), + worker = workerHelper.worker, + context = worker.ContextPool._contexts[0], + bitBufferHelper = context.BitBufferHelper, + cdmaPduHelper = context.CdmaPDUHelper; + + function test_CdmaSmsWapPdu(wdpData, reversed) { + let orig_address = "0987654321", + hexString, + fullDataHexString = ""; + + for (let i = 0; i < wdpData.length; i++) { + let dataIndex = (reversed) ? (wdpData.length - i - 1) : i; + hexString = "00"; // MSG_TYPE + hexString += bytesToHexString([wdpData.length]); // TOTAL_SEG + hexString += bytesToHexString([dataIndex]); // SEG_NUM (zero-based) + if ((dataIndex === 0)) { + hexString += "23F00B84"; // SOURCE_PORT, DEST_PORT for 1st segment + } + hexString += wdpData[dataIndex]; // WDP DATA + + do_print("hexString: " + hexString); + + fullDataHexString += wdpData[i]; + + let pdu = { + teleservice: PDU_CDMA_MSG_TELESERIVCIE_ID_WAP, + address: orig_address, + bearerData: encodeOpaqueUserData(bitBufferHelper, + { msg_type: PDU_CDMA_MSG_TYPE_DELIVER, + data: hexStringToBytes(hexString) }) + }; + + worker.onRILMessage(0, newSmsParcel(cdmaPduHelper, pdu)); + } + + let postedMessage = workerHelper.postedMessage; + + do_print("fullDataHexString: " + fullDataHexString); + + equal("sms-received", postedMessage.rilMessageType); + equal(PDU_CDMA_MSG_TELESERIVCIE_ID_WAP, postedMessage.teleservice); + equal(orig_address, postedMessage.sender); + equal(0x23F0, postedMessage.header.originatorPort); + equal(0x0B84, postedMessage.header.destinationPort); + equal(fullDataHexString, bytesToHexString(postedMessage.data)); + } + + // Verify Single WAP PDU + test_CdmaSmsWapPdu(["000102030405060708090A0B0C0D0E0F"]); + + run_next_test(); +}); diff --git a/dom/system/gonk/tests/test_ril_worker_sms_cdmapduhelper.js b/dom/system/gonk/tests/test_ril_worker_sms_cdmapduhelper.js new file mode 100644 index 000000000..276728f2f --- /dev/null +++ b/dom/system/gonk/tests/test_ril_worker_sms_cdmapduhelper.js @@ -0,0 +1,210 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +subscriptLoader.loadSubScript("resource://gre/modules/ril_consts.js", this); + +function run_test() { + run_next_test(); +} + +/** + * Verify CdmaPDUHelper#encodeUserDataReplyOption. + */ +add_test(function test_CdmaPDUHelper_encodeUserDataReplyOption() { + let worker = newWorker({ + postRILMessage: function(data) { + // Do nothing + }, + postMessage: function(message) { + // Do nothing + } + }); + let context = worker.ContextPool._contexts[0]; + + let testDataBuffer = []; + context.BitBufferHelper.startWrite(testDataBuffer); + + let helper = context.CdmaPDUHelper; + helper.encodeUserDataReplyOption({requestStatusReport: true}); + + let expectedDataBuffer = [PDU_CDMA_MSG_USERDATA_REPLY_OPTION, 0x01, 0x40]; + + equal(testDataBuffer.length, expectedDataBuffer.length); + + for (let i = 0; i < expectedDataBuffer.length; i++) { + equal(testDataBuffer[i], expectedDataBuffer[i]); + } + + run_next_test(); +}); + +/** + * Verify CdmaPDUHelper#cdma_decodeUserDataMsgStatus. + */ +add_test(function test_CdmaPDUHelper_decodeUserDataMsgStatus() { + let worker = newWorker({ + postRILMessage: function(data) { + // Do nothing + }, + postMessage: function(message) { + // Do nothing + } + }); + let context = worker.ContextPool._contexts[0]; + + let helper = context.CdmaPDUHelper; + function test_MsgStatus(octet) { + let testDataBuffer = [octet]; + context.BitBufferHelper.startRead(testDataBuffer); + let result = helper.decodeUserDataMsgStatus(); + + equal(result.errorClass, octet >>> 6); + equal(result.msgStatus, octet & 0x3F); + } + + // 00|000000: no error|Message accepted + test_MsgStatus(0x00); + + // 10|000100: temporary condition|Network congestion + test_MsgStatus(0x84); + + // 11|000101: permanent condition|Network error + test_MsgStatus(0xC5); + + run_next_test(); +}); + +/** + * Verify CdmaPDUHelper#decodeCdmaPDUMsg. + * - encoding by shift-jis + */ +add_test(function test_CdmaPDUHelper_decodeCdmaPDUMsg_Shift_jis() { + let worker = newWorker({ + postRILMessage: function(data) { + // Do nothing + }, + postMessage: function(message) { + // Do nothing + } + }); + let context = worker.ContextPool._contexts[0]; + + let helper = context.CdmaPDUHelper; + function test_decodePDUMsg(testDataBuffer, expected, encoding, msgType, msgBodySize) { + context.BitBufferHelper.startRead(testDataBuffer); + let result = helper.decodeCdmaPDUMsg(encoding, msgType, msgBodySize); + equal(result, expected); + } + + // Shift-JIS has 1 byte and 2 byte code for one character and has some types of characters: + // Hiragana, Kanji, Katakana(fullwidth, halfwidth)... + // This test is a combination of 1 byte and 2 byte code and types of characters. + + // test case 1 + let testDataBuffer1 = [0x82, 0x58, 0x33, 0x41, 0x61, 0x33, 0x82, 0x60, + 0x82, 0x81, 0x33, 0xB1, 0xAF, 0x33, 0x83, 0x41, + 0x83, 0x96, 0x33, 0x82, 0xA0, 0x33, 0x93, 0xFA, + 0x33, 0x3A, 0x3C, 0x33, 0x81, 0x80, 0x81, 0x8E, + 0x33, 0x31, 0x82, 0x51, 0x41, 0x61, 0x82, 0x51, + 0x82, 0x60, 0x82, 0x81, 0x82, 0x51, 0xB1, 0xAF, + 0x82, 0x51, 0x83, 0x41, 0x83, 0x96, 0x82, 0x51, + 0x82, 0xA0, 0x82, 0x51, 0x93, 0xFA, 0x82, 0x51, + 0x3A, 0x3C, 0x82, 0x51, 0x81, 0x80, 0x81, 0x8E, + 0x82, 0x51]; + + test_decodePDUMsg( + testDataBuffer1, + "\uFF19\u0033\u0041\u0061\u0033\uFF21\uFF41\u0033\uFF71\uFF6F\u0033\u30A2\u30F6\u0033\u3042\u0033\u65E5\u0033\u003A\u003C\u0033\u00F7\u2103\u0033\u0031\uFF12\u0041\u0061\uFF12\uFF21\uFF41\uFF12\uFF71\uFF6F\uFF12\u30A2\u30F6\uFF12\u3042\uFF12\u65E5\uFF12\u003A\u003C\uFF12\u00F7\u2103\uFF12", + PDU_CDMA_MSG_CODING_SHIFT_JIS, + undefined, + testDataBuffer1.length + ); + + // test case 2 + let testDataBuffer2 = [0x31, 0x51, 0x63, 0x82, 0x58, 0x51, 0x63, 0x82, + 0x60, 0x82, 0x81, 0x51, 0x63, 0xB1, 0xAF, 0x51, + 0x63, 0x83, 0x41, 0x83, 0x96, 0x51, 0x63, 0x82, + 0xA0, 0x51, 0x63, 0x93, 0xFA, 0x51, 0x63, 0x3A, + 0x3C, 0x51, 0x63, 0x81, 0x80, 0x81, 0x8E, 0x51, + 0x63, 0x31, 0x82, 0x70, 0x82, 0x85, 0x82, 0x58, + 0x82, 0x70, 0x82, 0x85, 0x41, 0x61, 0x82, 0x70, + 0x82, 0x85, 0xB1, 0xAF, 0x82, 0x70, 0x82, 0x85, + 0x83, 0x41, 0x83, 0x96, 0x82, 0x70, 0x82, 0x85, + 0x82, 0xA0, 0x82, 0x70, 0x82, 0x85, 0x93, 0xFA, + 0x82, 0x70, 0x82, 0x85, 0x3A, 0x3C, 0x82, 0x70, + 0x82, 0x85, 0x81, 0x80, 0x81, 0x8E, 0x82, 0x70, + 0x82, 0x85]; + + test_decodePDUMsg( + testDataBuffer2, + "\u0031\u0051\u0063\uFF19\u0051\u0063\uFF21\uFF41\u0051\u0063\uFF71\uFF6F\u0051\u0063\u30A2\u30F6\u0051\u0063\u3042\u0051\u0063\u65E5\u0051\u0063\u003A\u003C\u0051\u0063\u00F7\u2103\u0051\u0063\u0031\uFF31\uFF45\uFF19\uFF31\uFF45\u0041\u0061\uFF31\uFF45\uFF71\uFF6F\uFF31\uFF45\u30A2\u30F6\uFF31\uFF45\u3042\uFF31\uFF45\u65E5\uFF31\uFF45\u003A\u003C\uFF31\uFF45\u00F7\u2103\uFF31\uFF45", + PDU_CDMA_MSG_CODING_SHIFT_JIS, + undefined, + testDataBuffer2.length + ); + + // test case 3 + let testDataBuffer3 = [0x31, 0xC2, 0xDF, 0x82, 0x58, 0xC2, 0xDF, 0x41, + 0x61, 0xC2, 0xDF, 0x82, 0x60, 0x82, 0x81, 0xC2, + 0xDF, 0x83, 0x41, 0x83, 0x96, 0xC2, 0xDF, 0x82, + 0xA0, 0xC2, 0xDF, 0x93, 0xFA, 0xC2, 0xDF, 0x3A, + 0x3C, 0xC2, 0xDF, 0x81, 0x80, 0x81, 0x8E, 0xC2, + 0xDF, 0x31, 0x83, 0x51, 0x83, 0x87, 0x82, 0x58, + 0x83, 0x51, 0x83, 0x87, 0x41, 0x61, 0x83, 0x51, + 0x83, 0x87, 0x82, 0x60, 0x82, 0x81, 0x83, 0x51, + 0x83, 0x87, 0xB1, 0xAF, 0x83, 0x51, 0x83, 0x87, + 0x82, 0xA0, 0x83, 0x51, 0x83, 0x87, 0x93, 0xFA, + 0x83, 0x51, 0x83, 0x87, 0x3A, 0x3C, 0x83, 0x51, + 0x83, 0x87, 0x81, 0x80, 0x81, 0x8E, 0x83, 0x51, + 0x83, 0x87]; + + test_decodePDUMsg( + testDataBuffer3, + "\u0031\uFF82\uFF9F\uFF19\uFF82\uFF9F\u0041\u0061\uFF82\uFF9F\uFF21\uFF41\uFF82\uFF9F\u30A2\u30F6\uFF82\uFF9F\u3042\uFF82\uFF9F\u65E5\uFF82\uFF9F\u003A\u003C\uFF82\uFF9F\u00F7\u2103\uFF82\uFF9F\u0031\u30B2\u30E7\uFF19\u30B2\u30E7\u0041\u0061\u30B2\u30E7\uFF21\uFF41\u30B2\u30E7\uFF71\uFF6F\u30B2\u30E7\u3042\u30B2\u30E7\u65E5\u30B2\u30E7\u003A\u003C\u30B2\u30E7\u00F7\u2103\u30B2\u30E7", + PDU_CDMA_MSG_CODING_SHIFT_JIS, + undefined, + testDataBuffer3.length + ); + + // test case 4 + let testDataBuffer4 = [0x31, 0x82, 0xB0, 0x82, 0x58, 0x82, 0xB0, 0x41, + 0x61, 0x82, 0xB0, 0x82, 0x60, 0x82, 0x81, 0x82, + 0xB0, 0xB1, 0xAF, 0x82, 0xB0, 0x83, 0x41, 0x83, + 0x96, 0x82, 0xB0, 0x93, 0xFA, 0x82, 0xB0, 0x3A, + 0x3C, 0x82, 0xB0, 0x81, 0x80, 0x81, 0x8E, 0x82, + 0xB0, 0x31, 0x88, 0xA4, 0x82, 0x58, 0x88, 0xA4, + 0x41, 0x61, 0x88, 0xA4, 0x82, 0x60, 0x82, 0x81, + 0x88, 0xA4, 0xB1, 0xAF, 0x88, 0xA4, 0x83, 0x41, + 0x83, 0x96, 0x88, 0xA4, 0x82, 0xA0, 0x88, 0xA4, + 0x3A, 0x3C, 0x88, 0xA4, 0x81, 0x80, 0x81, 0x8E, + 0x88, 0xA4]; + + test_decodePDUMsg( + testDataBuffer4, + "\u0031\u3052\uFF19\u3052\u0041\u0061\u3052\uFF21\uFF41\u3052\uFF71\uFF6F\u3052\u30A2\u30F6\u3052\u65E5\u3052\u003A\u003C\u3052\u00F7\u2103\u3052\u0031\u611B\uFF19\u611B\u0041\u0061\u611B\uFF21\uFF41\u611B\uFF71\uFF6F\u611B\u30A2\u30F6\u611B\u3042\u611B\u003A\u003C\u611B\u00F7\u2103\u611B", + PDU_CDMA_MSG_CODING_SHIFT_JIS, + undefined, + testDataBuffer4.length + ); + + // test case 5 + let testDataBuffer5 = [0x31, 0x40, 0x82, 0x58, 0x40, 0x41, 0x61, 0x40, + 0x82, 0x60, 0x82, 0x81, 0x40, 0xB1, 0xAF, 0x40, + 0x83, 0x41, 0x83, 0x96, 0x40, 0x82, 0xA0, 0x40, + 0x93, 0xFA, 0x40, 0x81, 0x80, 0x81, 0x8E, 0x40, + 0x31, 0x81, 0x9B, 0x82, 0x58, 0x81, 0x9B, 0x41, + 0x61, 0x81, 0x9B, 0x82, 0x60, 0x82, 0x81, 0x81, + 0x9B, 0xB1, 0xAF, 0x81, 0x9B, 0x83, 0x41, 0x83, + 0x96, 0x81, 0x9B, 0x82, 0xA0, 0x81, 0x9B, 0x93, + 0xFA, 0x81, 0x9B, 0x3A, 0x3C, 0x81, 0x9B]; + + test_decodePDUMsg( + testDataBuffer5, + "\u0031\u0040\uFF19\u0040\u0041\u0061\u0040\uFF21\uFF41\u0040\uFF71\uFF6F\u0040\u30A2\u30F6\u0040\u3042\u0040\u65E5\u0040\u00F7\u2103\u0040\u0031\u25CB\uFF19\u25CB\u0041\u0061\u25CB\uFF21\uFF41\u25CB\uFF71\uFF6F\u25CB\u30A2\u30F6\u25CB\u3042\u25CB\u65E5\u25CB\u003A\u003C\u25CB", + PDU_CDMA_MSG_CODING_SHIFT_JIS, + undefined, + testDataBuffer5.length + ); + + run_next_test(); +}); diff --git a/dom/system/gonk/tests/test_ril_worker_sms_gsmpduhelper.js b/dom/system/gonk/tests/test_ril_worker_sms_gsmpduhelper.js new file mode 100644 index 000000000..f52c64cf8 --- /dev/null +++ b/dom/system/gonk/tests/test_ril_worker_sms_gsmpduhelper.js @@ -0,0 +1,282 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +subscriptLoader.loadSubScript("resource://gre/modules/ril_consts.js", this); + +function run_test() { + run_next_test(); +} + +/** + * Verify GsmPDUHelper#readDataCodingScheme. + */ +add_test(function test_GsmPDUHelper_readDataCodingScheme() { + let worker = newWorker({ + postRILMessage: function(data) { + // Do nothing + }, + postMessage: function(message) { + // Do nothing + } + }); + + let context = worker.ContextPool._contexts[0]; + let helper = context.GsmPDUHelper; + function test_dcs(dcs, encoding, messageClass, mwi) { + helper.readHexOctet = function() { + return dcs; + } + + let msg = {}; + helper.readDataCodingScheme(msg); + + equal(msg.dcs, dcs); + equal(msg.encoding, encoding); + equal(msg.messageClass, messageClass); + equal(msg.mwi == null, mwi == null); + if (mwi != null) { + equal(msg.mwi.active, mwi.active); + equal(msg.mwi.discard, mwi.discard); + equal(msg.mwi.msgCount, mwi.msgCount); + } + } + + // Group 00xx + // Bit 3 and 2 indicate the character set being used. + test_dcs(0x00, PDU_DCS_MSG_CODING_7BITS_ALPHABET, + GECKO_SMS_MESSAGE_CLASSES[PDU_DCS_MSG_CLASS_NORMAL]); + test_dcs(0x04, PDU_DCS_MSG_CODING_8BITS_ALPHABET, + GECKO_SMS_MESSAGE_CLASSES[PDU_DCS_MSG_CLASS_NORMAL]); + test_dcs(0x08, PDU_DCS_MSG_CODING_16BITS_ALPHABET, + GECKO_SMS_MESSAGE_CLASSES[PDU_DCS_MSG_CLASS_NORMAL]); + test_dcs(0x0C, PDU_DCS_MSG_CODING_7BITS_ALPHABET, + GECKO_SMS_MESSAGE_CLASSES[PDU_DCS_MSG_CLASS_NORMAL]); + // Bit 4, if set to 0, indicates that bits 1 to 0 are reserved and have no + // message class meaning. + test_dcs(0x01, PDU_DCS_MSG_CODING_7BITS_ALPHABET, + GECKO_SMS_MESSAGE_CLASSES[PDU_DCS_MSG_CLASS_NORMAL]); + test_dcs(0x02, PDU_DCS_MSG_CODING_7BITS_ALPHABET, + GECKO_SMS_MESSAGE_CLASSES[PDU_DCS_MSG_CLASS_NORMAL]); + test_dcs(0x03, PDU_DCS_MSG_CODING_7BITS_ALPHABET, + GECKO_SMS_MESSAGE_CLASSES[PDU_DCS_MSG_CLASS_NORMAL]); + // Bit 4, if set to 1, indicates that bits 1 to 0 have a message class meaning. + test_dcs(0x10, PDU_DCS_MSG_CODING_7BITS_ALPHABET, + GECKO_SMS_MESSAGE_CLASSES[PDU_DCS_MSG_CLASS_0]); + test_dcs(0x11, PDU_DCS_MSG_CODING_7BITS_ALPHABET, + GECKO_SMS_MESSAGE_CLASSES[PDU_DCS_MSG_CLASS_1]); + test_dcs(0x12, PDU_DCS_MSG_CODING_7BITS_ALPHABET, + GECKO_SMS_MESSAGE_CLASSES[PDU_DCS_MSG_CLASS_2]); + test_dcs(0x13, PDU_DCS_MSG_CODING_7BITS_ALPHABET, + GECKO_SMS_MESSAGE_CLASSES[PDU_DCS_MSG_CLASS_3]); + + // Group 01xx + test_dcs(0x50, PDU_DCS_MSG_CODING_7BITS_ALPHABET, + GECKO_SMS_MESSAGE_CLASSES[PDU_DCS_MSG_CLASS_0]); + + // Group 1000..1011: reserved + test_dcs(0x8F, PDU_DCS_MSG_CODING_7BITS_ALPHABET, + GECKO_SMS_MESSAGE_CLASSES[PDU_DCS_MSG_CLASS_NORMAL]); + test_dcs(0x9F, PDU_DCS_MSG_CODING_7BITS_ALPHABET, + GECKO_SMS_MESSAGE_CLASSES[PDU_DCS_MSG_CLASS_NORMAL]); + test_dcs(0xAF, PDU_DCS_MSG_CODING_7BITS_ALPHABET, + GECKO_SMS_MESSAGE_CLASSES[PDU_DCS_MSG_CLASS_NORMAL]); + test_dcs(0xBF, PDU_DCS_MSG_CODING_7BITS_ALPHABET, + GECKO_SMS_MESSAGE_CLASSES[PDU_DCS_MSG_CLASS_NORMAL]); + + // Group 1100: Message Waiting Indication Group: Discard Message + // Bit 3 indicates Indication Sense: + test_dcs(0xC0, PDU_DCS_MSG_CODING_7BITS_ALPHABET, + GECKO_SMS_MESSAGE_CLASSES[PDU_DCS_MSG_CLASS_NORMAL], + {active: false, discard: true, msgCount: 0}); + test_dcs(0xC8, PDU_DCS_MSG_CODING_7BITS_ALPHABET, + GECKO_SMS_MESSAGE_CLASSES[PDU_DCS_MSG_CLASS_NORMAL], + {active: true, discard: true, msgCount: -1}); + // Bit 2 is reserved, and set to 0: + test_dcs(0xCC, PDU_DCS_MSG_CODING_7BITS_ALPHABET, + GECKO_SMS_MESSAGE_CLASSES[PDU_DCS_MSG_CLASS_NORMAL], + {active: true, discard: true, msgCount: -1}); + + // Group 1101: Message Waiting Indication Group: Store Message + // Bit 3 indicates Indication Sense: + test_dcs(0xD0, PDU_DCS_MSG_CODING_7BITS_ALPHABET, + GECKO_SMS_MESSAGE_CLASSES[PDU_DCS_MSG_CLASS_NORMAL], + {active: false, discard: false, msgCount: 0}); + test_dcs(0xD8, PDU_DCS_MSG_CODING_7BITS_ALPHABET, + GECKO_SMS_MESSAGE_CLASSES[PDU_DCS_MSG_CLASS_NORMAL], + {active: true, discard: false, msgCount: -1}); + // Bit 2 is reserved, and set to 0: + test_dcs(0xDC, PDU_DCS_MSG_CODING_7BITS_ALPHABET, + GECKO_SMS_MESSAGE_CLASSES[PDU_DCS_MSG_CLASS_NORMAL], + {active: true, discard: false, msgCount: -1}); + + // Group 1110: Message Waiting Indication Group: Store Message, UCS2 + // Bit 3 indicates Indication Sense: + test_dcs(0xE0, PDU_DCS_MSG_CODING_16BITS_ALPHABET, + GECKO_SMS_MESSAGE_CLASSES[PDU_DCS_MSG_CLASS_NORMAL], + {active: false, discard: false, msgCount: 0}); + test_dcs(0xE8, PDU_DCS_MSG_CODING_16BITS_ALPHABET, + GECKO_SMS_MESSAGE_CLASSES[PDU_DCS_MSG_CLASS_NORMAL], + {active: true, discard: false, msgCount: -1}); + // Bit 2 is reserved, and set to 0: + test_dcs(0xEC, PDU_DCS_MSG_CODING_16BITS_ALPHABET, + GECKO_SMS_MESSAGE_CLASSES[PDU_DCS_MSG_CLASS_NORMAL], + {active: true, discard: false, msgCount: -1}); + + // Group 1111 + test_dcs(0xF0, PDU_DCS_MSG_CODING_7BITS_ALPHABET, + GECKO_SMS_MESSAGE_CLASSES[PDU_DCS_MSG_CLASS_0]); + test_dcs(0xF1, PDU_DCS_MSG_CODING_7BITS_ALPHABET, + GECKO_SMS_MESSAGE_CLASSES[PDU_DCS_MSG_CLASS_1]); + test_dcs(0xF2, PDU_DCS_MSG_CODING_7BITS_ALPHABET, + GECKO_SMS_MESSAGE_CLASSES[PDU_DCS_MSG_CLASS_2]); + test_dcs(0xF3, PDU_DCS_MSG_CODING_7BITS_ALPHABET, + GECKO_SMS_MESSAGE_CLASSES[PDU_DCS_MSG_CLASS_3]); + test_dcs(0xF4, PDU_DCS_MSG_CODING_8BITS_ALPHABET, + GECKO_SMS_MESSAGE_CLASSES[PDU_DCS_MSG_CLASS_0]); + test_dcs(0xF5, PDU_DCS_MSG_CODING_8BITS_ALPHABET, + GECKO_SMS_MESSAGE_CLASSES[PDU_DCS_MSG_CLASS_1]); + test_dcs(0xF6, PDU_DCS_MSG_CODING_8BITS_ALPHABET, + GECKO_SMS_MESSAGE_CLASSES[PDU_DCS_MSG_CLASS_2]); + test_dcs(0xF7, PDU_DCS_MSG_CODING_8BITS_ALPHABET, + GECKO_SMS_MESSAGE_CLASSES[PDU_DCS_MSG_CLASS_3]); + // Bit 3 is reserved and should be set to 0, but if it doesn't we should + // ignore it. + test_dcs(0xF8, PDU_DCS_MSG_CODING_7BITS_ALPHABET, + GECKO_SMS_MESSAGE_CLASSES[PDU_DCS_MSG_CLASS_0]); + + run_next_test(); +}); + +/** + * Verify GsmPDUHelper#writeStringAsSeptets() padding bits handling. + */ +add_test(function test_GsmPDUHelper_writeStringAsSeptets() { + let worker = newWorker({ + postRILMessage: function(data) { + // Do nothing + }, + postMessage: function(message) { + // Do nothing + } + }); + + let context = worker.ContextPool._contexts[0]; + let helper = context.GsmPDUHelper; + helper.resetOctetWritten = function() { + helper.octetsWritten = 0; + }; + helper.writeHexOctet = function() { + helper.octetsWritten++; + }; + + let base = "AAAAAAAA"; // Base string of 8 characters long + for (let len = 0; len < 8; len++) { + let str = base.substring(0, len); + + for (let paddingBits = 0; paddingBits < 8; paddingBits++) { + do_print("Verifying GsmPDUHelper.writeStringAsSeptets(" + + str + ", " + paddingBits + ", <default>, <default>)"); + helper.resetOctetWritten(); + helper.writeStringAsSeptets(str, paddingBits, PDU_NL_IDENTIFIER_DEFAULT, + PDU_NL_IDENTIFIER_DEFAULT); + equal(Math.ceil(((len * 7) + paddingBits) / 8), + helper.octetsWritten); + } + } + + run_next_test(); +}); + +/** + * Verify that encoding with Spanish locking shift table generates the same + * septets as with GSM default alphabet table. + * + * Bug 1138841 - Incorrect Spanish national language locking shift table + * definition. + */ +add_test(function test_GsmPDUHelper_writeStringAsSeptets_spanish_fallback() { + let worker = newWorker({ + postRILMessage: function(data) { + // Do nothing + }, + postMessage: function(message) { + // Do nothing + } + }); + + let context = worker.ContextPool._contexts[0]; + let helper = context.GsmPDUHelper; + let buf = []; + helper.writeHexOctet = function(octet) { + buf.push(octet); + } + + // Simple message string which is covered by GSM default alphabet. + let msg = "The quick brown fox jumps over the lazy dog"; + + // Encoded with GSM default alphabet. + helper.writeStringAsSeptets(msg, 0 /* paddingBits */, + PDU_NL_IDENTIFIER_DEFAULT, PDU_NL_IDENTIFIER_DEFAULT); + let octetsWithDefaultTable = buf; + buf = []; + + // Encoded with Spanish locking shift table. + helper.writeStringAsSeptets(msg, 0 /* paddingBits */, + PDU_NL_IDENTIFIER_SPANISH, PDU_NL_IDENTIFIER_SPANISH); + + // The length and content should be equal to what encoded with GSM default + // alphabet. + equal(octetsWithDefaultTable.length, buf.length); + for (let i = 0; i < buf.length; i++) { + equal(octetsWithDefaultTable[i], buf[i]); + } + + run_next_test(); +}); + +/** + * Verify GsmPDUHelper#readAddress + */ +add_test(function test_GsmPDUHelper_readAddress() { + let worker = newWorker({ + postRILMessage: function(data) { + // Do nothing + }, + postMessage: function(message) { + // Do nothing + } + }); + + let context = worker.ContextPool._contexts[0]; + let helper = context.GsmPDUHelper; + function test_address(addrHex, addrString) { + let uint16Array = []; + let ix = 0; + for (let i = 0; i < addrHex.length; ++i) { + uint16Array[i] = addrHex[i].charCodeAt(); + } + + context.Buf.readUint16 = function(){ + if(ix >= uint16Array.length) { + do_throw("out of range in uint16Array"); + } + return uint16Array[ix++]; + } + let length = helper.readHexOctet(); + let parsedAddr = helper.readAddress(length); + equal(parsedAddr, addrString); + } + + // For AlphaNumeric + test_address("04D01100", "_@"); + test_address("04D01000", "\u0394@"); + + // Direct prepand + test_address("0B914151245584F6", "+14154255486"); + test_address("0E914151245584B633", "+14154255486#33"); + + // PDU_TOA_NATIONAL + test_address("0BA14151245584F6", "14154255486"); + + run_next_test(); +}); diff --git a/dom/system/gonk/tests/test_ril_worker_sms_nl_tables.js b/dom/system/gonk/tests/test_ril_worker_sms_nl_tables.js new file mode 100644 index 000000000..32bc5dc2a --- /dev/null +++ b/dom/system/gonk/tests/test_ril_worker_sms_nl_tables.js @@ -0,0 +1,77 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +subscriptLoader.loadSubScript("resource://gre/modules/ril_consts.js", this); + +const ESCAPE = "\uffff"; +const RESCTL = "\ufffe"; +const LF = "\n"; +const CR = "\r"; +const SP = " "; +const FF = "\u000c"; + +function run_test() { + run_next_test(); +} + +/** + * Verify validity of the national language tables + */ +add_test(function test_nl_locking_shift_tables_validity() { + for (let lst = 0; lst < PDU_NL_LOCKING_SHIFT_TABLES.length; lst++) { + do_print("Verifying PDU_NL_LOCKING_SHIFT_TABLES[" + lst + "]"); + + let table = PDU_NL_LOCKING_SHIFT_TABLES[lst]; + + // Make sure table length is 128, or it will break table lookup algorithm. + equal(table.length, 128); + + // Make sure special values are preserved. + equal(table[PDU_NL_EXTENDED_ESCAPE], ESCAPE); + equal(table[PDU_NL_LINE_FEED], LF); + equal(table[PDU_NL_CARRIAGE_RETURN], CR); + equal(table[PDU_NL_SPACE], SP); + } + + run_next_test(); +}); + +add_test(function test_nl_single_shift_tables_validity() { + for (let sst = 0; sst < PDU_NL_SINGLE_SHIFT_TABLES.length; sst++) { + do_print("Verifying PDU_NL_SINGLE_SHIFT_TABLES[" + sst + "]"); + + let table = PDU_NL_SINGLE_SHIFT_TABLES[sst]; + + // Make sure table length is 128, or it will break table lookup algorithm. + equal(table.length, 128); + + // Make sure special values are preserved. + equal(table[PDU_NL_EXTENDED_ESCAPE], ESCAPE); + equal(table[PDU_NL_PAGE_BREAK], FF); + equal(table[PDU_NL_RESERVED_CONTROL], RESCTL); + } + + run_next_test(); +}); + +add_test(function test_gsm_sms_strict_7bit_charmap_validity() { + let defaultTable = PDU_NL_LOCKING_SHIFT_TABLES[PDU_NL_IDENTIFIER_DEFAULT]; + let defaultShiftTable = PDU_NL_SINGLE_SHIFT_TABLES[PDU_NL_IDENTIFIER_DEFAULT]; + for (let from in GSM_SMS_STRICT_7BIT_CHARMAP) { + let to = GSM_SMS_STRICT_7BIT_CHARMAP[from]; + do_print("Verifying GSM_SMS_STRICT_7BIT_CHARMAP[\"\\u0x" + + from.charCodeAt(0).toString(16) + "\"] => \"\\u" + + to.charCodeAt(0).toString(16) + "\""); + + // Make sure "from" is not in default table + equal(defaultTable.indexOf(from), -1); + equal(defaultShiftTable.indexOf(from), -1); + // Make sure "to" is in default table + if ((defaultTable.indexOf(to) < 0) + && (defaultShiftTable.indexOf(to) < 0)) { + equal(false, true); + } + } + + run_next_test(); +}); diff --git a/dom/system/gonk/tests/test_ril_worker_sms_segment_info.js b/dom/system/gonk/tests/test_ril_worker_sms_segment_info.js new file mode 100644 index 000000000..2b29ac60e --- /dev/null +++ b/dom/system/gonk/tests/test_ril_worker_sms_segment_info.js @@ -0,0 +1,115 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +subscriptLoader.loadSubScript("resource://gre/modules/ril_consts.js", this); + +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); + +XPCOMUtils.defineLazyGetter(this, "gSmsSegmentHelper", function() { + let ns = {}; + Cu.import("resource://gre/modules/SmsSegmentHelper.jsm", ns); + return ns.SmsSegmentHelper; +}); + +const ESCAPE = "\uffff"; +const RESCTL = "\ufffe"; + +function run_test() { + run_next_test(); +} + +/** + * Verify SmsSegmentHelper#countGsm7BitSeptets() and + * GsmPDUHelper#writeStringAsSeptets() algorithm match each other. + */ +add_test(function test_SmsSegmentHelper__countGsm7BitSeptets() { + let worker = newWorker({ + postRILMessage: function(data) { + // Do nothing + }, + postMessage: function(message) { + // Do nothing + } + }); + + let context = worker.ContextPool._contexts[0]; + let helper = context.GsmPDUHelper; + helper.resetOctetWritten = function() { + helper.octetsWritten = 0; + }; + helper.writeHexOctet = function() { + helper.octetsWritten++; + }; + + function do_check_calc(str, expectedCalcLen, lst, sst, strict7BitEncoding, strToWrite) { + equal(expectedCalcLen, + gSmsSegmentHelper + .countGsm7BitSeptets(str, + PDU_NL_LOCKING_SHIFT_TABLES[lst], + PDU_NL_SINGLE_SHIFT_TABLES[sst], + strict7BitEncoding)); + + helper.resetOctetWritten(); + strToWrite = strToWrite || str; + helper.writeStringAsSeptets(strToWrite, 0, lst, sst); + equal(Math.ceil(expectedCalcLen * 7 / 8), helper.octetsWritten); + } + + // Test calculation encoded message length using both locking/single shift tables. + for (let lst = 0; lst < PDU_NL_LOCKING_SHIFT_TABLES.length; lst++) { + let langTable = PDU_NL_LOCKING_SHIFT_TABLES[lst]; + + let str = langTable.substring(0, PDU_NL_EXTENDED_ESCAPE) + + langTable.substring(PDU_NL_EXTENDED_ESCAPE + 1); + + for (let sst = 0; sst < PDU_NL_SINGLE_SHIFT_TABLES.length; sst++) { + let langShiftTable = PDU_NL_SINGLE_SHIFT_TABLES[sst]; + + // <escape>, <resctrl> should be ignored. + do_check_calc(ESCAPE + RESCTL, 0, lst, sst); + + // Characters defined in locking shift table should be encoded directly. + do_check_calc(str, str.length, lst, sst); + + let [str1, str2] = ["", ""]; + for (let i = 0; i < langShiftTable.length; i++) { + if ((i == PDU_NL_EXTENDED_ESCAPE) || (i == PDU_NL_RESERVED_CONTROL)) { + continue; + } + + let c = langShiftTable[i]; + if (langTable.indexOf(c) >= 0) { + str1 += c; + } else { + str2 += c; + } + } + + // Characters found in both locking/single shift tables should be + // directly encoded. + do_check_calc(str1, str1.length, lst, sst); + + // Characters found only in single shift tables should be encoded as + // <escape><code>, therefore doubles its original length. + do_check_calc(str2, str2.length * 2, lst, sst); + } + } + + // Bug 790192: support strict GSM SMS 7-Bit encoding + let str = "", strToWrite = "", gsmLen = 0; + for (let c in GSM_SMS_STRICT_7BIT_CHARMAP) { + str += c; + strToWrite += GSM_SMS_STRICT_7BIT_CHARMAP[c]; + if (PDU_NL_LOCKING_SHIFT_TABLES.indexOf(GSM_SMS_STRICT_7BIT_CHARMAP[c])) { + gsmLen += 1; + } else { + gsmLen += 2; + } + } + do_check_calc(str, gsmLen, + PDU_NL_IDENTIFIER_DEFAULT, PDU_NL_IDENTIFIER_DEFAULT, + true, strToWrite); + + run_next_test(); +}); + diff --git a/dom/system/gonk/tests/test_ril_worker_smsc_address.js b/dom/system/gonk/tests/test_ril_worker_smsc_address.js new file mode 100644 index 000000000..c8c283b7c --- /dev/null +++ b/dom/system/gonk/tests/test_ril_worker_smsc_address.js @@ -0,0 +1,112 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +subscriptLoader.loadSubScript("resource://gre/modules/ril_consts.js", this); + +const SMSC_ATT = '+13123149810'; +const SMSC_ATT_TYPO = '+++1312@@@314$$$9,8,1,0'; +const SMSC_ATT_TEXT = '"+13123149810",145'; +const SMSC_ATT_TEXT_INCORRECT_TOA = '"+13123149810",129'; +const SMSC_ATT_PDU = '07913121139418F0'; +const SMSC_O2 = '+447802000332'; +const SMSC_O2_TEXT = '"+447802000332",145'; +const SMSC_O2_PDU = '0791448720003023'; +const SMSC_EMPTY = ''; +const SMSC_TON_UNKNOWN = '0407485455' +const SMSC_TON_UNKNOWN_TEXT = '"0407485455",129'; +const SMSC_TON_UNKNOWN_TEXT_NO_TOA = '"0407485455"'; +const SMSC_TON_UNKNOWN_TEXT_INVALID_TOA = '"0407485455",abc'; +const SMSC_TON_UNKNOWN_PDU = '06814070844555'; +const SMSC_EMPTY_PDU = 'FFFFFFFFFFFFFFFFFFFFFFFF'; +const SMSC_EMPTY_TEXT = ''; + +function run_test() { + run_next_test(); +} + +function setSmsc(context, smsc, ton, npi, expected) { + context.Buf.postRILMessage = function() { + equal(this.readString(), expected); + }; + + context.RIL.setSmscAddress({ + smscAddress: smsc, + typeOfNumber: ton, + numberPlanIdentification: npi + }); +} + +function getSmsc(worker, context, raw, smsc, ton, npi) { + worker.postMessage = function(message) { + equal(message.smscAddress, smsc); + equal(message.typeOfNumber, ton); + equal(message.numberPlanIdentification, npi); + } + + context.Buf.writeString(raw); + context.RIL[REQUEST_GET_SMSC_ADDRESS](0, { rilMessageType: "getSmscAddress"}); +} + +add_test(function test_setSmscAddress() { + let worker = newUint8Worker(); + let context = worker.ContextPool._contexts[0]; + let parcelTypes = []; + context.Buf.newParcel = (type, options) => parcelTypes.push(type); + + // Test text mode. + worker.RILQUIRKS_SMSC_ADDRESS_FORMAT = "text"; + + setSmsc(context, SMSC_ATT, 1, 1, SMSC_ATT_TEXT); + equal(parcelTypes.pop(), REQUEST_SET_SMSC_ADDRESS); + + setSmsc(context, SMSC_O2, 1, 1, SMSC_O2_TEXT); + equal(parcelTypes.pop(), REQUEST_SET_SMSC_ADDRESS); + + setSmsc(context, SMSC_ATT_TYPO, 1, 1, SMSC_ATT_TEXT); + equal(parcelTypes.pop(), REQUEST_SET_SMSC_ADDRESS); + + setSmsc(context, SMSC_TON_UNKNOWN, 0, 1, SMSC_TON_UNKNOWN_TEXT); + equal(parcelTypes.pop(), REQUEST_SET_SMSC_ADDRESS); + + // Test pdu mode. + worker.RILQUIRKS_SMSC_ADDRESS_FORMAT = "pdu"; + + setSmsc(context, SMSC_ATT, 1, 1, SMSC_ATT_PDU); + equal(parcelTypes.pop(), REQUEST_SET_SMSC_ADDRESS); + + setSmsc(context, SMSC_O2, 1, 1, SMSC_O2_PDU); + equal(parcelTypes.pop(), REQUEST_SET_SMSC_ADDRESS); + + setSmsc(context, SMSC_ATT_TYPO, 1, 1, SMSC_ATT_PDU); + equal(parcelTypes.pop(), REQUEST_SET_SMSC_ADDRESS); + + setSmsc(context, SMSC_TON_UNKNOWN, 0, 1, SMSC_TON_UNKNOWN_PDU); + equal(parcelTypes.pop(), REQUEST_SET_SMSC_ADDRESS); + + run_next_test(); +}); + +add_test(function test_getSmscAddress() { + let worker = newUint8Worker(); + let context = worker.ContextPool._contexts[0]; + + // Test text mode. + worker.RILQUIRKS_SMSC_ADDRESS_FORMAT = "text"; + getSmsc(worker, context, SMSC_ATT_TEXT, SMSC_ATT, 1, 1); + getSmsc(worker, context, SMSC_ATT_TEXT_INCORRECT_TOA, SMSC_ATT, 1, 1); + getSmsc(worker, context, SMSC_O2_TEXT, SMSC_O2, 1, 1); + getSmsc(worker, context, SMSC_TON_UNKNOWN_TEXT, SMSC_TON_UNKNOWN, 0, 1); + getSmsc(worker, context, SMSC_TON_UNKNOWN_TEXT_NO_TOA, SMSC_TON_UNKNOWN, 0, 1); + getSmsc(worker, context, SMSC_TON_UNKNOWN_TEXT_INVALID_TOA, SMSC_TON_UNKNOWN, + 0, 1); + getSmsc(worker, context, SMSC_EMPTY_TEXT, SMSC_EMPTY, 0, 1); + + // Test pdu mode. + worker.RILQUIRKS_SMSC_ADDRESS_FORMAT = "pdu"; + getSmsc(worker, context, SMSC_ATT_PDU, SMSC_ATT, 1, 1); + getSmsc(worker, context, SMSC_O2_PDU, SMSC_O2, 1, 1); + getSmsc(worker, context, SMSC_TON_UNKNOWN_PDU, SMSC_TON_UNKNOWN, 0, 1); + getSmsc(worker, context, SMSC_EMPTY_PDU, SMSC_EMPTY, 0, 1); + + run_next_test(); +}); diff --git a/dom/system/gonk/tests/test_ril_worker_ssn.js b/dom/system/gonk/tests/test_ril_worker_ssn.js new file mode 100644 index 000000000..ea0a2a599 --- /dev/null +++ b/dom/system/gonk/tests/test_ril_worker_ssn.js @@ -0,0 +1,104 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +subscriptLoader.loadSubScript("resource://gre/modules/ril_consts.js", this); + +function run_test() { + run_next_test(); +} + +add_test(function test_notification() { + let workerHelper = newInterceptWorker(); + let worker = workerHelper.worker; + let context = worker.ContextPool._contexts[0]; + + function Call(callIndex, number) { + this.callIndex = callIndex; + this.number = number; + } + + Call.prototype = { + // Should use CALL_STATE_ACTIVE. + // Any new outgoing call (state = dialing or alerting) will be drop if there + // is no pending outgoing call created before. + state: CALL_STATE_ACTIVE, + //callIndex: 0, + toa: 0, + isMpty: false, + isMT: false, + als: 0, + isVoice: true, + isVoicePrivacy: false, + //number: null, + numberPresentation: 0, + name: null, + namePresentation: 0, + uusInfo: null + }; + + let oneCall = { + 0: new Call(0, '00000') + }; + + let twoCalls = { + 0: new Call(0, '00000'), + 1: new Call(1, '11111') + }; + + function testNotification(calls, code, number, resultNotification) { + + let testInfo = {calls: calls, code: code, number: number, + resultNotification: resultNotification}; + do_print('Test case info: ' + JSON.stringify(testInfo)); + + // Set current calls. + context.RIL.sendChromeMessage({ + rilMessageType: "currentCalls", + calls: calls + }); + + let notificationInfo = { + notificationType: 1, // MT + code: code, + index: 0, + type: 0, + number: number + }; + + context.RIL._processSuppSvcNotification(notificationInfo); + + let postedMessage = workerHelper.postedMessage; + equal(postedMessage.rilMessageType, 'suppSvcNotification'); + equal(postedMessage.number, number); + equal(postedMessage.notification, resultNotification); + + // Clear all existed calls. + context.RIL.sendChromeMessage({ + rilMessageType: "currentCalls", + calls: {} + }); + } + + testNotification(oneCall, SUPP_SVC_NOTIFICATION_CODE2_PUT_ON_HOLD, null, + GECKO_SUPP_SVC_NOTIFICATION_REMOTE_HELD); + + testNotification(oneCall, SUPP_SVC_NOTIFICATION_CODE2_RETRIEVED, null, + GECKO_SUPP_SVC_NOTIFICATION_REMOTE_RESUMED); + + testNotification(twoCalls, SUPP_SVC_NOTIFICATION_CODE2_PUT_ON_HOLD, null, + GECKO_SUPP_SVC_NOTIFICATION_REMOTE_HELD); + + testNotification(twoCalls, SUPP_SVC_NOTIFICATION_CODE2_RETRIEVED, null, + GECKO_SUPP_SVC_NOTIFICATION_REMOTE_RESUMED); + + testNotification(twoCalls, SUPP_SVC_NOTIFICATION_CODE2_PUT_ON_HOLD, '00000', + GECKO_SUPP_SVC_NOTIFICATION_REMOTE_HELD); + + testNotification(twoCalls, SUPP_SVC_NOTIFICATION_CODE2_PUT_ON_HOLD, '11111', + GECKO_SUPP_SVC_NOTIFICATION_REMOTE_HELD); + + testNotification(twoCalls, SUPP_SVC_NOTIFICATION_CODE2_PUT_ON_HOLD, '22222', + GECKO_SUPP_SVC_NOTIFICATION_REMOTE_HELD); + + run_next_test(); +}); diff --git a/dom/system/gonk/tests/test_ril_worker_stk.js b/dom/system/gonk/tests/test_ril_worker_stk.js new file mode 100644 index 000000000..49b914e89 --- /dev/null +++ b/dom/system/gonk/tests/test_ril_worker_stk.js @@ -0,0 +1,1698 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +subscriptLoader.loadSubScript("resource://gre/modules/ril_consts.js", this); + +function run_test() { + run_next_test(); +} + +/** + * Helper function. + */ +function newUint8SupportOutgoingIndexWorker() { + let worker = newWorker(); + let index = 4; // index for read + let buf = [0, 0, 0, 0]; // Preserved parcel size + let context = worker.ContextPool._contexts[0]; + + context.Buf.writeUint8 = function(value) { + if (context.Buf.outgoingIndex >= buf.length) { + buf.push(value); + } else { + buf[context.Buf.outgoingIndex] = value; + } + + context.Buf.outgoingIndex++; + }; + + context.Buf.readUint8 = function() { + return buf[index++]; + }; + + context.Buf.seekIncoming = function(offset) { + index += offset; + }; + + worker.debug = do_print; + + return worker; +} + +// Test RIL requests related to STK. +/** + * Verify if RIL.sendStkTerminalProfile be called. + */ +add_test(function test_if_send_stk_terminal_profile() { + let worker = newUint8Worker(); + let context = worker.ContextPool._contexts[0]; + let profileSend = false; + context.RIL.sendStkTerminalProfile = function(data) { + profileSend = true; + }; + + let iccStatus = { + gsmUmtsSubscriptionAppIndex: 0, + apps: [{ + app_state: CARD_APPSTATE_READY, + app_type: CARD_APPTYPE_USIM + }], + }; + worker.RILQUIRKS_SEND_STK_PROFILE_DOWNLOAD = false; + + context.RIL._processICCStatus(iccStatus); + + equal(profileSend, false); + + run_next_test(); +}); + +/** + * Verify RIL.sendStkTerminalProfile + */ +add_test(function test_send_stk_terminal_profile() { + let worker = newUint8Worker(); + let context = worker.ContextPool._contexts[0]; + let ril = context.RIL; + let buf = context.Buf; + + ril.sendStkTerminalProfile(STK_SUPPORTED_TERMINAL_PROFILE); + + buf.seekIncoming(8); + let profile = buf.readString(); + for (let i = 0; i < STK_SUPPORTED_TERMINAL_PROFILE.length; i++) { + equal(parseInt(profile.substring(2 * i, 2 * i + 2), 16), + STK_SUPPORTED_TERMINAL_PROFILE[i]); + } + + run_next_test(); +}); + +/** + * Verify STK terminal response + */ +add_test(function test_stk_terminal_response() { + let worker = newUint8SupportOutgoingIndexWorker(); + let context = worker.ContextPool._contexts[0]; + let buf = context.Buf; + let pduHelper = context.GsmPDUHelper; + + buf.sendParcel = function() { + // Type + equal(this.readInt32(), REQUEST_STK_SEND_TERMINAL_RESPONSE); + + // Token : we don't care + this.readInt32(); + + // Data Size, 44 = 2 * (TLV_COMMAND_DETAILS_SIZE(5) + + // TLV_DEVICE_ID_SIZE(4) + + // TLV_RESULT_SIZE(3) + + // TEXT LENGTH(10)) + equal(this.readInt32(), 44); + + // Command Details, Type-Length-Value + equal(pduHelper.readHexOctet(), COMPREHENSIONTLV_TAG_COMMAND_DETAILS | + COMPREHENSIONTLV_FLAG_CR); + equal(pduHelper.readHexOctet(), 3); + equal(pduHelper.readHexOctet(), 0x01); + equal(pduHelper.readHexOctet(), STK_CMD_PROVIDE_LOCAL_INFO); + equal(pduHelper.readHexOctet(), STK_LOCAL_INFO_NNA); + + // Device Identifies, Type-Length-Value(Source ID-Destination ID) + equal(pduHelper.readHexOctet(), COMPREHENSIONTLV_TAG_DEVICE_ID); + equal(pduHelper.readHexOctet(), 2); + equal(pduHelper.readHexOctet(), STK_DEVICE_ID_ME); + equal(pduHelper.readHexOctet(), STK_DEVICE_ID_SIM); + + // Result + equal(pduHelper.readHexOctet(), COMPREHENSIONTLV_TAG_RESULT | + COMPREHENSIONTLV_FLAG_CR); + equal(pduHelper.readHexOctet(), 1); + equal(pduHelper.readHexOctet(), STK_RESULT_OK); + + // Text + equal(pduHelper.readHexOctet(), COMPREHENSIONTLV_TAG_TEXT_STRING | + COMPREHENSIONTLV_FLAG_CR); + equal(pduHelper.readHexOctet(), 8); + equal(pduHelper.readHexOctet(), STK_TEXT_CODING_GSM_7BIT_PACKED); + equal(pduHelper.readSeptetsToString(7, 0, PDU_NL_IDENTIFIER_DEFAULT, + PDU_NL_IDENTIFIER_DEFAULT), "Mozilla"); + + run_next_test(); + }; + + let response = { + command: { + commandNumber: 0x01, + typeOfCommand: STK_CMD_PROVIDE_LOCAL_INFO, + commandQualifier: STK_LOCAL_INFO_NNA, + options: { + isPacked: true + } + }, + input: "Mozilla", + resultCode: STK_RESULT_OK + }; + context.RIL.sendStkTerminalResponse(response); +}); + +/** + * Verify STK terminal response : GET INPUT with empty string. + * + * @See |TERMINAL RESPONSE: GET INPUT 1.9.1A| of 27.22.4.3.1 GET INPUT (normal) + * in TS 102 384. + */ +add_test(function test_stk_terminal_response_get_input_empty_string() { + let worker = newUint8SupportOutgoingIndexWorker(); + let context = worker.ContextPool._contexts[0]; + let buf = context.Buf; + let pduHelper = context.GsmPDUHelper; + + buf.sendParcel = function() { + // Type + equal(this.readInt32(), REQUEST_STK_SEND_TERMINAL_RESPONSE); + + // Token : we don't care + this.readInt32(); + + // Data Size, 30 = 2 * (TLV_COMMAND_DETAILS_SIZE(5) + + // TLV_DEVICE_ID_SIZE(4) + + // TLV_RESULT_SIZE(3) + + // TEXT LENGTH(3)) + equal(this.readInt32(), 30); + + // Command Details, Type-Length-Value + equal(pduHelper.readHexOctet(), COMPREHENSIONTLV_TAG_COMMAND_DETAILS | + COMPREHENSIONTLV_FLAG_CR); + equal(pduHelper.readHexOctet(), 3); + equal(pduHelper.readHexOctet(), 0x01); + equal(pduHelper.readHexOctet(), STK_CMD_GET_INPUT); + equal(pduHelper.readHexOctet(), 0x00); + + // Device Identifies, Type-Length-Value(Source ID-Destination ID) + equal(pduHelper.readHexOctet(), COMPREHENSIONTLV_TAG_DEVICE_ID); + equal(pduHelper.readHexOctet(), 2); + equal(pduHelper.readHexOctet(), STK_DEVICE_ID_ME); + equal(pduHelper.readHexOctet(), STK_DEVICE_ID_SIM); + + // Result + equal(pduHelper.readHexOctet(), COMPREHENSIONTLV_TAG_RESULT | + COMPREHENSIONTLV_FLAG_CR); + equal(pduHelper.readHexOctet(), 1); + equal(pduHelper.readHexOctet(), STK_RESULT_OK); + + // Text + equal(pduHelper.readHexOctet(), COMPREHENSIONTLV_TAG_TEXT_STRING | + COMPREHENSIONTLV_FLAG_CR); + equal(pduHelper.readHexOctet(), 1); + equal(pduHelper.readHexOctet(), STK_TEXT_CODING_GSM_8BIT); + + run_next_test(); + }; + + let response = { + command: { + commandNumber: 0x01, + typeOfCommand: STK_CMD_GET_INPUT, + commandQualifier: 0x00, + options: { + minLength: 0, + maxLength: 1, + defaultText: "<SEND>" + } + }, + input: "", + resultCode: STK_RESULT_OK + }; + context.RIL.sendStkTerminalResponse(response); +}); + +/** + * Verify STK terminal response : GET INPUT with 160 unpacked characters. + * + * @See |TERMINAL RESPONSE: GET INPUT 1.8.1| of 27.22.4.3.1 GET INPUT (normal) + * in TS 102 384. + */ +add_test(function test_stk_terminal_response_get_input_160_unpacked_characters() { + let worker = newUint8SupportOutgoingIndexWorker(); + let context = worker.ContextPool._contexts[0]; + let buf = context.Buf; + let pduHelper = context.GsmPDUHelper; + let iccPduHelper = context.ICCPDUHelper; + let TEST_TEXT_STRING = "***1111111111###" + + "***2222222222###" + + "***3333333333###" + + "***4444444444###" + + "***5555555555###" + + "***6666666666###" + + "***7777777777###" + + "***8888888888###" + + "***9999999999###" + + "***0000000000###"; + + buf.sendParcel = function() { + // Type + equal(this.readInt32(), REQUEST_STK_SEND_TERMINAL_RESPONSE); + + // Token : we don't care + this.readInt32(); + + // Data Size, 352 = 2 * (TLV_COMMAND_DETAILS_SIZE(5) + + // TLV_DEVICE_ID_SIZE(4) + + // TLV_RESULT_SIZE(3) + + // TEXT LENGTH(164)) + equal(this.readInt32(), 352); + + // Command Details, Type-Length-Value + equal(pduHelper.readHexOctet(), COMPREHENSIONTLV_TAG_COMMAND_DETAILS | + COMPREHENSIONTLV_FLAG_CR); + equal(pduHelper.readHexOctet(), 3); + equal(pduHelper.readHexOctet(), 0x01); + equal(pduHelper.readHexOctet(), STK_CMD_GET_INPUT); + equal(pduHelper.readHexOctet(), 0x00); + + // Device Identifies, Type-Length-Value(Source ID-Destination ID) + equal(pduHelper.readHexOctet(), COMPREHENSIONTLV_TAG_DEVICE_ID); + equal(pduHelper.readHexOctet(), 2); + equal(pduHelper.readHexOctet(), STK_DEVICE_ID_ME); + equal(pduHelper.readHexOctet(), STK_DEVICE_ID_SIM); + + // Result + equal(pduHelper.readHexOctet(), COMPREHENSIONTLV_TAG_RESULT | + COMPREHENSIONTLV_FLAG_CR); + equal(pduHelper.readHexOctet(), 1); + equal(pduHelper.readHexOctet(), STK_RESULT_OK); + + // Text + equal(pduHelper.readHexOctet(), COMPREHENSIONTLV_TAG_TEXT_STRING | + COMPREHENSIONTLV_FLAG_CR); + // C-TLV Length Encoding: 161 = 0x81 0xA1 + equal(pduHelper.readHexOctet(), 0x81); + equal(pduHelper.readHexOctet(), 0xA1); + equal(pduHelper.readHexOctet(), STK_TEXT_CODING_GSM_8BIT); + equal(iccPduHelper.read8BitUnpackedToString(160), TEST_TEXT_STRING); + + run_next_test(); + }; + + let response = { + command: { + commandNumber: 0x01, + typeOfCommand: STK_CMD_GET_INPUT, + commandQualifier: 0x00, + options: { + minLength: 160, + maxLength: 160, + text: TEST_TEXT_STRING + } + }, + input: TEST_TEXT_STRING, + resultCode: STK_RESULT_OK + }; + context.RIL.sendStkTerminalResponse(response); +}); + +/** + * Verify STK terminal response : GET_INKEY - NO_RESPONSE_FROM_USER with + * duration provided. + * + * @See |27.22.4.2.8 GET INKEY (Variable Time out)| in TS 102 384. + */ +add_test(function test_stk_terminal_response_get_inkey_no_response_from_user() { + let worker = newUint8SupportOutgoingIndexWorker(); + let context = worker.ContextPool._contexts[0]; + let buf = context.Buf; + let pduHelper = context.GsmPDUHelper; + + buf.sendParcel = function() { + // Type + equal(this.readInt32(), REQUEST_STK_SEND_TERMINAL_RESPONSE); + + // Token : we don't care + this.readInt32(); + + // Data Size, 32 = 2 * (TLV_COMMAND_DETAILS_SIZE(5) + + // TLV_DEVICE_ID_SIZE(4) + + // TLV_RESULT_SIZE(3) + + // DURATION(4)) + equal(this.readInt32(), 32); + + // Command Details, Type-Length-Value + equal(pduHelper.readHexOctet(), COMPREHENSIONTLV_TAG_COMMAND_DETAILS | + COMPREHENSIONTLV_FLAG_CR); + equal(pduHelper.readHexOctet(), 3); + equal(pduHelper.readHexOctet(), 0x01); + equal(pduHelper.readHexOctet(), STK_CMD_GET_INKEY); + equal(pduHelper.readHexOctet(), 0x00); + + // Device Identifies, Type-Length-Value(Source ID-Destination ID) + equal(pduHelper.readHexOctet(), COMPREHENSIONTLV_TAG_DEVICE_ID); + equal(pduHelper.readHexOctet(), 2); + equal(pduHelper.readHexOctet(), STK_DEVICE_ID_ME); + equal(pduHelper.readHexOctet(), STK_DEVICE_ID_SIM); + + // Result + equal(pduHelper.readHexOctet(), COMPREHENSIONTLV_TAG_RESULT | + COMPREHENSIONTLV_FLAG_CR); + equal(pduHelper.readHexOctet(), 1); + equal(pduHelper.readHexOctet(), STK_RESULT_NO_RESPONSE_FROM_USER); + + // Duration, Type-Length-Value(Time unit, Time interval) + equal(pduHelper.readHexOctet(), COMPREHENSIONTLV_TAG_DURATION); + equal(pduHelper.readHexOctet(), 2); + equal(pduHelper.readHexOctet(), STK_TIME_UNIT_SECOND); + equal(pduHelper.readHexOctet(), 10); + + run_next_test(); + }; + + let response = { + command: { + commandNumber: 0x01, + typeOfCommand: STK_CMD_GET_INKEY, + commandQualifier: 0x00, + options: { + duration: { + timeUnit: STK_TIME_UNIT_SECOND, + timeInterval: 10 + }, + text: 'Enter "+"' + } + }, + resultCode: STK_RESULT_NO_RESPONSE_FROM_USER + }; + context.RIL.sendStkTerminalResponse(response); +}); + +/** + * Verify STK terminal response : GET_INKEY - YES/NO request + */ +add_test(function test_stk_terminal_response_get_inkey() { + function do_test(isYesNo) { + let worker = newUint8SupportOutgoingIndexWorker(); + let context = worker.ContextPool._contexts[0]; + let buf = context.Buf; + let pduHelper = context.GsmPDUHelper; + + buf.sendParcel = function() { + // Type + equal(this.readInt32(), REQUEST_STK_SEND_TERMINAL_RESPONSE); + + // Token : we don't care + this.readInt32(); + + // Data Size, 32 = 2 * (TLV_COMMAND_DETAILS_SIZE(5) + + // TLV_DEVICE_ID_SIZE(4) + + // TLV_RESULT_SIZE(3) + + // TEXT LENGTH(4)) + equal(this.readInt32(), 32); + + // Command Details, Type-Length-Value + equal(pduHelper.readHexOctet(), COMPREHENSIONTLV_TAG_COMMAND_DETAILS | + COMPREHENSIONTLV_FLAG_CR); + equal(pduHelper.readHexOctet(), 3); + equal(pduHelper.readHexOctet(), 0x01); + equal(pduHelper.readHexOctet(), STK_CMD_GET_INKEY); + equal(pduHelper.readHexOctet(), 0x04); + + // Device Identifies, Type-Length-Value(Source ID-Destination ID) + equal(pduHelper.readHexOctet(), COMPREHENSIONTLV_TAG_DEVICE_ID); + equal(pduHelper.readHexOctet(), 2); + equal(pduHelper.readHexOctet(), STK_DEVICE_ID_ME); + equal(pduHelper.readHexOctet(), STK_DEVICE_ID_SIM); + + // Result + equal(pduHelper.readHexOctet(), COMPREHENSIONTLV_TAG_RESULT | + COMPREHENSIONTLV_FLAG_CR); + equal(pduHelper.readHexOctet(), 1); + equal(pduHelper.readHexOctet(), STK_RESULT_OK); + + // Yes/No response + equal(pduHelper.readHexOctet(), COMPREHENSIONTLV_TAG_TEXT_STRING | + COMPREHENSIONTLV_FLAG_CR); + equal(pduHelper.readHexOctet(), 2); + equal(pduHelper.readHexOctet(), STK_TEXT_CODING_GSM_8BIT); + equal(pduHelper.readHexOctet(), isYesNo ? 0x01 : 0x00); + }; + + let response = { + command: { + commandNumber: 0x01, + typeOfCommand: STK_CMD_GET_INKEY, + commandQualifier: 0x04, + options: { + isYesNoRequested: true + } + }, + isYesNo: isYesNo, + resultCode: STK_RESULT_OK + }; + + context.RIL.sendStkTerminalResponse(response); + }; + + // Test "Yes" response + do_test(true); + // Test "No" response + do_test(false); + + run_next_test(); +}); + +/** + * Verify STK terminal response with additional information. + */ +add_test(function test_stk_terminal_response_with_additional_info() { + function do_test(aInfo) { + let worker = newUint8SupportOutgoingIndexWorker(); + let context = worker.ContextPool._contexts[0]; + let buf = context.Buf; + let pduHelper = context.GsmPDUHelper; + + buf.sendParcel = function() { + // Type + equal(this.readInt32(), REQUEST_STK_SEND_TERMINAL_RESPONSE); + + // Token : we don't care + this.readInt32(); + + // Data Length 26 = 2 * (TLV_COMMAND_DETAILS_SIZE(5) + + // TLV_DEVICE_ID_SIZE(4) + + // TLV_RESULT_SIZE(4)) + equal(this.readInt32(), 26); + + // Command Details, Type-Length-Value(commandNumber, typeOfCommand, commandQualifier) + equal(pduHelper.readHexOctet(), COMPREHENSIONTLV_TAG_COMMAND_DETAILS | + COMPREHENSIONTLV_FLAG_CR); + equal(pduHelper.readHexOctet(), 3); + equal(pduHelper.readHexOctet(), 0x01); + equal(pduHelper.readHexOctet(), STK_CMD_DISPLAY_TEXT); + equal(pduHelper.readHexOctet(), 0x01); + + // Device Identifies, Type-Length-Value(Source ID-Destination ID) + equal(pduHelper.readHexOctet(), COMPREHENSIONTLV_TAG_DEVICE_ID); + equal(pduHelper.readHexOctet(), 2); + equal(pduHelper.readHexOctet(), STK_DEVICE_ID_ME); + equal(pduHelper.readHexOctet(), STK_DEVICE_ID_SIM); + + // Result, Type-Length-Value(General result, Additional information on result) + equal(pduHelper.readHexOctet(), COMPREHENSIONTLV_TAG_RESULT | + COMPREHENSIONTLV_FLAG_CR); + equal(pduHelper.readHexOctet(), 2); + equal(pduHelper.readHexOctet(), STK_RESULT_TERMINAL_CRNTLY_UNABLE_TO_PROCESS); + equal(pduHelper.readHexOctet(), aInfo); + }; + + let response = { + command: { + commandNumber: 0x01, + typeOfCommand: STK_CMD_DISPLAY_TEXT, + commandQualifier: 0x01, + options: { + isHighPriority: true + } + }, + resultCode: STK_RESULT_TERMINAL_CRNTLY_UNABLE_TO_PROCESS, + additionalInformation: aInfo + }; + + context.RIL.sendStkTerminalResponse(response); + }; + + do_test(0x01); // 'Screen is busy' + + run_next_test(); +}); + +// Test ComprehensionTlvHelper + +/** + * Verify ComprehensionTlvHelper.writeLocationInfoTlv + */ +add_test(function test_write_location_info_tlv() { + let worker = newUint8Worker(); + let context = worker.ContextPool._contexts[0]; + let pduHelper = context.GsmPDUHelper; + let tlvHelper = context.ComprehensionTlvHelper; + + // Test with 2-digit mnc, and gsmCellId obtained from UMTS network. + let loc = { + mcc: "466", + mnc: "92", + gsmLocationAreaCode : 10291, + gsmCellId: 19072823 + }; + tlvHelper.writeLocationInfoTlv(loc); + + let tag = pduHelper.readHexOctet(); + equal(tag, COMPREHENSIONTLV_TAG_LOCATION_INFO | + COMPREHENSIONTLV_FLAG_CR); + + let length = pduHelper.readHexOctet(); + equal(length, 9); + + let mcc_mnc = pduHelper.readSwappedNibbleBcdString(3); + equal(mcc_mnc, "46692"); + + let lac = (pduHelper.readHexOctet() << 8) | pduHelper.readHexOctet(); + equal(lac, 10291); + + let cellId = (pduHelper.readHexOctet() << 24) | + (pduHelper.readHexOctet() << 16) | + (pduHelper.readHexOctet() << 8) | + (pduHelper.readHexOctet()); + equal(cellId, 19072823); + + // Test with 1-digit mnc, and gsmCellId obtained from GSM network. + loc = { + mcc: "466", + mnc: "02", + gsmLocationAreaCode : 10291, + gsmCellId: 65534 + }; + tlvHelper.writeLocationInfoTlv(loc); + + tag = pduHelper.readHexOctet(); + equal(tag, COMPREHENSIONTLV_TAG_LOCATION_INFO | + COMPREHENSIONTLV_FLAG_CR); + + length = pduHelper.readHexOctet(); + equal(length, 7); + + mcc_mnc = pduHelper.readSwappedNibbleBcdString(3); + equal(mcc_mnc, "46602"); + + lac = (pduHelper.readHexOctet() << 8) | pduHelper.readHexOctet(); + equal(lac, 10291); + + cellId = (pduHelper.readHexOctet() << 8) | (pduHelper.readHexOctet()); + equal(cellId, 65534); + + // Test with 3-digit mnc, and gsmCellId obtained from GSM network. + loc = { + mcc: "466", + mnc: "222", + gsmLocationAreaCode : 10291, + gsmCellId: 65534 + }; + tlvHelper.writeLocationInfoTlv(loc); + + tag = pduHelper.readHexOctet(); + equal(tag, COMPREHENSIONTLV_TAG_LOCATION_INFO | + COMPREHENSIONTLV_FLAG_CR); + + length = pduHelper.readHexOctet(); + equal(length, 7); + + mcc_mnc = pduHelper.readSwappedNibbleBcdString(3); + equal(mcc_mnc, "466222"); + + lac = (pduHelper.readHexOctet() << 8) | pduHelper.readHexOctet(); + equal(lac, 10291); + + cellId = (pduHelper.readHexOctet() << 8) | (pduHelper.readHexOctet()); + equal(cellId, 65534); + + run_next_test(); +}); + +/** + * Verify ComprehensionTlvHelper.writeErrorNumber + */ +add_test(function test_write_disconnecting_cause() { + let worker = newUint8Worker(); + let context = worker.ContextPool._contexts[0]; + let pduHelper = context.GsmPDUHelper; + let tlvHelper = context.ComprehensionTlvHelper; + + tlvHelper.writeCauseTlv(RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[CALL_FAIL_BUSY]); + let tag = pduHelper.readHexOctet(); + equal(tag, COMPREHENSIONTLV_TAG_CAUSE | COMPREHENSIONTLV_FLAG_CR); + let len = pduHelper.readHexOctet(); + equal(len, 2); // We have one cause. + let standard = pduHelper.readHexOctet(); + equal(standard, 0x60); + let cause = pduHelper.readHexOctet(); + equal(cause, 0x80 | CALL_FAIL_BUSY); + + run_next_test(); +}); + +/** + * Verify ComprehensionTlvHelper.getSizeOfLengthOctets + */ +add_test(function test_get_size_of_length_octets() { + let worker = newUint8Worker(); + let context = worker.ContextPool._contexts[0]; + let tlvHelper = context.ComprehensionTlvHelper; + + let length = 0x70; + equal(tlvHelper.getSizeOfLengthOctets(length), 1); + + length = 0x80; + equal(tlvHelper.getSizeOfLengthOctets(length), 2); + + length = 0x180; + equal(tlvHelper.getSizeOfLengthOctets(length), 3); + + length = 0x18000; + equal(tlvHelper.getSizeOfLengthOctets(length), 4); + + run_next_test(); +}); + +/** + * Verify ComprehensionTlvHelper.writeLength + */ +add_test(function test_write_length() { + let worker = newUint8Worker(); + let context = worker.ContextPool._contexts[0]; + let pduHelper = context.GsmPDUHelper; + let tlvHelper = context.ComprehensionTlvHelper; + + let length = 0x70; + tlvHelper.writeLength(length); + equal(pduHelper.readHexOctet(), length); + + length = 0x80; + tlvHelper.writeLength(length); + equal(pduHelper.readHexOctet(), 0x81); + equal(pduHelper.readHexOctet(), length); + + length = 0x180; + tlvHelper.writeLength(length); + equal(pduHelper.readHexOctet(), 0x82); + equal(pduHelper.readHexOctet(), (length >> 8) & 0xff); + equal(pduHelper.readHexOctet(), length & 0xff); + + length = 0x18000; + tlvHelper.writeLength(length); + equal(pduHelper.readHexOctet(), 0x83); + equal(pduHelper.readHexOctet(), (length >> 16) & 0xff); + equal(pduHelper.readHexOctet(), (length >> 8) & 0xff); + equal(pduHelper.readHexOctet(), length & 0xff); + + run_next_test(); +}); + +// Test Proactive commands. + +function test_stk_proactive_command(aOptions) { + let worker = newUint8Worker(); + let context = worker.ContextPool._contexts[0]; + let pduHelper = context.GsmPDUHelper; + let berHelper = context.BerTlvHelper; + let stkHelper = context.StkProactiveCmdHelper; + let stkFactory = context.StkCommandParamsFactory; + + let testPdu = aOptions.pdu; + let testTypeOfCommand = aOptions.typeOfCommand; + let testIcons = aOptions.icons; + let testFunc = aOptions.testFunc; + + if (testIcons) { + let ril = context.RIL; + ril.iccInfoPrivate.sst = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x30]; //IMG: 39 + ril.appType = CARD_APPTYPE_SIM; + + // skip asynchornous process in IconLoader.loadIcons(). + let iconLoader = context.IconLoader; + iconLoader.loadIcons = (recordNumbers, onsuccess, onerror) => { + onsuccess(testIcons); + }; + } + + for(let i = 0 ; i < testPdu.length; i++) { + pduHelper.writeHexOctet(testPdu[i]); + } + + let berTlv = berHelper.decode(testPdu.length); + let ctlvs = berTlv.value; + let ctlv = stkHelper.searchForTag(COMPREHENSIONTLV_TAG_COMMAND_DETAILS, ctlvs); + let cmdDetails = ctlv.value; + equal(cmdDetails.typeOfCommand, testTypeOfCommand); + + stkFactory.createParam(cmdDetails, ctlvs, (aResult) => { + cmdDetails.options = aResult; + testFunc(context, cmdDetails, ctlvs); + }); +} + +/** + * Verify Proactive command helper : searchForSelectedTags + */ +add_test(function test_stk_proactive_command_search_for_selected_tags() { + let worker = newUint8Worker(); + let context = worker.ContextPool._contexts[0]; + let pduHelper = context.GsmPDUHelper; + let berHelper = context.BerTlvHelper; + let stkHelper = context.StkProactiveCmdHelper; + + let tag_test = [ + 0xD0, + 0x3E, + 0x85, 0x0A, 0x61, 0x6C, 0x70, 0x68, 0x61, 0x20, 0x69, 0x64, 0x20, 0x31, + 0x85, 0x0A, 0x61, 0x6C, 0x70, 0x68, 0x61, 0x20, 0x69, 0x64, 0x20, 0x32, + 0x85, 0x0A, 0x61, 0x6C, 0x70, 0x68, 0x61, 0x20, 0x69, 0x64, 0x20, 0x33, + 0x85, 0x0A, 0x61, 0x6C, 0x70, 0x68, 0x61, 0x20, 0x69, 0x64, 0x20, 0x34, + 0x85, 0x0A, 0x61, 0x6C, 0x70, 0x68, 0x61, 0x20, 0x69, 0x64, 0x20, 0x35, + 0x85, 0x00]; + + for (let i = 0; i < tag_test.length; i++) { + pduHelper.writeHexOctet(tag_test[i]); + } + + let berTlv = berHelper.decode(tag_test.length); + let selectedCtlvs = + stkHelper.searchForSelectedTags(berTlv.value, [COMPREHENSIONTLV_TAG_ALPHA_ID]); + let tlv = selectedCtlvs.retrieve(COMPREHENSIONTLV_TAG_ALPHA_ID); + equal(tlv.value.identifier, "alpha id 1"); + + tlv = selectedCtlvs.retrieve(COMPREHENSIONTLV_TAG_ALPHA_ID); + equal(tlv.value.identifier, "alpha id 2"); + + tlv = selectedCtlvs.retrieve(COMPREHENSIONTLV_TAG_ALPHA_ID); + equal(tlv.value.identifier, "alpha id 3"); + + tlv = selectedCtlvs.retrieve(COMPREHENSIONTLV_TAG_ALPHA_ID); + equal(tlv.value.identifier, "alpha id 4"); + + tlv = selectedCtlvs.retrieve(COMPREHENSIONTLV_TAG_ALPHA_ID); + equal(tlv.value.identifier, "alpha id 5"); + + // emulate that the alpha identifier is provided and is a null data object, + // which is converted to an empty string in ICCPDUHelper. + tlv = selectedCtlvs.retrieve(COMPREHENSIONTLV_TAG_ALPHA_ID); + strictEqual(tlv.value.identifier, ""); + + // emulate that the alpha identifier is not provided + tlv = selectedCtlvs.retrieve(COMPREHENSIONTLV_TAG_ALPHA_ID); + strictEqual(tlv, undefined); + + run_next_test(); +}); + +/** + * Verify Proactive Command : Refresh + */ +add_test(function test_stk_proactive_command_refresh() { + test_stk_proactive_command({ + pdu: [ + 0xD0, + 0x10, + 0x81, 0x03, 0x01, 0x01, 0x01, + 0x82, 0x02, 0x81, 0x82, + 0x92, 0x05, 0x01, 0x3F, 0x00, 0x2F, 0xE2 + ], + typeOfCommand: STK_CMD_REFRESH, + icons: null, + testFunc: (context, cmdDetails, ctlvs) => { + let stkHelper = context.StkProactiveCmdHelper; + let ctlv = stkHelper.searchForTag(COMPREHENSIONTLV_TAG_FILE_LIST, ctlvs); + equal(ctlv.value.fileList, "3F002FE2"); + } + }); + + run_next_test(); +}); + +/** + * Verify Proactive Command : Play Tone + */ +add_test(function test_stk_proactive_command_play_tone() { + test_stk_proactive_command({ + pdu: [ + 0xD0, + 0x1F, + 0x81, 0x03, 0x01, 0x20, 0x00, + 0x82, 0x02, 0x81, 0x03, + 0x85, 0x09, 0x44, 0x69, 0x61, 0x6C, 0x20, 0x54, 0x6F, 0x6E, 0x65, + 0x8E, 0x01, 0x01, + 0x84, 0x02, 0x01, 0x05, + 0x9E, 0x02, 0x00, 0x01 + ], + typeOfCommand: STK_CMD_PLAY_TONE, + icons: [1], + testFunc: (context, cmdDetails, ctlvs) => { + let playTone = cmdDetails.options; + + equal(playTone.text, "Dial Tone"); + equal(playTone.tone, STK_TONE_TYPE_DIAL_TONE); + equal(playTone.duration.timeUnit, STK_TIME_UNIT_SECOND); + equal(playTone.duration.timeInterval, 5); + equal(playTone.iconSelfExplanatory, true); + equal(playTone.icons, 1); + } + }); + + run_next_test(); +}); + +/** + * Verify Proactive Command : Poll Interval + */ +add_test(function test_stk_proactive_command_poll_interval() { + test_stk_proactive_command({ + pdu: [ + 0xD0, + 0x0D, + 0x81, 0x03, 0x01, 0x03, 0x00, + 0x82, 0x02, 0x81, 0x82, + 0x84, 0x02, 0x01, 0x14 + ], + typeOfCommand: STK_CMD_POLL_INTERVAL, + icons: null, + testFunc: (context, cmdDetails, ctlvs) => { + let interval = cmdDetails.options; + + equal(interval.timeUnit, STK_TIME_UNIT_SECOND); + equal(interval.timeInterval, 0x14); + } + }); + + run_next_test(); +}); + +/** + * Verify Proactive Command: Display Text + */ +add_test(function test_stk_proactive_command_display_text() { + test_stk_proactive_command({ + pdu: [ + 0xd0, + 0x2c, + 0x81, 0x03, 0x01, 0x21, 0x80, + 0x82, 0x02, 0x81, 0x02, + 0x0d, 0x1d, 0x00, 0xd3, 0x30, 0x9b, 0xfc, 0x06, 0xc9, 0x5c, 0x30, 0x1a, + 0xa8, 0xe8, 0x02, 0x59, 0xc3, 0xec, 0x34, 0xb9, 0xac, 0x07, 0xc9, 0x60, + 0x2f, 0x58, 0xed, 0x15, 0x9b, 0xb9, 0x40, + 0x9e, 0x02, 0x00, 0x01 + ], + typeOfCommand: STK_CMD_DISPLAY_TEXT, + icons: [1], + testFunc: (context, cmdDetails, ctlvs) => { + let textMsg = cmdDetails.options; + + equal(textMsg.text, "Saldo 2.04 E. Validez 20/05/13. "); + equal(textMsg.iconSelfExplanatory, true); + equal(textMsg.icons, 1); + } + }); + + run_next_test(); +}); + +/** + * Verify Proactive Command: Set Up Event List. + */ +add_test(function test_stk_proactive_command_event_list() { + test_stk_proactive_command({ + pdu: [ + 0xD0, + 0x0F, + 0x81, 0x03, 0x01, 0x05, 0x00, + 0x82, 0x02, 0x81, 0x82, + 0x99, 0x04, 0x00, 0x01, 0x02, 0x03 + ], + typeOfCommand: STK_CMD_SET_UP_EVENT_LIST, + icons: null, + testFunc: (context, cmdDetails, ctlvs) => { + let event = cmdDetails.options; + + equal(Array.isArray(event.eventList), true); + + for (let i = 0; i < event.eventList.length; i++) { + equal(event.eventList[i], i); + } + } + }); + + run_next_test(); +}); + +/** + * Verify Proactive Command : Get Input + */ +add_test(function test_stk_proactive_command_get_input() { + test_stk_proactive_command({ + pdu: [ + 0xD0, + 0x22, + 0x81, 0x03, 0x01, 0x23, 0x8F, + 0x82, 0x02, 0x81, 0x82, + 0x8D, 0x05, 0x04, 0x54, 0x65, 0x78, 0x74, + 0x91, 0x02, 0x01, 0x10, + 0x17, 0x08, 0x04, 0x44, 0x65, 0x66, 0x61, 0x75, 0x6C, 0x74, + 0x9E, 0x02, 0x00, 0x01 + ], + typeOfCommand: STK_CMD_GET_INPUT, + icons: [1], + testFunc: (context, cmdDetails, ctlvs) => { + let input = cmdDetails.options; + + equal(input.text, "Text"); + equal(input.isAlphabet, true); + equal(input.isUCS2, true); + equal(input.hideInput, true); + equal(input.isPacked, true); + equal(input.isHelpAvailable, true); + equal(input.minLength, 0x01); + equal(input.maxLength, 0x10); + equal(input.defaultText, "Default"); + equal(input.iconSelfExplanatory, true); + equal(input.icons, 1); + } + }); + + test_stk_proactive_command({ + pdu: [ + 0xD0, + 0x11, + 0x81, 0x03, 0x01, 0x23, 0x00, + 0x82, 0x02, 0x81, 0x82, + 0x8D, 0x00, + 0x91, 0x02, 0x01, 0x10, + 0x17, 0x00 + ], + typeOfCommand: STK_CMD_GET_INPUT, + icons: null, + testFunc: (context, cmdDetails, ctlvs) => { + let input = cmdDetails.options; + + equal(input.text, null); + equal(input.minLength, 0x01); + equal(input.maxLength, 0x10); + equal(input.defaultText, null); + } + }); + + run_next_test(); +}); + +/** + * Verify Proactive Command : More Time + */ +add_test(function test_stk_proactive_command_more_time() { + let worker = newUint8Worker(); + let context = worker.ContextPool._contexts[0]; + let pduHelper = context.GsmPDUHelper; + let berHelper = context.BerTlvHelper; + let stkHelper = context.StkProactiveCmdHelper; + + let more_time_1 = [ + 0xD0, + 0x09, + 0x81, 0x03, 0x01, 0x02, 0x00, + 0x82, 0x02, 0x81, 0x82]; + + for(let i = 0 ; i < more_time_1.length; i++) { + pduHelper.writeHexOctet(more_time_1[i]); + } + + let berTlv = berHelper.decode(more_time_1.length); + let ctlvs = berTlv.value; + let tlv = stkHelper.searchForTag(COMPREHENSIONTLV_TAG_COMMAND_DETAILS, ctlvs); + equal(tlv.value.commandNumber, 0x01); + equal(tlv.value.typeOfCommand, STK_CMD_MORE_TIME); + equal(tlv.value.commandQualifier, 0x00); + + run_next_test(); +}); + +/** + * Verify Proactive Command : Select Item + */ +add_test(function test_stk_proactive_command_select_item() { + test_stk_proactive_command({ + pdu: [ + 0xD0, + 0x3D, + 0x81, 0x03, 0x01, 0x24, 0x00, + 0x82, 0x02, 0x81, 0x82, + 0x85, 0x05, 0x54, 0x69, 0x74, 0x6C, 0x65, + 0x8F, 0x07, 0x01, 0x69, 0x74, 0x65, 0x6D, 0x20, 0x31, + 0x8F, 0x07, 0x02, 0x69, 0x74, 0x65, 0x6D, 0x20, 0x32, + 0x8F, 0x07, 0x03, 0x69, 0x74, 0x65, 0x6D, 0x20, 0x33, + 0x18, 0x03, 0x10, 0x15, 0x20, + 0x90, 0x01, 0x01, + 0x9E, 0x02, 0x00, 0x01, + 0x9F, 0x04, 0x00, 0x01, 0x02, 0x03 + ], + typeOfCommand: STK_CMD_SELECT_ITEM, + icons: [1, 1, 2, 3], + testFunc: (context, cmdDetails, ctlvs) => { + let menu = cmdDetails.options; + + equal(menu.title, "Title"); + equal(menu.iconSelfExplanatory, true); + equal(menu.icons, 1); + equal(menu.items[0].identifier, 1); + equal(menu.items[0].text, "item 1"); + equal(menu.items[0].iconSelfExplanatory, true); + equal(menu.items[0].icons, 1); + equal(menu.items[1].identifier, 2); + equal(menu.items[1].text, "item 2"); + equal(menu.items[1].iconSelfExplanatory, true); + equal(menu.items[1].icons, 2); + equal(menu.items[2].identifier, 3); + equal(menu.items[2].text, "item 3"); + equal(menu.items[2].iconSelfExplanatory, true); + equal(menu.items[2].icons, 3); + equal(menu.nextActionList[0], STK_CMD_SET_UP_CALL); + equal(menu.nextActionList[1], STK_CMD_LAUNCH_BROWSER); + equal(menu.nextActionList[2], STK_CMD_PLAY_TONE); + equal(menu.defaultItem, 0x00); + } + }); + + test_stk_proactive_command({ + pdu: [ + 0xD0, + 0x33, + 0x81, 0x03, 0x01, 0x24, 0x00, + 0x82, 0x02, 0x81, 0x82, + 0x85, 0x05, 0x54, 0x69, 0x74, 0x6C, 0x65, + 0x8F, 0x07, 0x01, 0x69, 0x74, 0x65, 0x6D, 0x20, 0x31, + 0x8F, 0x07, 0x02, 0x69, 0x74, 0x65, 0x6D, 0x20, 0x32, + 0x8F, 0x07, 0x03, 0x69, 0x74, 0x65, 0x6D, 0x20, 0x33, + 0x18, 0x03, 0x00, 0x15, 0x81, + 0x90, 0x01, 0x03 + ], + typeOfCommand: STK_CMD_SELECT_ITEM, + icons: null, + testFunc: (context, cmdDetails, ctlvs) => { + let menu = cmdDetails.options; + + equal(menu.title, "Title"); + equal(menu.items[0].identifier, 1); + equal(menu.items[0].text, "item 1"); + equal(menu.items[1].identifier, 2); + equal(menu.items[1].text, "item 2"); + equal(menu.items[2].identifier, 3); + equal(menu.items[2].text, "item 3"); + equal(menu.nextActionList[0], STK_NEXT_ACTION_NULL); + equal(menu.nextActionList[1], STK_CMD_LAUNCH_BROWSER); + equal(menu.nextActionList[2], STK_NEXT_ACTION_END_PROACTIVE_SESSION); + equal(menu.defaultItem, 0x02); + } + }); + + run_next_test(); +}); + +/** + * Verify Proactive Command : Set Up Menu + */ +add_test(function test_stk_proactive_command_set_up_menu() { + test_stk_proactive_command({ + pdu: [ + 0xD0, + 0x3A, + 0x81, 0x03, 0x01, 0x25, 0x00, + 0x82, 0x02, 0x81, 0x82, + 0x85, 0x05, 0x54, 0x69, 0x74, 0x6C, 0x65, + 0x8F, 0x07, 0x01, 0x69, 0x74, 0x65, 0x6D, 0x20, 0x31, + 0x8F, 0x07, 0x02, 0x69, 0x74, 0x65, 0x6D, 0x20, 0x32, + 0x8F, 0x07, 0x03, 0x69, 0x74, 0x65, 0x6D, 0x20, 0x33, + 0x18, 0x03, 0x10, 0x15, 0x20, + 0x9E, 0x02, 0x00, 0x01, + 0x9F, 0x04, 0x00, 0x01, 0x02, 0x03 + ], + typeOfCommand: STK_CMD_SET_UP_MENU, + icons: [1, 1, 2, 3], + testFunc: (context, cmdDetails, ctlvs) => { + let menu = cmdDetails.options; + + equal(menu.title, "Title"); + equal(menu.iconSelfExplanatory, true); + equal(menu.icons, 1); + equal(menu.items[0].identifier, 1); + equal(menu.items[0].text, "item 1"); + equal(menu.items[0].iconSelfExplanatory, true); + equal(menu.items[0].icons, 1); + equal(menu.items[1].identifier, 2); + equal(menu.items[1].text, "item 2"); + equal(menu.items[1].iconSelfExplanatory, true); + equal(menu.items[1].icons, 2); + equal(menu.items[2].identifier, 3); + equal(menu.items[2].text, "item 3"); + equal(menu.items[2].iconSelfExplanatory, true); + equal(menu.items[2].icons, 3); + equal(menu.nextActionList[0], STK_CMD_SET_UP_CALL); + equal(menu.nextActionList[1], STK_CMD_LAUNCH_BROWSER); + equal(menu.nextActionList[2], STK_CMD_PLAY_TONE); + } + }); + + test_stk_proactive_command({ + pdu: [ + 0xD0, + 0x30, + 0x81, 0x03, 0x01, 0x25, 0x00, + 0x82, 0x02, 0x81, 0x82, + 0x85, 0x05, 0x54, 0x69, 0x74, 0x6C, 0x65, + 0x8F, 0x07, 0x01, 0x69, 0x74, 0x65, 0x6D, 0x20, 0x31, + 0x8F, 0x07, 0x02, 0x69, 0x74, 0x65, 0x6D, 0x20, 0x32, + 0x8F, 0x07, 0x03, 0x69, 0x74, 0x65, 0x6D, 0x20, 0x33, + 0x18, 0x03, 0x81, 0x00, 0x00 + ], + typeOfCommand: STK_CMD_SET_UP_MENU, + icons: null, + testFunc: (context, cmdDetails, ctlvs) => { + let menu = cmdDetails.options; + + equal(menu.title, "Title"); + equal(menu.items[0].identifier, 1); + equal(menu.items[0].text, "item 1"); + equal(menu.items[1].identifier, 2); + equal(menu.items[1].text, "item 2"); + equal(menu.items[2].identifier, 3); + equal(menu.items[2].text, "item 3"); + equal(menu.nextActionList[0], STK_NEXT_ACTION_END_PROACTIVE_SESSION); + equal(menu.nextActionList[1], STK_NEXT_ACTION_NULL); + equal(menu.nextActionList[2], STK_NEXT_ACTION_NULL); + } + }); + + run_next_test(); +}); + +/** + * Verify Proactive Command : Set Up Call + */ +add_test(function test_stk_proactive_command_set_up_call() { + test_stk_proactive_command({ + pdu: [ + 0xD0, + 0x31, + 0x81, 0x03, 0x01, 0x10, 0x04, + 0x82, 0x02, 0x81, 0x82, + 0x05, 0x0A, 0x44, 0x69, 0x73, 0x63, 0x6F, 0x6E, 0x6E, 0x65, 0x63, 0x74, + 0x86, 0x09, 0x81, 0x10, 0x32, 0x04, 0x21, 0x43, 0x65, 0x1C, 0x2C, + 0x05, 0x07, 0x4D, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, + 0x9E, 0x02, 0x00, 0x01, + 0x9E, 0x02, 0x01, 0x02 + ], + typeOfCommand: STK_CMD_SET_UP_CALL, + icons: [1, 2], + testFunc: (context, cmdDetails, ctlvs) => { + let setupCall = cmdDetails.options; + + equal(setupCall.address, "012340123456,1,2"); + equal(setupCall.confirmMessage.text, "Disconnect"); + equal(setupCall.confirmMessage.iconSelfExplanatory, true); + equal(setupCall.confirmMessage.icons, 1); + equal(setupCall.callMessage.text, "Message"); + equal(setupCall.callMessage.iconSelfExplanatory, false); + equal(setupCall.callMessage.icons, 2); + } + }); + + run_next_test(); +}); + +/** + * Verify Proactive Command : Timer Management + */ +add_test(function test_stk_proactive_command_timer_management() { + // Timer Management - Start + test_stk_proactive_command({ + pdu: [ + 0xD0, + 0x11, + 0x81, 0x03, 0x01, 0x27, 0x00, + 0x82, 0x02, 0x81, 0x82, + 0xA4, 0x01, 0x01, + 0xA5, 0x03, 0x10, 0x20, 0x30 + ], + typeOfCommand: STK_CMD_TIMER_MANAGEMENT, + icons: null, + testFunc: (context, cmdDetails, ctlvs) => { + equal(cmdDetails.commandQualifier, STK_TIMER_START); + + let timer = cmdDetails.options; + + equal(timer.timerId, 0x01); + equal(timer.timerValue, (0x01 * 60 * 60) + (0x02 * 60) + 0x03); + } + }); + + // Timer Management - Deactivate + test_stk_proactive_command({ + pdu: [ + 0xD0, + 0x0C, + 0x81, 0x03, 0x01, 0x27, 0x01, + 0x82, 0x02, 0x81, 0x82, + 0xA4, 0x01, 0x01 + ], + typeOfCommand: STK_CMD_TIMER_MANAGEMENT, + icons: null, + testFunc: (context, cmdDetails, ctlvs) => { + equal(cmdDetails.commandQualifier, STK_TIMER_DEACTIVATE); + + let timer = cmdDetails.options; + + equal(timer.timerId, 0x01); + ok(timer.timerValue === undefined); + } + }); + + run_next_test(); +}); + +/** + * Verify Proactive Command : Provide Local Information + */ +add_test(function test_stk_proactive_command_provide_local_information() { + let worker = newUint8Worker(); + let context = worker.ContextPool._contexts[0]; + let pduHelper = context.GsmPDUHelper; + let berHelper = context.BerTlvHelper; + let stkHelper = context.StkProactiveCmdHelper; + let stkCmdHelper = context.StkCommandParamsFactory; + + // Verify IMEI + test_stk_proactive_command({ + pdu: [ + 0xD0, + 0x09, + 0x81, 0x03, 0x01, 0x26, 0x01, + 0x82, 0x02, 0x81, 0x82 + ], + typeOfCommand: STK_CMD_PROVIDE_LOCAL_INFO, + icons: null, + testFunc: (context, cmdDetails, ctlvs) => { + equal(cmdDetails.commandQualifier, STK_LOCAL_INFO_IMEI); + + let provideLocalInfo = cmdDetails.options; + equal(provideLocalInfo.localInfoType, STK_LOCAL_INFO_IMEI); + } + }); + + // Verify Date and Time Zone + test_stk_proactive_command({ + pdu: [ + 0xD0, + 0x09, + 0x81, 0x03, 0x01, 0x26, 0x03, + 0x82, 0x02, 0x81, 0x82 + ], + typeOfCommand: STK_CMD_PROVIDE_LOCAL_INFO, + icons: null, + testFunc: (context, cmdDetails, ctlvs) => { + equal(cmdDetails.commandQualifier, STK_LOCAL_INFO_DATE_TIME_ZONE); + + let provideLocalInfo = cmdDetails.options; + equal(provideLocalInfo.localInfoType, STK_LOCAL_INFO_DATE_TIME_ZONE); + } + }); + + run_next_test(); +}); + +/** + * Verify Proactive command : BIP Messages + */ +add_test(function test_stk_proactive_command_open_channel() { + let worker = newUint8Worker(); + let context = worker.ContextPool._contexts[0]; + let pduHelper = context.GsmPDUHelper; + let berHelper = context.BerTlvHelper; + let stkHelper = context.StkProactiveCmdHelper; + let stkCmdHelper = context.StkCommandParamsFactory; + + // Open Channel + test_stk_proactive_command({ + pdu: [ + 0xD0, + 0x0F, + 0x81, 0x03, 0x01, 0x40, 0x00, + 0x82, 0x02, 0x81, 0x82, + 0x85, 0x04, 0x4F, 0x70, 0x65, 0x6E //alpha id: "Open" + ], + typeOfCommand: STK_CMD_OPEN_CHANNEL, + icons: null, + testFunc: (context, cmdDetails, ctlvs) => { + let bipMsg = cmdDetails.options; + + equal(bipMsg.text, "Open"); + } + }); + + // Close Channel + test_stk_proactive_command({ + pdu: [ + 0xD0, + 0x10, + 0x81, 0x03, 0x01, 0x41, 0x00, + 0x82, 0x02, 0x81, 0x82, + 0x85, 0x05, 0x43, 0x6C, 0x6F, 0x73, 0x65 //alpha id: "Close" + ], + typeOfCommand: STK_CMD_CLOSE_CHANNEL, + icons: null, + testFunc: (context, cmdDetails, ctlvs) => { + let bipMsg = cmdDetails.options; + + equal(bipMsg.text, "Close"); + } + }); + + // Receive Data + test_stk_proactive_command({ + pdu: [ + 0XD0, + 0X12, + 0x81, 0x03, 0x01, 0x42, 0x00, + 0x82, 0x02, 0x81, 0x82, + 0x85, 0x07, 0x52, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65 //alpha id: "Receive" + ], + typeOfCommand: STK_CMD_RECEIVE_DATA, + icons: null, + testFunc: (context, cmdDetails, ctlvs) => { + let bipMsg = cmdDetails.options; + + equal(bipMsg.text, "Receive"); + } + }); + + // Send Data + test_stk_proactive_command({ + pdu: [ + 0xD0, + 0x0F, + 0x81, 0x03, 0x01, 0x43, 0x00, + 0x82, 0x02, 0x81, 0x82, + 0x85, 0x04, 0x53, 0x65, 0x6E, 0x64 //alpha id: "Send" + ], + typeOfCommand: STK_CMD_SEND_DATA, + icons: null, + testFunc: (context, cmdDetails, ctlvs) => { + let bipMsg = cmdDetails.options; + + equal(bipMsg.text, "Send"); + } + }); + + run_next_test(); +}); + +/** + * Verify Event Download Command : Location Status + */ +add_test(function test_stk_event_download_location_status() { + let worker = newUint8SupportOutgoingIndexWorker(); + let context = worker.ContextPool._contexts[0]; + let buf = context.Buf; + let pduHelper = context.GsmPDUHelper; + + buf.sendParcel = function() { + // Type + equal(this.readInt32(), REQUEST_STK_SEND_ENVELOPE_COMMAND); + + // Token : we don't care + this.readInt32(); + + // Data Size, 42 = 2 * (2 + TLV_DEVICE_ID_SIZE(4) + + // TLV_EVENT_LIST_SIZE(3) + + // TLV_LOCATION_STATUS_SIZE(3) + + // TLV_LOCATION_INFO_GSM_SIZE(9)) + equal(this.readInt32(), 42); + + // BER tag + equal(pduHelper.readHexOctet(), BER_EVENT_DOWNLOAD_TAG); + + // BER length, 19 = TLV_DEVICE_ID_SIZE(4) + + // TLV_EVENT_LIST_SIZE(3) + + // TLV_LOCATION_STATUS_SIZE(3) + + // TLV_LOCATION_INFO_GSM_SIZE(9) + equal(pduHelper.readHexOctet(), 19); + + // Event List, Type-Length-Value + equal(pduHelper.readHexOctet(), COMPREHENSIONTLV_TAG_EVENT_LIST | + COMPREHENSIONTLV_FLAG_CR); + equal(pduHelper.readHexOctet(), 1); + equal(pduHelper.readHexOctet(), STK_EVENT_TYPE_LOCATION_STATUS); + + // Device Identifies, Type-Length-Value(Source ID-Destination ID) + equal(pduHelper.readHexOctet(), COMPREHENSIONTLV_TAG_DEVICE_ID | + COMPREHENSIONTLV_FLAG_CR); + equal(pduHelper.readHexOctet(), 2); + equal(pduHelper.readHexOctet(), STK_DEVICE_ID_ME); + equal(pduHelper.readHexOctet(), STK_DEVICE_ID_SIM); + + // Location Status, Type-Length-Value + equal(pduHelper.readHexOctet(), COMPREHENSIONTLV_TAG_LOCATION_STATUS | + COMPREHENSIONTLV_FLAG_CR); + equal(pduHelper.readHexOctet(), 1); + equal(pduHelper.readHexOctet(), STK_SERVICE_STATE_NORMAL); + + // Location Info, Type-Length-Value + equal(pduHelper.readHexOctet(), COMPREHENSIONTLV_TAG_LOCATION_INFO | + COMPREHENSIONTLV_FLAG_CR); + equal(pduHelper.readHexOctet(), 7); + + equal(pduHelper.readHexOctet(), 0x21); // MCC + MNC + equal(pduHelper.readHexOctet(), 0x63); + equal(pduHelper.readHexOctet(), 0x54); + equal(pduHelper.readHexOctet(), 0); // LAC + equal(pduHelper.readHexOctet(), 0); + equal(pduHelper.readHexOctet(), 0); // Cell ID + equal(pduHelper.readHexOctet(), 0); + + run_next_test(); + }; + + let event = { + eventType: STK_EVENT_TYPE_LOCATION_STATUS, + locationStatus: STK_SERVICE_STATE_NORMAL, + locationInfo: { + mcc: "123", + mnc: "456", + gsmLocationAreaCode: 0, + gsmCellId: 0 + } + }; + context.RIL.sendStkEventDownload({ + event: event + }); +}); + +// Test Event Download commands. + +/** + * Verify Event Download Command : Language Selection + */ +add_test(function test_stk_event_download_language_selection() { + let worker = newUint8SupportOutgoingIndexWorker(); + let context = worker.ContextPool._contexts[0]; + let buf = context.Buf; + let pduHelper = context.GsmPDUHelper; + let iccHelper = context.ICCPDUHelper; + + buf.sendParcel = function() { + // Type + equal(this.readInt32(), REQUEST_STK_SEND_ENVELOPE_COMMAND); + + // Token : we don't care + this.readInt32(); + + // Data Size, 26 = 2 * (2 + TLV_DEVICE_ID_SIZE(4) + + // TLV_EVENT_LIST_SIZE(3) + + // TLV_LANGUAGE(4)) + equal(this.readInt32(), 26); + + // BER tag + equal(pduHelper.readHexOctet(), BER_EVENT_DOWNLOAD_TAG); + + // BER length, 19 = TLV_DEVICE_ID_SIZE(4) + + // TLV_EVENT_LIST_SIZE(3) + + // TLV_LANGUAGE(4) + equal(pduHelper.readHexOctet(), 11); + + // Event List, Type-Length-Value + equal(pduHelper.readHexOctet(), COMPREHENSIONTLV_TAG_EVENT_LIST | + COMPREHENSIONTLV_FLAG_CR); + equal(pduHelper.readHexOctet(), 1); + equal(pduHelper.readHexOctet(), STK_EVENT_TYPE_LANGUAGE_SELECTION); + + // Device Identifies, Type-Length-Value(Source ID-Destination ID) + equal(pduHelper.readHexOctet(), COMPREHENSIONTLV_TAG_DEVICE_ID | + COMPREHENSIONTLV_FLAG_CR); + equal(pduHelper.readHexOctet(), 2); + equal(pduHelper.readHexOctet(), STK_DEVICE_ID_ME); + equal(pduHelper.readHexOctet(), STK_DEVICE_ID_SIM); + + // Language, Type-Length-Value + equal(pduHelper.readHexOctet(), COMPREHENSIONTLV_TAG_LANGUAGE); + equal(pduHelper.readHexOctet(), 2); + equal(iccHelper.read8BitUnpackedToString(2), "zh"); + + run_next_test(); + }; + + let event = { + eventType: STK_EVENT_TYPE_LANGUAGE_SELECTION, + language: "zh" + }; + context.RIL.sendStkEventDownload({ + event: event + }); +}); + +/** + * Verify Event Download Command : User Activity + */ +add_test(function test_stk_event_download_user_activity() { + let worker = newUint8SupportOutgoingIndexWorker(); + let context = worker.ContextPool._contexts[0]; + let buf = context.Buf; + let pduHelper = context.GsmPDUHelper; + + buf.sendParcel = function() { + // Type + equal(this.readInt32(), REQUEST_STK_SEND_ENVELOPE_COMMAND); + + // Token : we don't care + this.readInt32(); + + // Data Size, 18 = 2 * (2 + TLV_DEVICE_ID_SIZE(4) + TLV_EVENT_LIST_SIZE(3)) + equal(this.readInt32(), 18); + + // BER tag + equal(pduHelper.readHexOctet(), BER_EVENT_DOWNLOAD_TAG); + + // BER length, 7 = TLV_DEVICE_ID_SIZE(4) + TLV_EVENT_LIST_SIZE(3) + equal(pduHelper.readHexOctet(), 7); + + // Event List, Type-Length-Value + equal(pduHelper.readHexOctet(), COMPREHENSIONTLV_TAG_EVENT_LIST | + COMPREHENSIONTLV_FLAG_CR); + equal(pduHelper.readHexOctet(), 1); + equal(pduHelper.readHexOctet(), STK_EVENT_TYPE_USER_ACTIVITY); + + // Device Identities, Type-Length-Value(Source ID-Destination ID) + equal(pduHelper.readHexOctet(), COMPREHENSIONTLV_TAG_DEVICE_ID | + COMPREHENSIONTLV_FLAG_CR); + equal(pduHelper.readHexOctet(), 2); + equal(pduHelper.readHexOctet(), STK_DEVICE_ID_ME); + equal(pduHelper.readHexOctet(), STK_DEVICE_ID_SIM); + + run_next_test(); + }; + + let event = { + eventType: STK_EVENT_TYPE_USER_ACTIVITY + }; + context.RIL.sendStkEventDownload({ + event: event + }); +}); + +/** + * Verify Event Download Command : Idle Screen Available + */ +add_test(function test_stk_event_download_idle_screen_available() { + let worker = newUint8SupportOutgoingIndexWorker(); + let context = worker.ContextPool._contexts[0]; + let buf = context.Buf; + let pduHelper = context.GsmPDUHelper; + + buf.sendParcel = function() { + // Type + equal(this.readInt32(), REQUEST_STK_SEND_ENVELOPE_COMMAND); + + // Token : we don't care + this.readInt32(); + + // Data Size, 18 = 2 * (2 + TLV_DEVICE_ID_SIZE(4) + TLV_EVENT_LIST_SIZE(3)) + equal(this.readInt32(), 18); + + // BER tag + equal(pduHelper.readHexOctet(), BER_EVENT_DOWNLOAD_TAG); + + // BER length, 7 = TLV_DEVICE_ID_SIZE(4) + TLV_EVENT_LIST_SIZE(3) + equal(pduHelper.readHexOctet(), 7); + + // Event List, Type-Length-Value + equal(pduHelper.readHexOctet(), COMPREHENSIONTLV_TAG_EVENT_LIST | + COMPREHENSIONTLV_FLAG_CR); + equal(pduHelper.readHexOctet(), 1); + equal(pduHelper.readHexOctet(), STK_EVENT_TYPE_IDLE_SCREEN_AVAILABLE); + + // Device Identities, Type-Length-Value(Source ID-Destination ID) + equal(pduHelper.readHexOctet(), COMPREHENSIONTLV_TAG_DEVICE_ID | + COMPREHENSIONTLV_FLAG_CR); + equal(pduHelper.readHexOctet(), 2); + equal(pduHelper.readHexOctet(), STK_DEVICE_ID_DISPLAY); + equal(pduHelper.readHexOctet(), STK_DEVICE_ID_SIM); + + run_next_test(); + }; + + let event = { + eventType: STK_EVENT_TYPE_IDLE_SCREEN_AVAILABLE + }; + context.RIL.sendStkEventDownload({ + event: event + }); +}); + +/** + * Verify Event Downloaded Command :Browser Termination + */ +add_test(function test_stk_event_download_browser_termination() { + let worker = newUint8SupportOutgoingIndexWorker(); + let context = worker.ContextPool._contexts[0]; + let buf = context.Buf; + let pduHelper = context.GsmPDUHelper; + + buf.sendParcel = function() { + // Type + equal(this.readInt32(), REQUEST_STK_SEND_ENVELOPE_COMMAND); + + // Token : we don't care + this.readInt32(); + + // Data Size, 24 = 2 * ( 2+TLV_DEVICE_ID(4)+TLV_EVENT_LIST_SIZE(3) + // +TLV_BROWSER_TERMINATION_CAUSE(3) ) + equal(this.readInt32(), 24); + + // BER tag + equal(pduHelper.readHexOctet(), BER_EVENT_DOWNLOAD_TAG); + + // BER length, 10 = TLV_DEVICE_ID(4)+TLV_EVENT_LIST_SIZE(3) + // ++TLV_BROWSER_TERMINATION_CAUSE(3) + equal(pduHelper.readHexOctet(), 10); + + // Event List, Type-Length-Value + equal(pduHelper.readHexOctet(), COMPREHENSIONTLV_TAG_EVENT_LIST | + COMPREHENSIONTLV_FLAG_CR); + equal(pduHelper.readHexOctet(), 1); + equal(pduHelper.readHexOctet(), STK_EVENT_TYPE_BROWSER_TERMINATION); + + // Device Identities, Type-Length-Value(Source ID-Destination ID) + equal(pduHelper.readHexOctet(), COMPREHENSIONTLV_TAG_DEVICE_ID | + COMPREHENSIONTLV_FLAG_CR); + equal(pduHelper.readHexOctet(), 2); + equal(pduHelper.readHexOctet(), STK_DEVICE_ID_ME); + equal(pduHelper.readHexOctet(), STK_DEVICE_ID_SIM); + + // Browser Termination Case, Type-Length-Value + equal(pduHelper.readHexOctet(), COMPREHENSIONTLV_TAG_BROWSER_TERMINATION_CAUSE | + COMPREHENSIONTLV_FLAG_CR); + equal(pduHelper.readHexOctet(), 1); + equal(pduHelper.readHexOctet(), STK_BROWSER_TERMINATION_CAUSE_USER); + + run_next_test(); + }; + + let event = { + eventType: STK_EVENT_TYPE_BROWSER_TERMINATION, + terminationCause: STK_BROWSER_TERMINATION_CAUSE_USER + }; + context.RIL.sendStkEventDownload({ + event: event + }); +}); diff --git a/dom/system/gonk/tests/test_ril_worker_voiceprivacy.js b/dom/system/gonk/tests/test_ril_worker_voiceprivacy.js new file mode 100644 index 000000000..21829da22 --- /dev/null +++ b/dom/system/gonk/tests/test_ril_worker_voiceprivacy.js @@ -0,0 +1,94 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +subscriptLoader.loadSubScript("resource://gre/modules/ril_consts.js", this); + +function run_test() { + run_next_test(); +} + +add_test(function test_setVoicePrivacyMode_success() { + let workerHelper = newInterceptWorker(); + let worker = workerHelper.worker; + let context = worker.ContextPool._contexts[0]; + + context.RIL.setVoicePrivacyMode = function fakeSetVoicePrivacyMode(options) { + context.RIL[REQUEST_CDMA_SET_PREFERRED_VOICE_PRIVACY_MODE](0, {}); + }; + + context.RIL.setVoicePrivacyMode({ + enabled: true + }); + + let postedMessage = workerHelper.postedMessage; + + equal(postedMessage.errorMsg, undefined); + + run_next_test(); +}); + +add_test(function test_setVoicePrivacyMode_generic_failure() { + let workerHelper = newInterceptWorker(); + let worker = workerHelper.worker; + let context = worker.ContextPool._contexts[0]; + + context.RIL.setVoicePrivacyMode = function fakeSetVoicePrivacyMode(options) { + context.RIL[REQUEST_CDMA_SET_PREFERRED_VOICE_PRIVACY_MODE](0, { + errorMsg: GECKO_ERROR_GENERIC_FAILURE + }); + }; + + context.RIL.setVoicePrivacyMode({ + enabled: true + }); + + let postedMessage = workerHelper.postedMessage; + + equal(postedMessage.errorMsg, GECKO_ERROR_GENERIC_FAILURE); + + run_next_test(); +}); + +add_test(function test_queryVoicePrivacyMode_success_enabled_true() { + let workerHelper = newInterceptWorker(); + let worker = workerHelper.worker; + let context = worker.ContextPool._contexts[0]; + + context.Buf.readInt32List = function fakeReadUint32List() { + return [1]; + }; + + context.RIL.queryVoicePrivacyMode = function fakeQueryVoicePrivacyMode(options) { + context.RIL[REQUEST_CDMA_QUERY_PREFERRED_VOICE_PRIVACY_MODE](1, {}); + }; + + context.RIL.queryVoicePrivacyMode(); + + let postedMessage = workerHelper.postedMessage; + + equal(postedMessage.errorMsg, undefined); + ok(postedMessage.enabled); + run_next_test(); +}); + +add_test(function test_queryVoicePrivacyMode_success_enabled_false() { + let workerHelper = newInterceptWorker(); + let worker = workerHelper.worker; + let context = worker.ContextPool._contexts[0]; + + context.Buf.readInt32List = function fakeReadUint32List() { + return [0]; + }; + + context.RIL.queryVoicePrivacyMode = function fakeQueryVoicePrivacyMode(options) { + context.RIL[REQUEST_CDMA_QUERY_PREFERRED_VOICE_PRIVACY_MODE](1, {}); + }; + + context.RIL.queryVoicePrivacyMode(); + + let postedMessage = workerHelper.postedMessage; + + equal(postedMessage.errorMsg, undefined); + ok(!postedMessage.enabled); + run_next_test(); +}); diff --git a/dom/system/gonk/tests/xpcshell.ini b/dom/system/gonk/tests/xpcshell.ini new file mode 100644 index 000000000..1e8b798a3 --- /dev/null +++ b/dom/system/gonk/tests/xpcshell.ini @@ -0,0 +1,43 @@ +[DEFAULT] +head = header_helpers.js +tail = + +[test_ril_worker_buf.js] +[test_ril_worker_icc_CardLock.js] +[test_ril_worker_icc_CardState.js] +[test_ril_worker_icc_BerTlvHelper.js] +[test_ril_worker_icc_GsmPDUHelper.js] +[test_ril_worker_icc_ICCContactHelper.js] +[test_ril_worker_icc_ICCIOHelper.js] +[test_ril_worker_icc_ICCPDUHelper.js] +[test_ril_worker_icc_ICCRecordHelper.js] +[test_ril_worker_icc_IconLoader.js] +[test_ril_worker_icc_ICCUtilsHelper.js] +[test_ril_worker_icc_SimRecordHelper.js] +[test_ril_worker_sms.js] +# Bug 916067 - B2G RIL: test_ril_worker_sms.js takes too long to finish +skip-if = true +[test_ril_worker_sms_cdma.js] +[test_ril_worker_sms_cdmapduhelper.js] +[test_ril_worker_sms_nl_tables.js] +[test_ril_worker_sms_gsmpduhelper.js] +[test_ril_worker_sms_segment_info.js] +[test_ril_worker_smsc_address.js] +[test_ril_worker_cf.js] +[test_ril_worker_cellbroadcast_config.js] +[test_ril_worker_cellbroadcast_gsm.js] +[test_ril_worker_cellbroadcast_umts.js] +[test_ril_worker_ruim.js] +[test_ril_worker_cw.js] +[test_ril_worker_clir.js] +[test_ril_worker_clip.js] +[test_ril_worker_ssn.js] +[test_ril_worker_voiceprivacy.js] +[test_ril_worker_ecm.js] +[test_ril_worker_stk.js] +requesttimeoutfactor = 4 +[test_ril_worker_barring_password.js] +[test_ril_worker_cdma_info_rec.js] +[test_ril_system_messenger.js] +# header_helpers.js is not needed for test_ril_system_messenger.js +head = |