summaryrefslogtreecommitdiffstats
path: root/dom/system/gonk/ril_worker.js
diff options
context:
space:
mode:
Diffstat (limited to 'dom/system/gonk/ril_worker.js')
-rw-r--r--dom/system/gonk/ril_worker.js15206
1 files changed, 15206 insertions, 0 deletions
diff --git a/dom/system/gonk/ril_worker.js b/dom/system/gonk/ril_worker.js
new file mode 100644
index 000000000..0608a5be3
--- /dev/null
+++ b/dom/system/gonk/ril_worker.js
@@ -0,0 +1,15206 @@
+/* Copyright 2012 Mozilla Foundation and Mozilla contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * This file implements the RIL worker thread. It communicates with
+ * the main thread to provide a high-level API to the phone's RIL
+ * stack, and with the RIL IPC thread to communicate with the RIL
+ * device itself. These communication channels use message events as
+ * known from Web Workers:
+ *
+ * - postMessage()/"message" events for main thread communication
+ *
+ * - postRILMessage()/"RILMessageEvent" events for RIL IPC thread
+ * communication.
+ *
+ * The two main objects in this file represent individual parts of this
+ * communication chain:
+ *
+ * - RILMessageEvent -> Buf -> RIL -> postMessage() -> nsIRadioInterfaceLayer
+ * - nsIRadioInterfaceLayer -> postMessage() -> RIL -> Buf -> postRILMessage()
+ *
+ * Note: The code below is purposely lean on abstractions to be as lean in
+ * terms of object allocations. As a result, it may look more like C than
+ * JavaScript, and that's intended.
+ */
+
+/* global BufObject */
+/* global TelephonyRequestQueue */
+
+"use strict";
+
+importScripts("ril_consts.js");
+importScripts("resource://gre/modules/workers/require.js");
+importScripts("ril_worker_buf_object.js");
+importScripts("ril_worker_telephony_request_queue.js");
+
+// set to true in ril_consts.js to see debug messages
+var DEBUG = DEBUG_WORKER;
+var GLOBAL = this;
+
+if (!this.debug) {
+ // Debugging stub that goes nowhere.
+ this.debug = function debug(message) {
+ dump("RIL Worker: " + message + "\n");
+ };
+}
+
+// Timeout value for emergency callback mode.
+const EMERGENCY_CB_MODE_TIMEOUT_MS = 300000; // 5 mins = 300000 ms.
+
+const ICC_MAX_LINEAR_FIXED_RECORDS = 0xfe;
+
+const GET_CURRENT_CALLS_RETRY_MAX = 3;
+
+var RILQUIRKS_CALLSTATE_EXTRA_UINT32;
+var RILQUIRKS_REQUEST_USE_DIAL_EMERGENCY_CALL;
+var RILQUIRKS_SIM_APP_STATE_EXTRA_FIELDS;
+var RILQUIRKS_SIGNAL_EXTRA_INT32;
+var RILQUIRKS_AVAILABLE_NETWORKS_EXTRA_STRING;
+// Needed for call-waiting on Peak device
+var RILQUIRKS_EXTRA_UINT32_2ND_CALL;
+// On the emulator we support querying the number of lock retries
+var RILQUIRKS_HAVE_QUERY_ICC_LOCK_RETRY_COUNT;
+// Ril quirk to Send STK Profile Download
+var RILQUIRKS_SEND_STK_PROFILE_DOWNLOAD;
+// Ril quirk to attach data registration on demand.
+var RILQUIRKS_DATA_REGISTRATION_ON_DEMAND;
+// Ril quirk to control the uicc/data subscription.
+var RILQUIRKS_SUBSCRIPTION_CONTROL;
+// Ril quirk to describe the SMSC address format.
+var RILQUIRKS_SMSC_ADDRESS_FORMAT;
+
+/**
+ * The RIL state machine.
+ *
+ * This object communicates with rild via parcels and with the main thread
+ * via post messages. It maintains state about the radio, ICC, calls, etc.
+ * and acts upon state changes accordingly.
+ */
+function RilObject(aContext) {
+ this.context = aContext;
+
+ this.telephonyRequestQueue = new TelephonyRequestQueue(this);
+ this.currentConferenceState = CALL_STATE_UNKNOWN;
+ this._pendingSentSmsMap = {};
+ this.pendingNetworkType = {};
+ this._receivedSmsCbPagesMap = {};
+ this._getCurrentCallsRetryCount = 0;
+}
+RilObject.prototype = {
+ context: null,
+
+ /**
+ * RIL version.
+ */
+ version: null,
+
+ /**
+ * Call state of current conference group.
+ */
+ currentConferenceState: null,
+
+ /**
+ * Outgoing messages waiting for SMS-STATUS-REPORT.
+ */
+ _pendingSentSmsMap: null,
+
+ /**
+ * Marker object.
+ */
+ pendingNetworkType: null,
+
+ /**
+ * Global Cell Broadcast switch.
+ */
+ cellBroadcastDisabled: false,
+
+ /**
+ * Parsed Cell Broadcast search lists.
+ * cellBroadcastConfigs.MMI should be preserved over rild reset.
+ */
+ cellBroadcastConfigs: null,
+ mergedCellBroadcastConfig: null,
+
+ _receivedSmsCbPagesMap: null,
+
+ initRILState: function() {
+ /**
+ * One of the RADIO_STATE_* constants.
+ */
+ this.radioState = GECKO_RADIOSTATE_UNKNOWN;
+
+ /**
+ * True if we are on a CDMA phone.
+ */
+ this._isCdma = false;
+
+ /**
+ * True if we are in emergency callback mode.
+ */
+ this._isInEmergencyCbMode = false;
+
+ /**
+ * Set when radio is ready but radio tech is unknown. That is, we are
+ * waiting for REQUEST_VOICE_RADIO_TECH
+ */
+ this._waitingRadioTech = false;
+
+ /**
+ * Card state
+ */
+ this.cardState = GECKO_CARDSTATE_UNINITIALIZED;
+
+ /**
+ * Device Identities including IMEI, IMEISV, ESN and MEID.
+ */
+ this.deviceIdentities = null;
+
+ /**
+ * ICC information that is not exposed to Gaia.
+ */
+ this.iccInfoPrivate = {};
+
+ /**
+ * ICC information, such as MSISDN, MCC, MNC, SPN...etc.
+ */
+ this.iccInfo = {};
+
+ /**
+ * CDMA specific information. ex. CDMA Network ID, CDMA System ID... etc.
+ */
+ this.cdmaHome = null;
+
+ /**
+ * Application identification for apps in ICC.
+ */
+ this.aid = null;
+
+ /**
+ * Application type for apps in ICC.
+ */
+ this.appType = null;
+
+ this.networkSelectionMode = GECKO_NETWORK_SELECTION_UNKNOWN;
+
+ this.voiceRegistrationState = {};
+ this.dataRegistrationState = {};
+
+ /**
+ * List of strings identifying the network operator.
+ */
+ this.operator = null;
+
+ /**
+ * String containing the baseband version.
+ */
+ this.basebandVersion = null;
+
+ // Clean up currentCalls: rild might have restarted.
+ this.sendChromeMessage({
+ rilMessageType: "currentCalls",
+ calls: {}
+ });
+
+ // Don't clean up this._pendingSentSmsMap
+ // because on rild restart: we may continue with the pending segments.
+
+ /**
+ * Whether or not the multiple requests in requestNetworkInfo() are currently
+ * being processed
+ */
+ this._processingNetworkInfo = false;
+
+ /**
+ * Multiple requestNetworkInfo() in a row before finishing the first
+ * request, hence we need to fire requestNetworkInfo() again after
+ * gathering all necessary stuffs. This is to make sure that ril_worker
+ * gets precise network information.
+ */
+ this._needRepollNetworkInfo = false;
+
+ /**
+ * Pending messages to be send in batch from requestNetworkInfo()
+ */
+ this._pendingNetworkInfo = {rilMessageType: "networkinfochanged"};
+
+ /**
+ * Cell Broadcast Search Lists.
+ */
+ let cbmmi = this.cellBroadcastConfigs && this.cellBroadcastConfigs.MMI;
+ this.cellBroadcastConfigs = {
+ MMI: cbmmi || null
+ };
+ this.mergedCellBroadcastConfig = null;
+
+ /**
+ * True when the request to report SMS Memory Status is pending.
+ */
+ this.pendingToReportSmsMemoryStatus = false;
+ this.smsStorageAvailable = true;
+ },
+
+ /**
+ * Parse an integer from a string, falling back to a default value
+ * if the the provided value is not a string or does not contain a valid
+ * number.
+ *
+ * @param string
+ * String to be parsed.
+ * @param defaultValue [optional]
+ * Default value to be used.
+ * @param radix [optional]
+ * A number that represents the numeral system to be used. Default 10.
+ */
+ parseInt: function(string, defaultValue, radix) {
+ let number = parseInt(string, radix || 10);
+ if (!isNaN(number)) {
+ return number;
+ }
+ if (defaultValue === undefined) {
+ defaultValue = null;
+ }
+ return defaultValue;
+ },
+
+
+ /**
+ * Outgoing requests to the RIL. These can be triggered from the
+ * main thread via messages that look like this:
+ *
+ * {rilMessageType: "methodName",
+ * extra: "parameters",
+ * go: "here"}
+ *
+ * So if one of the following methods takes arguments, it takes only one,
+ * an object, which then contains all of the parameters as attributes.
+ * The "@param" documentation is to be interpreted accordingly.
+ */
+
+ /**
+ * Retrieve the ICC's status.
+ */
+ getICCStatus: function() {
+ this.context.Buf.simpleRequest(REQUEST_GET_SIM_STATUS);
+ },
+
+ /**
+ * Helper function for unlocking ICC locks.
+ */
+ iccUnlockCardLock: function(options) {
+ switch (options.lockType) {
+ case GECKO_CARDLOCK_PIN:
+ this.enterICCPIN(options);
+ break;
+ case GECKO_CARDLOCK_PIN2:
+ this.enterICCPIN2(options);
+ break;
+ case GECKO_CARDLOCK_PUK:
+ this.enterICCPUK(options);
+ break;
+ case GECKO_CARDLOCK_PUK2:
+ this.enterICCPUK2(options);
+ break;
+ case GECKO_CARDLOCK_NCK:
+ case GECKO_CARDLOCK_NSCK:
+ case GECKO_CARDLOCK_NCK1:
+ case GECKO_CARDLOCK_NCK2:
+ case GECKO_CARDLOCK_HNCK:
+ case GECKO_CARDLOCK_CCK:
+ case GECKO_CARDLOCK_SPCK:
+ case GECKO_CARDLOCK_PCK:
+ case GECKO_CARDLOCK_RCCK:
+ case GECKO_CARDLOCK_RSPCK:
+ case GECKO_CARDLOCK_NCK_PUK:
+ case GECKO_CARDLOCK_NSCK_PUK:
+ case GECKO_CARDLOCK_NCK1_PUK:
+ case GECKO_CARDLOCK_NCK2_PUK:
+ case GECKO_CARDLOCK_HNCK_PUK:
+ case GECKO_CARDLOCK_CCK_PUK:
+ case GECKO_CARDLOCK_SPCK_PUK:
+ case GECKO_CARDLOCK_PCK_PUK:
+ case GECKO_CARDLOCK_RCCK_PUK: // Fall through.
+ case GECKO_CARDLOCK_RSPCK_PUK:
+ this.enterDepersonalization(options);
+ break;
+ default:
+ options.errorMsg = GECKO_ERROR_REQUEST_NOT_SUPPORTED;
+ this.sendChromeMessage(options);
+ }
+ },
+
+ /**
+ * Enter a PIN to unlock the ICC.
+ *
+ * @param password
+ * String containing the PIN.
+ * @param [optional] aid
+ * AID value.
+ */
+ enterICCPIN: function(options) {
+ let Buf = this.context.Buf;
+ Buf.newParcel(REQUEST_ENTER_SIM_PIN, options);
+ Buf.writeInt32(2);
+ Buf.writeString(options.password);
+ Buf.writeString(options.aid || this.aid);
+ Buf.sendParcel();
+ },
+
+ /**
+ * Enter a PIN2 to unlock the ICC.
+ *
+ * @param password
+ * String containing the PIN2.
+ * @param [optional] aid
+ * AID value.
+ */
+ enterICCPIN2: function(options) {
+ let Buf = this.context.Buf;
+ Buf.newParcel(REQUEST_ENTER_SIM_PIN2, options);
+ Buf.writeInt32(2);
+ Buf.writeString(options.password);
+ Buf.writeString(options.aid || this.aid);
+ Buf.sendParcel();
+ },
+
+ /**
+ * Requests a network personalization be deactivated.
+ *
+ * @param personlization
+ * One of CARD_PERSOSUBSTATE_*
+ * @param password
+ * String containing the password.
+ */
+ enterDepersonalization: function(options) {
+ let Buf = this.context.Buf;
+ Buf.newParcel(REQUEST_ENTER_NETWORK_DEPERSONALIZATION_CODE, options);
+ Buf.writeInt32(1);
+ Buf.writeString(options.password);
+ Buf.sendParcel();
+ },
+
+ /**
+ * Change the current ICC PIN number.
+ *
+ * @param password
+ * String containing the old PIN value
+ * @param newPassword
+ * String containing the new PIN value
+ * @param [optional] aid
+ * AID value.
+ */
+ changeICCPIN: function(options) {
+ let Buf = this.context.Buf;
+ Buf.newParcel(REQUEST_CHANGE_SIM_PIN, options);
+ Buf.writeInt32(3);
+ Buf.writeString(options.password);
+ Buf.writeString(options.newPassword);
+ Buf.writeString(options.aid || this.aid);
+ Buf.sendParcel();
+ },
+
+ /**
+ * Change the current ICC PIN2 number.
+ *
+ * @param password
+ * String containing the old PIN2 value
+ * @param newPassword
+ * String containing the new PIN2 value
+ * @param [optional] aid
+ * AID value.
+ */
+ changeICCPIN2: function(options) {
+ let Buf = this.context.Buf;
+ Buf.newParcel(REQUEST_CHANGE_SIM_PIN2, options);
+ Buf.writeInt32(3);
+ Buf.writeString(options.password);
+ Buf.writeString(options.newPassword);
+ Buf.writeString(options.aid || this.aid);
+ Buf.sendParcel();
+ },
+
+ /**
+ * Supplies ICC PUK and a new PIN to unlock the ICC.
+ *
+ * @param password
+ * String containing the PUK value.
+ * @param newPassword
+ * String containing the new PIN value.
+ * @param [optional] aid
+ * AID value.
+ */
+ enterICCPUK: function(options) {
+ let Buf = this.context.Buf;
+ Buf.newParcel(REQUEST_ENTER_SIM_PUK, options);
+ Buf.writeInt32(3);
+ Buf.writeString(options.password);
+ Buf.writeString(options.newPin);
+ Buf.writeString(options.aid || this.aid);
+ Buf.sendParcel();
+ },
+
+ /**
+ * Supplies ICC PUK2 and a new PIN2 to unlock the ICC.
+ *
+ * @param password
+ * String containing the PUK2 value.
+ * @param newPassword
+ * String containing the new PIN2 value.
+ * @param [optional] aid
+ * AID value.
+ */
+ enterICCPUK2: function(options) {
+ let Buf = this.context.Buf;
+ Buf.newParcel(REQUEST_ENTER_SIM_PUK2, options);
+ Buf.writeInt32(3);
+ Buf.writeString(options.password);
+ Buf.writeString(options.newPin);
+ Buf.writeString(options.aid || this.aid);
+ Buf.sendParcel();
+ },
+
+ /**
+ * Helper function for changing ICC locks.
+ */
+ iccChangeCardLockPassword: function(options) {
+ switch (options.lockType) {
+ case GECKO_CARDLOCK_PIN:
+ this.changeICCPIN(options);
+ break;
+ case GECKO_CARDLOCK_PIN2:
+ this.changeICCPIN2(options);
+ break;
+ default:
+ options.errorMsg = GECKO_ERROR_REQUEST_NOT_SUPPORTED;
+ this.sendChromeMessage(options);
+ }
+ },
+
+ /**
+ * Helper function for setting the state of ICC locks.
+ */
+ iccSetCardLockEnabled: function(options) {
+ switch (options.lockType) {
+ case GECKO_CARDLOCK_PIN: // Fall through.
+ case GECKO_CARDLOCK_FDN:
+ options.facility = GECKO_CARDLOCK_TO_FACILITY[options.lockType];
+ break;
+ default:
+ options.errorMsg = GECKO_ERROR_REQUEST_NOT_SUPPORTED;
+ this.sendChromeMessage(options);
+ return;
+ }
+
+ options.serviceClass = ICC_SERVICE_CLASS_VOICE |
+ ICC_SERVICE_CLASS_DATA |
+ ICC_SERVICE_CLASS_FAX;
+ this.setICCFacilityLock(options);
+ },
+
+ /**
+ * Helper function for fetching the state of ICC locks.
+ */
+ iccGetCardLockEnabled: function(options) {
+ switch (options.lockType) {
+ case GECKO_CARDLOCK_PIN: // Fall through.
+ case GECKO_CARDLOCK_FDN:
+ options.facility = GECKO_CARDLOCK_TO_FACILITY[options.lockType];
+ break;
+ default:
+ options.errorMsg = GECKO_ERROR_REQUEST_NOT_SUPPORTED;
+ this.sendChromeMessage(options);
+ return;
+ }
+
+ options.password = ""; // For query no need to provide pin.
+ options.serviceClass = ICC_SERVICE_CLASS_VOICE |
+ ICC_SERVICE_CLASS_DATA |
+ ICC_SERVICE_CLASS_FAX;
+ this.queryICCFacilityLock(options);
+ },
+
+ /**
+ * Helper function for fetching the number of unlock retries of ICC locks.
+ *
+ * We only query the retry count when we're on the emulator. The phones do
+ * not support the request id and their rild doesn't return an error.
+ */
+ iccGetCardLockRetryCount: function(options) {
+ if (!RILQUIRKS_HAVE_QUERY_ICC_LOCK_RETRY_COUNT) {
+ // Only the emulator supports this request.
+ options.errorMsg = GECKO_ERROR_REQUEST_NOT_SUPPORTED;
+ this.sendChromeMessage(options);
+ return;
+ }
+
+ switch (options.lockType) {
+ case GECKO_CARDLOCK_PIN:
+ case GECKO_CARDLOCK_PIN2:
+ case GECKO_CARDLOCK_PUK:
+ case GECKO_CARDLOCK_PUK2:
+ case GECKO_CARDLOCK_NCK:
+ case GECKO_CARDLOCK_NSCK:
+ case GECKO_CARDLOCK_CCK: // Fall through.
+ case GECKO_CARDLOCK_SPCK:
+ // TODO: Bug 1116072: identify the mapping between RIL_PERSOSUBSTATE_SIM_SIM
+ // @ ril.h and TS 27.007, clause 8.65 for GECKO_CARDLOCK_SPCK.
+ options.selCode = GECKO_CARDLOCK_TO_SEL_CODE[options.lockType];
+ break;
+ default:
+ options.errorMsg = GECKO_ERROR_REQUEST_NOT_SUPPORTED;
+ this.sendChromeMessage(options);
+ return;
+ }
+
+ this.queryICCLockRetryCount(options);
+ },
+
+ /**
+ * Query ICC lock retry count.
+ *
+ * @param selCode
+ * One of ICC_SEL_CODE_*.
+ * @param serviceClass
+ * One of ICC_SERVICE_CLASS_*.
+ */
+ queryICCLockRetryCount: function(options) {
+ let Buf = this.context.Buf;
+ Buf.newParcel(REQUEST_GET_UNLOCK_RETRY_COUNT, options);
+ Buf.writeInt32(1);
+ Buf.writeString(options.selCode);
+ Buf.sendParcel();
+ },
+
+ /**
+ * Query ICC facility lock.
+ *
+ * @param facility
+ * One of ICC_CB_FACILITY_*.
+ * @param password
+ * Password for the facility, or "" if not required.
+ * @param serviceClass
+ * One of ICC_SERVICE_CLASS_*.
+ * @param [optional] aid
+ * AID value.
+ */
+ queryICCFacilityLock: function(options) {
+ let Buf = this.context.Buf;
+ Buf.newParcel(REQUEST_QUERY_FACILITY_LOCK, options);
+ Buf.writeInt32(4);
+ Buf.writeString(options.facility);
+ Buf.writeString(options.password);
+ Buf.writeString(options.serviceClass.toString());
+ Buf.writeString(options.aid || this.aid);
+ Buf.sendParcel();
+ },
+
+ /**
+ * Set ICC facility lock.
+ *
+ * @param facility
+ * One of ICC_CB_FACILITY_*.
+ * @param enabled
+ * true to enable, false to disable.
+ * @param password
+ * Password for the facility, or "" if not required.
+ * @param serviceClass
+ * One of ICC_SERVICE_CLASS_*.
+ * @param [optional] aid
+ * AID value.
+ */
+ setICCFacilityLock: function(options) {
+ let Buf = this.context.Buf;
+ Buf.newParcel(REQUEST_SET_FACILITY_LOCK, options);
+ Buf.writeInt32(5);
+ Buf.writeString(options.facility);
+ Buf.writeString(options.enabled ? "1" : "0");
+ Buf.writeString(options.password);
+ Buf.writeString(options.serviceClass.toString());
+ Buf.writeString(options.aid || this.aid);
+ Buf.sendParcel();
+ },
+
+ /**
+ * Request an ICC I/O operation.
+ *
+ * See TS 27.007 "restricted SIM" operation, "AT Command +CRSM".
+ * The sequence is in the same order as how libril reads this parcel,
+ * see the struct RIL_SIM_IO_v5 or RIL_SIM_IO_v6 defined in ril.h
+ *
+ * @param command
+ * The I/O command, one of the ICC_COMMAND_* constants.
+ * @param fileId
+ * The file to operate on, one of the ICC_EF_* constants.
+ * @param pathId
+ * String type, check the 'pathid' parameter from TS 27.007 +CRSM.
+ * @param p1, p2, p3
+ * Arbitrary integer parameters for the command.
+ * @param [optional] dataWriter
+ * The function for writing string parameter for the ICC_COMMAND_UPDATE_RECORD.
+ * @param [optional] pin2
+ * String containing the PIN2.
+ * @param [optional] aid
+ * AID value.
+ */
+ iccIO: function(options) {
+ let Buf = this.context.Buf;
+ Buf.newParcel(REQUEST_SIM_IO, options);
+ Buf.writeInt32(options.command);
+ Buf.writeInt32(options.fileId);
+ Buf.writeString(options.pathId);
+ Buf.writeInt32(options.p1);
+ Buf.writeInt32(options.p2);
+ Buf.writeInt32(options.p3);
+
+ // Write data.
+ if (options.command == ICC_COMMAND_UPDATE_RECORD &&
+ options.dataWriter) {
+ options.dataWriter(options.p3);
+ } else {
+ Buf.writeString(null);
+ }
+
+ // Write pin2.
+ if (options.command == ICC_COMMAND_UPDATE_RECORD &&
+ options.pin2) {
+ Buf.writeString(options.pin2);
+ } else {
+ Buf.writeString(null);
+ }
+
+ Buf.writeString(options.aid || this.aid);
+ Buf.sendParcel();
+ },
+
+ /**
+ * Get IMSI.
+ *
+ * @param [optional] aid
+ * AID value.
+ */
+ getIMSI: function(aid) {
+ let Buf = this.context.Buf;
+ Buf.newParcel(REQUEST_GET_IMSI);
+ Buf.writeInt32(1);
+ Buf.writeString(aid || this.aid);
+ Buf.sendParcel();
+ },
+
+ /**
+ * Retrieve ICC's GID1 field.
+ */
+ getGID1: function(options) {
+ options.gid1 = this.iccInfoPrivate.gid1;
+ this.sendChromeMessage(options);
+ },
+
+ /**
+ * Read UICC Phonebook contacts.
+ *
+ * @param contactType
+ * One of GECKO_CARDCONTACT_TYPE_*.
+ * @param requestId
+ * Request id from RadioInterfaceLayer.
+ */
+ readICCContacts: function(options) {
+ if (!this.appType) {
+ options.errorMsg = CONTACT_ERR_REQUEST_NOT_SUPPORTED;
+ this.sendChromeMessage(options);
+ return;
+ }
+
+ this.context.ICCContactHelper.readICCContacts(
+ this.appType,
+ options.contactType,
+ function onsuccess(contacts) {
+ for (let i = 0; i < contacts.length; i++) {
+ let contact = contacts[i];
+ let pbrIndex = contact.pbrIndex || 0;
+ let recordIndex = pbrIndex * ICC_MAX_LINEAR_FIXED_RECORDS + contact.recordId;
+ contact.contactId = this.iccInfo.iccid + recordIndex;
+ }
+ // Reuse 'options' to get 'requestId' and 'contactType'.
+ options.contacts = contacts;
+ this.sendChromeMessage(options);
+ }.bind(this),
+ function onerror(errorMsg) {
+ options.errorMsg = errorMsg;
+ this.sendChromeMessage(options);
+ }.bind(this));
+ },
+
+ /**
+ * Update UICC Phonebook.
+ *
+ * @param contactType One of GECKO_CARDCONTACT_TYPE_*.
+ * @param contact The contact will be updated.
+ * @param pin2 PIN2 is required for updating FDN.
+ * @param requestId Request id from RadioInterfaceLayer.
+ */
+ updateICCContact: function(options) {
+ let onsuccess = function onsuccess(updatedContact) {
+ let recordIndex =
+ updatedContact.pbrIndex * ICC_MAX_LINEAR_FIXED_RECORDS + updatedContact.recordId;
+ updatedContact.contactId = this.iccInfo.iccid + recordIndex;
+ options.contact = updatedContact;
+ // Reuse 'options' to get 'requestId' and 'contactType'.
+ this.sendChromeMessage(options);
+ }.bind(this);
+
+ let onerror = function onerror(errorMsg) {
+ options.errorMsg = errorMsg;
+ this.sendChromeMessage(options);
+ }.bind(this);
+
+ if (!this.appType || !options.contact) {
+ onerror(CONTACT_ERR_REQUEST_NOT_SUPPORTED );
+ return;
+ }
+
+ let contact = options.contact;
+ let iccid = this.iccInfo.iccid;
+ let isValidRecordId = false;
+ if (typeof contact.contactId === "string" &&
+ contact.contactId.startsWith(iccid)) {
+ let recordIndex = contact.contactId.substring(iccid.length);
+ contact.pbrIndex = Math.floor(recordIndex / ICC_MAX_LINEAR_FIXED_RECORDS);
+ contact.recordId = recordIndex % ICC_MAX_LINEAR_FIXED_RECORDS;
+ isValidRecordId = contact.recordId > 0 && contact.recordId < 0xff;
+ }
+
+ if (DEBUG) {
+ this.context.debug("Update ICC Contact " + JSON.stringify(contact));
+ }
+
+ let ICCContactHelper = this.context.ICCContactHelper;
+ // If contact has 'recordId' property, updates corresponding record.
+ // If not, inserts the contact into a free record.
+ if (isValidRecordId) {
+ ICCContactHelper.updateICCContact(
+ this.appType, options.contactType, contact, options.pin2, onsuccess, onerror);
+ } else {
+ ICCContactHelper.addICCContact(
+ this.appType, options.contactType, contact, options.pin2, onsuccess, onerror);
+ }
+ },
+
+ /**
+ * Check if operator name needs to be overriden by current voiceRegistrationState
+ * , EFOPL and EFPNN. See 3GPP TS 31.102 clause 4.2.58 EFPNN and 4.2.59 EFOPL
+ * for detail.
+ *
+ * @return true if operator name is overridden, false otherwise.
+ */
+ overrideICCNetworkName: function() {
+ if (!this.operator) {
+ return false;
+ }
+
+ // We won't get network name using PNN and OPL if voice registration isn't
+ // ready.
+ if (!this.voiceRegistrationState.cell ||
+ this.voiceRegistrationState.cell.gsmLocationAreaCode == -1) {
+ return false;
+ }
+
+ let ICCUtilsHelper = this.context.ICCUtilsHelper;
+ let networkName = ICCUtilsHelper.getNetworkNameFromICC(
+ this.operator.mcc,
+ this.operator.mnc,
+ this.voiceRegistrationState.cell.gsmLocationAreaCode);
+
+ if (!networkName) {
+ return false;
+ }
+
+ if (DEBUG) {
+ this.context.debug("Operator names will be overriden: " +
+ "longName = " + networkName.fullName + ", " +
+ "shortName = " + networkName.shortName);
+ }
+
+ this.operator.longName = networkName.fullName;
+ this.operator.shortName = networkName.shortName;
+
+ this._sendNetworkInfoMessage(NETWORK_INFO_OPERATOR, this.operator);
+ return true;
+ },
+
+ /**
+ * Request the phone's radio to be enabled or disabled.
+ *
+ * @param enabled
+ * Boolean indicating the desired state.
+ */
+ setRadioEnabled: function(options) {
+ let Buf = this.context.Buf;
+ Buf.newParcel(REQUEST_RADIO_POWER, options);
+ Buf.writeInt32(1);
+ Buf.writeInt32(options.enabled ? 1 : 0);
+ Buf.sendParcel();
+ },
+
+ /**
+ * Query call waiting status.
+ *
+ */
+ queryCallWaiting: function(options) {
+ let Buf = this.context.Buf;
+ Buf.newParcel(REQUEST_QUERY_CALL_WAITING, options);
+ Buf.writeInt32(1);
+ // As per 3GPP TS 24.083, section 1.6 UE doesn't need to send service
+ // class parameter in call waiting interrogation to network.
+ Buf.writeInt32(ICC_SERVICE_CLASS_NONE);
+ Buf.sendParcel();
+ },
+
+ /**
+ * Set call waiting status.
+ *
+ * @param enabled
+ * Boolean indicating the desired waiting status.
+ * @param serviceClass
+ * One of ICC_SERVICE_CLASS_* constants.
+ */
+ setCallWaiting: function(options) {
+ let Buf = this.context.Buf;
+ Buf.newParcel(REQUEST_SET_CALL_WAITING, options);
+ Buf.writeInt32(2);
+ Buf.writeInt32(options.enabled ? 1 : 0);
+ Buf.writeInt32(options.serviceClass);
+ Buf.sendParcel();
+ },
+
+ /**
+ * Queries current CLIP status.
+ */
+ queryCLIP: function(options) {
+ this.context.Buf.simpleRequest(REQUEST_QUERY_CLIP, options);
+ },
+
+ /**
+ * Queries current CLIR status.
+ *
+ */
+ getCLIR: function(options) {
+ this.context.Buf.simpleRequest(REQUEST_GET_CLIR, options);
+ },
+
+ /**
+ * Enables or disables the presentation of the calling line identity (CLI) to
+ * the called party when originating a call.
+ *
+ * @param options.clirMode
+ * One of the CLIR_* constants.
+ */
+ setCLIR: function(options) {
+ let Buf = this.context.Buf;
+ Buf.newParcel(REQUEST_SET_CLIR, options);
+ Buf.writeInt32(1);
+ Buf.writeInt32(options.clirMode);
+ Buf.sendParcel();
+ },
+
+ /**
+ * Set screen state.
+ *
+ * @param on
+ * Boolean indicating whether the screen should be on or off.
+ */
+ setScreenState: function(options) {
+ let Buf = this.context.Buf;
+ Buf.newParcel(REQUEST_SCREEN_STATE);
+ Buf.writeInt32(1);
+ Buf.writeInt32(options.on ? 1 : 0);
+ Buf.sendParcel();
+ },
+
+ getVoiceRegistrationState: function() {
+ this.context.Buf.simpleRequest(REQUEST_VOICE_REGISTRATION_STATE);
+ },
+
+ getVoiceRadioTechnology: function() {
+ this.context.Buf.simpleRequest(REQUEST_VOICE_RADIO_TECH);
+ },
+
+ getDataRegistrationState: function() {
+ this.context.Buf.simpleRequest(REQUEST_DATA_REGISTRATION_STATE);
+ },
+
+ getOperator: function() {
+ this.context.Buf.simpleRequest(REQUEST_OPERATOR);
+ },
+
+ /**
+ * Set the preferred network type.
+ *
+ * @param options An object contains a valid value of
+ * RIL_PREFERRED_NETWORK_TYPE_TO_GECKO as its `type` attribute.
+ */
+ setPreferredNetworkType: function(options) {
+ let networkType = options.type;
+ if (networkType < 0 || networkType >= RIL_PREFERRED_NETWORK_TYPE_TO_GECKO.length) {
+ options.errorMsg = GECKO_ERROR_INVALID_PARAMETER;
+ this.sendChromeMessage(options);
+ return;
+ }
+
+ let Buf = this.context.Buf;
+ Buf.newParcel(REQUEST_SET_PREFERRED_NETWORK_TYPE, options);
+ Buf.writeInt32(1);
+ Buf.writeInt32(networkType);
+ Buf.sendParcel();
+ },
+
+ /**
+ * Get the preferred network type.
+ */
+ getPreferredNetworkType: function(options) {
+ this.context.Buf.simpleRequest(REQUEST_GET_PREFERRED_NETWORK_TYPE, options);
+ },
+
+ /**
+ * Request neighboring cell ids in GSM network.
+ */
+ getNeighboringCellIds: function(options) {
+ this.context.Buf.simpleRequest(REQUEST_GET_NEIGHBORING_CELL_IDS, options);
+ },
+
+ /**
+ * Request all of the current cell information known to the radio.
+ */
+ getCellInfoList: function(options) {
+ this.context.Buf.simpleRequest(REQUEST_GET_CELL_INFO_LIST, options);
+ },
+
+ /**
+ * Request various states about the network.
+ */
+ requestNetworkInfo: function() {
+ if (this._processingNetworkInfo) {
+ if (DEBUG) {
+ this.context.debug("Network info requested, but we're already " +
+ "requesting network info.");
+ }
+ this._needRepollNetworkInfo = true;
+ return;
+ }
+
+ if (DEBUG) this.context.debug("Requesting network info");
+
+ this._processingNetworkInfo = true;
+ this.getVoiceRegistrationState();
+ this.getDataRegistrationState(); //TODO only GSM
+ this.getOperator();
+ this.getNetworkSelectionMode();
+ this.getSignalStrength();
+ },
+
+ /**
+ * Get the available networks
+ */
+ getAvailableNetworks: function(options) {
+ if (DEBUG) this.context.debug("Getting available networks");
+ let Buf = this.context.Buf;
+ Buf.newParcel(REQUEST_QUERY_AVAILABLE_NETWORKS, options);
+ Buf.sendParcel();
+ },
+
+ /**
+ * Request the radio's network selection mode
+ */
+ getNetworkSelectionMode: function() {
+ if (DEBUG) this.context.debug("Getting network selection mode");
+ this.context.Buf.simpleRequest(REQUEST_QUERY_NETWORK_SELECTION_MODE);
+ },
+
+ /**
+ * Tell the radio to automatically choose a voice/data network
+ */
+ selectNetworkAuto: function(options) {
+ if (DEBUG) this.context.debug("Setting automatic network selection");
+ this.context.Buf.simpleRequest(REQUEST_SET_NETWORK_SELECTION_AUTOMATIC, options);
+ },
+
+ /**
+ * Set the roaming preference mode
+ */
+ setRoamingPreference: function(options) {
+ let Buf = this.context.Buf;
+ Buf.newParcel(REQUEST_CDMA_SET_ROAMING_PREFERENCE, options);
+ Buf.writeInt32(1);
+ Buf.writeInt32(options.mode);
+ Buf.sendParcel();
+ },
+
+ /**
+ * Get the roaming preference mode
+ */
+ queryRoamingPreference: function(options) {
+ this.context.Buf.simpleRequest(REQUEST_CDMA_QUERY_ROAMING_PREFERENCE, options);
+ },
+
+ /**
+ * Set the voice privacy mode
+ */
+ setVoicePrivacyMode: function(options) {
+ let Buf = this.context.Buf;
+ Buf.newParcel(REQUEST_CDMA_SET_PREFERRED_VOICE_PRIVACY_MODE, options);
+ Buf.writeInt32(1);
+ Buf.writeInt32(options.enabled ? 1 : 0);
+ Buf.sendParcel();
+ },
+
+ /**
+ * Get the voice privacy mode
+ */
+ queryVoicePrivacyMode: function(options) {
+ this.context.Buf.simpleRequest(REQUEST_CDMA_QUERY_PREFERRED_VOICE_PRIVACY_MODE, options);
+ },
+
+ /**
+ * Open Logical UICC channel (aid) for Secure Element access
+ */
+ iccOpenChannel: function(options) {
+ let Buf = this.context.Buf;
+ Buf.newParcel(REQUEST_SIM_OPEN_CHANNEL, options);
+ Buf.writeString(options.aid);
+ Buf.sendParcel();
+ },
+
+ /**
+ * Exchange APDU data on an open Logical UICC channel
+ */
+ iccExchangeAPDU: function(options) {
+ let Buf = this.context.Buf;
+ Buf.newParcel(REQUEST_SIM_TRANSMIT_APDU_CHANNEL, options);
+ Buf.writeInt32(options.channel);
+ Buf.writeInt32(options.apdu.cla);
+ Buf.writeInt32(options.apdu.command);
+ Buf.writeInt32(options.apdu.p1);
+ Buf.writeInt32(options.apdu.p2);
+ Buf.writeInt32(options.apdu.p3);
+ Buf.writeString(options.apdu.data);
+ Buf.sendParcel();
+ },
+
+ /**
+ * Close Logical UICC channel
+ */
+ iccCloseChannel: function(options) {
+ let Buf = this.context.Buf;
+ Buf.newParcel(REQUEST_SIM_CLOSE_CHANNEL, options);
+ Buf.writeInt32(1);
+ Buf.writeInt32(options.channel);
+ Buf.sendParcel();
+ },
+
+ /**
+ * Get UICC service state
+ */
+ getIccServiceState: function(options) {
+ switch (options.service) {
+ case GECKO_CARDSERVICE_FDN:
+ let ICCUtilsHelper = this.context.ICCUtilsHelper;
+ options.result = ICCUtilsHelper.isICCServiceAvailable("FDN");
+ break;
+ default:
+ options.errorMsg = GECKO_ERROR_REQUEST_NOT_SUPPORTED;
+ break;
+ }
+ this.sendChromeMessage(options);
+ },
+
+ /**
+ * Enable/Disable UICC subscription
+ */
+ setUiccSubscription: function(options) {
+ if (DEBUG) {
+ this.context.debug("setUiccSubscription: " + JSON.stringify(options));
+ }
+
+ let Buf = this.context.Buf;
+ Buf.newParcel(REQUEST_SET_UICC_SUBSCRIPTION, options);
+ Buf.writeInt32(this.context.clientId);
+ Buf.writeInt32(options.appIndex);
+ Buf.writeInt32(this.context.clientId);
+ Buf.writeInt32(options.enabled ? 1 : 0);
+ Buf.sendParcel();
+ },
+
+ /**
+ * Tell the radio to choose a specific voice/data network
+ */
+ selectNetwork: function(options) {
+ if (DEBUG) {
+ this.context.debug("Setting manual network selection: " +
+ options.mcc + ", " + options.mnc);
+ }
+
+ let numeric = (options.mcc && options.mnc) ? options.mcc + options.mnc : null;
+ let Buf = this.context.Buf;
+ Buf.newParcel(REQUEST_SET_NETWORK_SELECTION_MANUAL, options);
+ Buf.writeString(numeric);
+ Buf.sendParcel();
+ },
+
+ /**
+ * Get the signal strength.
+ */
+ getSignalStrength: function() {
+ this.context.Buf.simpleRequest(REQUEST_SIGNAL_STRENGTH);
+ },
+
+ getDeviceIdentity: function() {
+ this.deviceIdentities || this.context.Buf.simpleRequest(REQUEST_DEVICE_IDENTITY);
+ },
+
+ getBasebandVersion: function() {
+ this.context.Buf.simpleRequest(REQUEST_BASEBAND_VERSION);
+ },
+
+ sendExitEmergencyCbModeRequest: function(options) {
+ this.context.Buf.simpleRequest(REQUEST_EXIT_EMERGENCY_CALLBACK_MODE, options);
+ },
+
+ getCdmaSubscription: function() {
+ this.context.Buf.simpleRequest(REQUEST_CDMA_SUBSCRIPTION);
+ },
+
+ exitEmergencyCbMode: function(options) {
+ // The function could be called by an API from RadioInterfaceLayer or by
+ // ril_worker itself. From ril_worker, we won't pass the parameter
+ // 'options'. In this case, it is marked as internal.
+ if (!options) {
+ options = {internal: true};
+ }
+ this._cancelEmergencyCbModeTimeout();
+ this.sendExitEmergencyCbModeRequest(options);
+ },
+
+ /**
+ * Dial a non-emergency number.
+ *
+ * @param isEmergency
+ * Whether the number is an emergency number.
+ * @param number
+ * String containing the number to dial.
+ * @param clirMode
+ * Integer for showing/hidding the caller Id to the called party.
+ * @param uusInfo
+ * Integer doing something XXX TODO
+ */
+ dial: function(options) {
+ if (options.isEmergency) {
+ options.request = RILQUIRKS_REQUEST_USE_DIAL_EMERGENCY_CALL ?
+ REQUEST_DIAL_EMERGENCY_CALL : REQUEST_DIAL;
+
+ } else {
+ options.request = REQUEST_DIAL;
+
+ // Exit emergency callback mode when user dial a non-emergency call.
+ if (this._isInEmergencyCbMode) {
+ this.exitEmergencyCbMode();
+ }
+ }
+
+ this.telephonyRequestQueue.push(options.request, () => {
+ let Buf = this.context.Buf;
+ Buf.newParcel(options.request, options);
+ Buf.writeString(options.number);
+ Buf.writeInt32(options.clirMode || 0);
+ Buf.writeInt32(options.uusInfo || 0);
+ // TODO Why do we need this extra 0? It was put it in to make this
+ // match the format of the binary message.
+ Buf.writeInt32(0);
+ Buf.sendParcel();
+ });
+ },
+
+ /**
+ * CDMA flash.
+ *
+ * @param featureStr (optional)
+ * Dialing number when the command is used for three-way-calling
+ */
+ cdmaFlash: function(options) {
+ let Buf = this.context.Buf;
+ options.request = REQUEST_CDMA_FLASH;
+ Buf.newParcel(options.request, options);
+ Buf.writeString(options.featureStr || "");
+ Buf.sendParcel();
+ },
+
+ /**
+ * Hang up the phone.
+ *
+ * @param callIndex
+ * Call index (1-based) as reported by REQUEST_GET_CURRENT_CALLS.
+ */
+ hangUpCall: function(options) {
+ this.telephonyRequestQueue.push(REQUEST_HANGUP, () => {
+ let Buf = this.context.Buf;
+ Buf.newParcel(REQUEST_HANGUP, options);
+ Buf.writeInt32(1);
+ Buf.writeInt32(options.callIndex);
+ Buf.sendParcel();
+ });
+ },
+
+ hangUpForeground: function(options) {
+ this.telephonyRequestQueue.push(REQUEST_HANGUP_FOREGROUND_RESUME_BACKGROUND, () => {
+ this.context.Buf.simpleRequest(REQUEST_HANGUP_FOREGROUND_RESUME_BACKGROUND,
+ options);
+ });
+ },
+
+ hangUpBackground: function(options) {
+ this.telephonyRequestQueue.push(REQUEST_HANGUP_WAITING_OR_BACKGROUND, () => {
+ this.context.Buf.simpleRequest(REQUEST_HANGUP_WAITING_OR_BACKGROUND,
+ options);
+ });
+ },
+
+ switchActiveCall: function(options) {
+ this.telephonyRequestQueue.push(REQUEST_SWITCH_WAITING_OR_HOLDING_AND_ACTIVE, () => {
+ this.context.Buf.simpleRequest(REQUEST_SWITCH_WAITING_OR_HOLDING_AND_ACTIVE,
+ options);
+ });
+ },
+
+ udub: function(options) {
+ this.telephonyRequestQueue.push(REQUEST_UDUB, () => {
+ this.context.Buf.simpleRequest(REQUEST_UDUB, options);
+ });
+ },
+
+ answerCall: function(options) {
+ this.telephonyRequestQueue.push(REQUEST_ANSWER, () => {
+ this.context.Buf.simpleRequest(REQUEST_ANSWER, options);
+ });
+ },
+
+ conferenceCall: function(options) {
+ this.telephonyRequestQueue.push(REQUEST_CONFERENCE, () => {
+ this.context.Buf.simpleRequest(REQUEST_CONFERENCE, options);
+ });
+ },
+
+ separateCall: function(options) {
+ this.telephonyRequestQueue.push(REQUEST_SEPARATE_CONNECTION, () => {
+ let Buf = this.context.Buf;
+ Buf.newParcel(REQUEST_SEPARATE_CONNECTION, options);
+ Buf.writeInt32(1);
+ Buf.writeInt32(options.callIndex);
+ Buf.sendParcel();
+ });
+ },
+
+ /**
+ * Get current calls.
+ */
+ getCurrentCalls: function(options) {
+ this.telephonyRequestQueue.push(REQUEST_GET_CURRENT_CALLS, () => {
+ this.context.Buf.simpleRequest(REQUEST_GET_CURRENT_CALLS, options);
+ });
+ },
+
+ /**
+ * Mute or unmute the radio.
+ *
+ * @param mute
+ * Boolean to indicate whether to mute or unmute the radio.
+ */
+ setMute: function(options) {
+ let Buf = this.context.Buf;
+ Buf.newParcel(REQUEST_SET_MUTE);
+ Buf.writeInt32(1);
+ Buf.writeInt32(options.muted ? 1 : 0);
+ Buf.sendParcel();
+ },
+
+ /**
+ * Send an SMS.
+ *
+ * The `options` parameter object should contain the following attributes:
+ *
+ * @param number
+ * String containing the recipient number.
+ * @param body
+ * String containing the message text.
+ * @param envelopeId
+ * Numeric value identifying the sms request.
+ */
+ sendSMS: function(options) {
+ options.langIndex = options.langIndex || PDU_NL_IDENTIFIER_DEFAULT;
+ options.langShiftIndex = options.langShiftIndex || PDU_NL_IDENTIFIER_DEFAULT;
+
+ if (!options.segmentSeq) {
+ // Fist segment to send
+ options.segmentSeq = 1;
+ options.body = options.segments[0].body;
+ options.encodedBodyLength = options.segments[0].encodedBodyLength;
+ }
+
+ let Buf = this.context.Buf;
+ if (this._isCdma) {
+ Buf.newParcel(REQUEST_CDMA_SEND_SMS, options);
+ this.context.CdmaPDUHelper.writeMessage(options);
+ } else {
+ Buf.newParcel(REQUEST_SEND_SMS, options);
+ Buf.writeInt32(2);
+ Buf.writeString(options.SMSC);
+ this.context.GsmPDUHelper.writeMessage(options);
+ }
+ Buf.sendParcel();
+ },
+
+ /**
+ * Acknowledge the receipt and handling of an SMS.
+ *
+ * @param success
+ * Boolean indicating whether the message was successfuly handled.
+ * @param cause
+ * SMS_* constant indicating the reason for unsuccessful handling.
+ */
+ acknowledgeGsmSms: function(success, cause) {
+ let Buf = this.context.Buf;
+ Buf.newParcel(REQUEST_SMS_ACKNOWLEDGE);
+ Buf.writeInt32(2);
+ Buf.writeInt32(success ? 1 : 0);
+ Buf.writeInt32(cause);
+ Buf.sendParcel();
+ },
+
+ /**
+ * Acknowledge the receipt and handling of an SMS.
+ *
+ * @param success
+ * Boolean indicating whether the message was successfuly handled.
+ */
+ ackSMS: function(options) {
+ if (options.result == PDU_FCS_RESERVED) {
+ return;
+ }
+ if (this._isCdma) {
+ this.acknowledgeCdmaSms(options.result == PDU_FCS_OK, options.result);
+ } else {
+ this.acknowledgeGsmSms(options.result == PDU_FCS_OK, options.result);
+ }
+ },
+
+ /**
+ * Acknowledge the receipt and handling of a CDMA SMS.
+ *
+ * @param success
+ * Boolean indicating whether the message was successfuly handled.
+ * @param cause
+ * SMS_* constant indicating the reason for unsuccessful handling.
+ */
+ acknowledgeCdmaSms: function(success, cause) {
+ let Buf = this.context.Buf;
+ Buf.newParcel(REQUEST_CDMA_SMS_ACKNOWLEDGE);
+ Buf.writeInt32(success ? 0 : 1);
+ Buf.writeInt32(cause);
+ Buf.sendParcel();
+ },
+
+ /**
+ * Update received MWI into EF_MWIS.
+ */
+ updateMwis: function(options) {
+ if (this.context.ICCUtilsHelper.isICCServiceAvailable("MWIS")) {
+ this.context.SimRecordHelper.updateMWIS(options.mwi);
+ }
+ },
+
+ /**
+ * Report SMS storage status to modem.
+ */
+ _updateSmsMemoryStatus: function() {
+ let Buf = this.context.Buf;
+ Buf.newParcel(REQUEST_REPORT_SMS_MEMORY_STATUS);
+ Buf.writeInt32(1);
+ Buf.writeInt32(this.smsStorageAvailable ? 1 : 0);
+ Buf.sendParcel();
+ },
+
+ reportSmsMemoryStatus: function(options) {
+ this.pendingToReportSmsMemoryStatus = true;
+ this.smsStorageAvailable = options.isAvailable;
+ this._updateSmsMemoryStatus();
+ },
+
+ setCellBroadcastDisabled: function(options) {
+ this.cellBroadcastDisabled = options.disabled;
+
+ // If |this.mergedCellBroadcastConfig| is null, either we haven't finished
+ // reading required SIM files, or no any channel is ever configured. In
+ // the former case, we'll call |this.updateCellBroadcastConfig()| later
+ // with correct configs; in the latter case, we don't bother resetting CB
+ // to disabled again.
+ if (this.mergedCellBroadcastConfig) {
+ this.updateCellBroadcastConfig();
+ }
+ },
+
+ setCellBroadcastSearchList: function(options) {
+ let getSearchListStr = function(aSearchList) {
+ if (typeof aSearchList === "string" || aSearchList instanceof String) {
+ return aSearchList;
+ }
+
+ // TODO: Set search list for CDMA/GSM individually. Bug 990926
+ let prop = this._isCdma ? "cdma" : "gsm";
+
+ return aSearchList && aSearchList[prop];
+ }.bind(this);
+
+ try {
+ let str = getSearchListStr(options.searchList);
+ this.cellBroadcastConfigs.MMI = this._convertCellBroadcastSearchList(str);
+ } catch (e) {
+ if (DEBUG) {
+ this.context.debug("Invalid Cell Broadcast search list: " + e);
+ }
+ options.errorMsg = GECKO_ERROR_UNSPECIFIED_ERROR;
+ }
+
+ this.sendChromeMessage(options);
+ if (options.errorMsg) {
+ return;
+ }
+
+ this._mergeAllCellBroadcastConfigs();
+ },
+
+ updateCellBroadcastConfig: function() {
+ let activate = !this.cellBroadcastDisabled &&
+ (this.mergedCellBroadcastConfig != null) &&
+ (this.mergedCellBroadcastConfig.length > 0);
+ if (activate) {
+ this.setSmsBroadcastConfig(this.mergedCellBroadcastConfig);
+ } else {
+ // It's unnecessary to set config first if we're deactivating.
+ this.setSmsBroadcastActivation(false);
+ }
+ },
+
+ setGsmSmsBroadcastConfig: function(config) {
+ let Buf = this.context.Buf;
+ Buf.newParcel(REQUEST_GSM_SET_BROADCAST_SMS_CONFIG);
+
+ let numConfigs = config ? config.length / 2 : 0;
+ Buf.writeInt32(numConfigs);
+ for (let i = 0; i < config.length;) {
+ // convert [from, to) to [from, to - 1]
+ Buf.writeInt32(config[i++]);
+ Buf.writeInt32(config[i++] - 1);
+ Buf.writeInt32(0x00);
+ Buf.writeInt32(0xFF);
+ Buf.writeInt32(1);
+ }
+
+ Buf.sendParcel();
+ },
+
+ /**
+ * Send CDMA SMS broadcast config.
+ *
+ * @see 3GPP2 C.R1001 Sec. 9.2 and 9.3
+ */
+ setCdmaSmsBroadcastConfig: function(config) {
+ let Buf = this.context.Buf;
+ // |config| is an array of half-closed range: [[from, to), [from, to), ...].
+ // It will be further decomposed, ex: [1, 4) => 1, 2, 3.
+ Buf.newParcel(REQUEST_CDMA_SET_BROADCAST_SMS_CONFIG);
+
+ let numConfigs = 0;
+ for (let i = 0; i < config.length; i += 2) {
+ numConfigs += (config[i+1] - config[i]);
+ }
+
+ Buf.writeInt32(numConfigs);
+ for (let i = 0; i < config.length;) {
+ let begin = config[i++];
+ let end = config[i++];
+
+ for (let j = begin; j < end; ++j) {
+ Buf.writeInt32(j);
+ Buf.writeInt32(0); // Language Indicator: Unknown or unspecified.
+ Buf.writeInt32(1);
+ }
+ }
+
+ Buf.sendParcel();
+ },
+
+ setSmsBroadcastConfig: function(config) {
+ if (this._isCdma) {
+ this.setCdmaSmsBroadcastConfig(config);
+ } else {
+ this.setGsmSmsBroadcastConfig(config);
+ }
+ },
+
+ setSmsBroadcastActivation: function(activate) {
+ let parcelType = this._isCdma ? REQUEST_CDMA_SMS_BROADCAST_ACTIVATION :
+ REQUEST_GSM_SMS_BROADCAST_ACTIVATION;
+ let Buf = this.context.Buf;
+ Buf.newParcel(parcelType);
+ Buf.writeInt32(1);
+ // See hardware/ril/include/telephony/ril.h, 0 - Activate, 1 - Turn off.
+ Buf.writeInt32(activate ? 0 : 1);
+ Buf.sendParcel();
+ },
+
+ /**
+ * Start a DTMF Tone.
+ *
+ * @param dtmfChar
+ * DTMF signal to send, 0-9, *, +
+ */
+ startTone: function(options) {
+ let Buf = this.context.Buf;
+ Buf.newParcel(REQUEST_DTMF_START, options);
+ Buf.writeString(options.dtmfChar);
+ Buf.sendParcel();
+ },
+
+ stopTone: function() {
+ this.context.Buf.simpleRequest(REQUEST_DTMF_STOP);
+ },
+
+ /**
+ * Send a DTMF tone.
+ *
+ * @param dtmfChar
+ * DTMF signal to send, 0-9, *, +
+ */
+ sendTone: function(options) {
+ let Buf = this.context.Buf;
+ Buf.newParcel(REQUEST_DTMF);
+ Buf.writeString(options.dtmfChar);
+ Buf.sendParcel();
+ },
+
+ /**
+ * Get the Short Message Service Center address.
+ */
+ getSmscAddress: function(options) {
+ this.context.Buf.simpleRequest(REQUEST_GET_SMSC_ADDRESS, options);
+ },
+
+ /**
+ * Set the Short Message Service Center address.
+ *
+ * @param smscAddress
+ * Number part of the SMSC address.
+ * @param typeOfNumber
+ * Type of number in integer, as defined in
+ * |Table 10.5.118: Called party BCD number| of 3GPP TS 24.008.
+ * @param numberPlanIdentification
+ * The index of number plan identification value in
+ * CALLED_PARTY_BCD_NPI array.
+ */
+ setSmscAddress: function(options) {
+ let ton = options.typeOfNumber;
+ let npi = CALLED_PARTY_BCD_NPI[options.numberPlanIdentification];
+
+ // If any of the mandatory arguments is not available, return an error
+ // immediately.
+ if (ton === undefined || npi === undefined || !options.smscAddress) {
+ options.errorMsg = GECKO_ERROR_INVALID_PARAMETER;
+ this.sendChromeMessage(options);
+ return;
+ }
+
+ // Remove all illegal characters in the number string for user-input fault
+ // tolerance.
+ let numStart = options.smscAddress[0] === "+" ? 1 : 0;
+ let number = options.smscAddress.substring(0, numStart) +
+ options.smscAddress.substring(numStart)
+ .replace(/[^0-9*#abc]/ig, "");
+
+ // If the filtered number is an empty string, return an error immediately.
+ if (number.length === 0) {
+ options.errorMsg = GECKO_ERROR_INVALID_PARAMETER;
+ this.sendChromeMessage(options);
+ return;
+ }
+
+ // Init parcel.
+ let Buf = this.context.Buf;
+ Buf.newParcel(REQUEST_SET_SMSC_ADDRESS, options);
+
+ // +---+-----------+---------------+
+ // | 1 | TON | NPI |
+ // +---+-----------+---------------+
+ let tosca = (0x1 << 7) + (ton << 4) + npi;
+ if (RILQUIRKS_SMSC_ADDRESS_FORMAT === "pdu") {
+ let pduHelper = this.context.GsmPDUHelper;
+
+ // Remove the preceding '+', and covert the special BCD digits defined in
+ // |Called party BCD number| of 3GPP TS 24.008 to corresponding
+ // hexadecimal values (refer the following table).
+ //
+ // +=========+=======+=====+
+ // | value | digit | hex |
+ // +========================
+ // | 1 0 1 0 | * | 0xA |
+ // | 1 0 1 1 | # | 0xB |
+ // | 1 1 0 0 | a | 0xC |
+ // | 1 1 0 1 | b | 0xD |
+ // | 1 1 1 0 | c | 0xE |
+ // +=========+=======+=====+
+ //
+ // The replace order is reversed intentionally, because if the digits are
+ // replaced in ascending order, "#" will be converted to "b" and then be
+ // converted again to "d", which generates incorrect result.
+ let pureNumber = number.substring(numStart)
+ .replace(/c/ig, "e")
+ .replace(/b/ig, "d")
+ .replace(/a/ig, "c")
+ .replace(/\#/g, "b")
+ .replace(/\*/g, "a");
+
+ // address length and string length
+ let length = Math.ceil(pureNumber.length / 2) + 1; // +1 octet for TOA
+ let strlen = length * 2 + 2; // +2 semi-octets for length octet
+
+ Buf.writeInt32(strlen);
+ pduHelper.writeHexOctet(length);
+ pduHelper.writeHexOctet(tosca);
+ pduHelper.writeSwappedNibbleBCD(pureNumber);
+ Buf.writeStringDelimiter(strlen);
+ } else /* RILQUIRKS_SMSC_ADDRESS_FORMAT === "text" */ {
+ let sca;
+ sca = '"' + number + '"' + ',' + tosca;
+ Buf.writeString(sca);
+ }
+
+ Buf.sendParcel();
+ },
+
+ /**
+ * Setup a data call.
+ *
+ * @param radioTech
+ * Integer to indicate radio technology.
+ * DATACALL_RADIOTECHNOLOGY_CDMA => CDMA.
+ * DATACALL_RADIOTECHNOLOGY_GSM => GSM.
+ * @param apn
+ * String containing the name of the APN to connect to.
+ * @param user
+ * String containing the username for the APN.
+ * @param passwd
+ * String containing the password for the APN.
+ * @param chappap
+ * Integer containing CHAP/PAP auth type.
+ * DATACALL_AUTH_NONE => PAP and CHAP is never performed.
+ * DATACALL_AUTH_PAP => PAP may be performed.
+ * DATACALL_AUTH_CHAP => CHAP may be performed.
+ * DATACALL_AUTH_PAP_OR_CHAP => PAP / CHAP may be performed.
+ * @param pdptype
+ * String containing PDP type to request. ("IP", "IPV6", ...)
+ */
+ setupDataCall: function(options) {
+ // From ./hardware/ril/include/telephony/ril.h:
+ // ((const char **)data)[0] Radio technology to use: 0-CDMA, 1-GSM/UMTS, 2...
+ // for values above 2 this is RIL_RadioTechnology + 2.
+ //
+ // From frameworks/base/telephony/java/com/android/internal/telephony/DataConnection.java:
+ // if the mRilVersion < 6, radio technology must be GSM/UMTS or CDMA.
+ // Otherwise, it must be + 2
+ //
+ // See also bug 901232 and 867873
+ let radioTech = options.radioTech + 2;
+ let Buf = this.context.Buf;
+ let token = Buf.newParcel(REQUEST_SETUP_DATA_CALL, options);
+ Buf.writeInt32(7);
+ Buf.writeString(radioTech.toString());
+ Buf.writeString(DATACALL_PROFILE_DEFAULT.toString());
+ Buf.writeString(options.apn);
+ Buf.writeString(options.user);
+ Buf.writeString(options.passwd);
+ Buf.writeString(options.chappap.toString());
+ Buf.writeString(options.pdptype);
+ Buf.sendParcel();
+ return token;
+ },
+
+ /**
+ * Deactivate a data call.
+ *
+ * @param cid
+ * String containing CID.
+ * @param reason
+ * One of DATACALL_DEACTIVATE_* constants.
+ */
+ deactivateDataCall: function(options) {
+ let Buf = this.context.Buf;
+ Buf.newParcel(REQUEST_DEACTIVATE_DATA_CALL, options);
+ Buf.writeInt32(2);
+ Buf.writeString(options.cid.toString());
+ Buf.writeString(options.reason !== undefined ?
+ options.reason.toString() :
+ DATACALL_DEACTIVATE_NO_REASON.toString());
+ Buf.sendParcel();
+ },
+
+ /**
+ * Get a list of data calls.
+ */
+ getDataCallList: function(options) {
+ this.context.Buf.simpleRequest(REQUEST_DATA_CALL_LIST, options);
+ },
+
+ _attachDataRegistration: false,
+ /**
+ * Manually attach/detach data registration.
+ *
+ * @param attach
+ * Boolean value indicating attach or detach.
+ */
+ setDataRegistration: function(options) {
+ this._attachDataRegistration = options.attach;
+
+ if (RILQUIRKS_DATA_REGISTRATION_ON_DEMAND) {
+ let request = options.attach ? RIL_REQUEST_GPRS_ATTACH :
+ RIL_REQUEST_GPRS_DETACH;
+ this.context.Buf.simpleRequest(request, options);
+ return;
+ } else if (RILQUIRKS_SUBSCRIPTION_CONTROL && options.attach) {
+ this.context.Buf.simpleRequest(REQUEST_SET_DATA_SUBSCRIPTION, options);
+ return;
+ }
+
+ // We don't really send a request to rild, so instantly reply success to
+ // RadioInterfaceLayer.
+ this.sendChromeMessage(options);
+ },
+
+ /**
+ * Get failure casue code for the most recently failed PDP context.
+ */
+ getFailCause: function(options) {
+ this.context.Buf.simpleRequest(REQUEST_LAST_CALL_FAIL_CAUSE, options);
+ },
+
+ /**
+ * Send USSD.
+ *
+ * @param ussd
+ * String containing the USSD code.
+ */
+ sendUSSD: function(options) {
+ let Buf = this.context.Buf;
+ Buf.newParcel(REQUEST_SEND_USSD, options);
+ Buf.writeString(options.ussd);
+ Buf.sendParcel();
+ },
+
+ /**
+ * Cancel pending USSD.
+ */
+ cancelUSSD: function(options) {
+ this.context.Buf.simpleRequest(REQUEST_CANCEL_USSD, options);
+ },
+
+ /**
+ * Queries current call forward rules.
+ *
+ * @param reason
+ * One of CALL_FORWARD_REASON_* constants.
+ * @param serviceClass
+ * One of ICC_SERVICE_CLASS_* constants.
+ * @param number
+ * Phone number of forwarding address.
+ */
+ queryCallForwardStatus: function(options) {
+ let Buf = this.context.Buf;
+ let number = options.number || "";
+ Buf.newParcel(REQUEST_QUERY_CALL_FORWARD_STATUS, options);
+ Buf.writeInt32(CALL_FORWARD_ACTION_QUERY_STATUS);
+ Buf.writeInt32(options.reason);
+ Buf.writeInt32(options.serviceClass || ICC_SERVICE_CLASS_NONE);
+ Buf.writeInt32(this._toaFromString(number));
+ Buf.writeString(number);
+ Buf.writeInt32(0);
+ Buf.sendParcel();
+ },
+
+ /**
+ * Configures call forward rule.
+ *
+ * @param action
+ * One of CALL_FORWARD_ACTION_* constants.
+ * @param reason
+ * One of CALL_FORWARD_REASON_* constants.
+ * @param serviceClass
+ * One of ICC_SERVICE_CLASS_* constants.
+ * @param number
+ * Phone number of forwarding address.
+ * @param timeSeconds
+ * Time in seconds to wait beforec all is forwarded.
+ */
+ setCallForward: function(options) {
+ let Buf = this.context.Buf;
+ Buf.newParcel(REQUEST_SET_CALL_FORWARD, options);
+ Buf.writeInt32(options.action);
+ Buf.writeInt32(options.reason);
+ Buf.writeInt32(options.serviceClass);
+ Buf.writeInt32(this._toaFromString(options.number));
+ Buf.writeString(options.number);
+ Buf.writeInt32(options.timeSeconds);
+ Buf.sendParcel();
+ },
+
+ /**
+ * Queries current call barring rules.
+ *
+ * @param program
+ * One of CALL_BARRING_PROGRAM_* constants.
+ * @param serviceClass
+ * One of ICC_SERVICE_CLASS_* constants.
+ */
+ queryCallBarringStatus: function(options) {
+ options.facility = CALL_BARRING_PROGRAM_TO_FACILITY[options.program];
+ options.password = ""; // For query no need to provide it.
+
+ // For some operators, querying specific serviceClass doesn't work. We use
+ // serviceClass 0 instead, and then process the response to extract the
+ // answer for queryServiceClass.
+ options.queryServiceClass = options.serviceClass;
+ options.serviceClass = 0;
+
+ this.queryICCFacilityLock(options);
+ },
+
+ /**
+ * Configures call barring rule.
+ *
+ * @param program
+ * One of CALL_BARRING_PROGRAM_* constants.
+ * @param enabled
+ * Enable or disable the call barring.
+ * @param password
+ * Barring password.
+ * @param serviceClass
+ * One of ICC_SERVICE_CLASS_* constants.
+ */
+ setCallBarring: function(options) {
+ options.facility = CALL_BARRING_PROGRAM_TO_FACILITY[options.program];
+ this.setICCFacilityLock(options);
+ },
+
+ /**
+ * Change call barring facility password.
+ *
+ * @param pin
+ * Old password.
+ * @param newPin
+ * New password.
+ */
+ changeCallBarringPassword: function(options) {
+ let Buf = this.context.Buf;
+ Buf.newParcel(REQUEST_CHANGE_BARRING_PASSWORD, options);
+ Buf.writeInt32(3);
+ // Set facility to ICC_CB_FACILITY_BA_ALL by following TS.22.030 clause
+ // 6.5.4 and Table B.1.
+ Buf.writeString(ICC_CB_FACILITY_BA_ALL);
+ Buf.writeString(options.pin);
+ Buf.writeString(options.newPin);
+ Buf.sendParcel();
+ },
+
+ /**
+ * Handle STK CALL_SET_UP request.
+ *
+ * @param hasConfirmed
+ * Does use have confirmed the call requested from ICC?
+ */
+ stkHandleCallSetup: function(options) {
+ let Buf = this.context.Buf;
+ Buf.newParcel(REQUEST_STK_HANDLE_CALL_SETUP_REQUESTED_FROM_SIM);
+ Buf.writeInt32(1);
+ Buf.writeInt32(options.hasConfirmed ? 1 : 0);
+ Buf.sendParcel();
+ },
+
+ /**
+ * Send STK Profile Download.
+ *
+ * @param profile Profile supported by ME.
+ */
+ sendStkTerminalProfile: function(profile) {
+ let Buf = this.context.Buf;
+ let GsmPDUHelper = this.context.GsmPDUHelper;
+
+ Buf.newParcel(REQUEST_STK_SET_PROFILE);
+ Buf.writeInt32(profile.length * 2);
+ for (let i = 0; i < profile.length; i++) {
+ GsmPDUHelper.writeHexOctet(profile[i]);
+ }
+ Buf.writeInt32(0);
+ Buf.sendParcel();
+ },
+
+ /**
+ * Send STK terminal response.
+ *
+ * @param command
+ * @param deviceIdentities
+ * @param resultCode
+ * @param [optional] additionalInformation
+ * @param [optional] itemIdentifier
+ * @param [optional] input
+ * @param [optional] isYesNo
+ * @param [optional] hasConfirmed
+ * @param [optional] localInfo
+ * @param [optional] timer
+ */
+ sendStkTerminalResponse: function(response) {
+ if (response.hasConfirmed !== undefined) {
+ this.stkHandleCallSetup(response);
+ return;
+ }
+
+ let Buf = this.context.Buf;
+ let ComprehensionTlvHelper = this.context.ComprehensionTlvHelper;
+ let GsmPDUHelper = this.context.GsmPDUHelper;
+
+ let command = response.command;
+ Buf.newParcel(REQUEST_STK_SEND_TERMINAL_RESPONSE);
+
+ // 1st mark for Parcel size
+ Buf.startCalOutgoingSize(function(size) {
+ // Parcel size is in string length, which costs 2 uint8 per char.
+ Buf.writeInt32(size / 2);
+ });
+
+ // Command Details
+ GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_COMMAND_DETAILS |
+ COMPREHENSIONTLV_FLAG_CR);
+ GsmPDUHelper.writeHexOctet(3);
+ if (command) {
+ GsmPDUHelper.writeHexOctet(command.commandNumber);
+ GsmPDUHelper.writeHexOctet(command.typeOfCommand);
+ GsmPDUHelper.writeHexOctet(command.commandQualifier);
+ } else {
+ GsmPDUHelper.writeHexOctet(0x00);
+ GsmPDUHelper.writeHexOctet(0x00);
+ GsmPDUHelper.writeHexOctet(0x00);
+ }
+
+ // Device Identifier
+ // According to TS102.223/TS31.111 section 6.8 Structure of
+ // TERMINAL RESPONSE, "For all SIMPLE-TLV objects with Min=N,
+ // the ME should set the CR(comprehension required) flag to
+ // comprehension not required.(CR=0)"
+ // Since DEVICE_IDENTITIES and DURATION TLVs have Min=N,
+ // the CR flag is not set.
+ GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_DEVICE_ID);
+ GsmPDUHelper.writeHexOctet(2);
+ GsmPDUHelper.writeHexOctet(STK_DEVICE_ID_ME);
+ GsmPDUHelper.writeHexOctet(STK_DEVICE_ID_SIM);
+
+ // Result
+ GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_RESULT |
+ COMPREHENSIONTLV_FLAG_CR);
+ if ("additionalInformation" in response) {
+ // In |12.12 Result| TS 11.14, the length of additional information is
+ // varied and all possible values are addressed in 12.12.1-11 of TS 11.14
+ // and 8.12.1-13 in TS 31.111.
+ // However,
+ // 1. Only SEND SS requires info with more than 1 octet.
+ // 2. In rild design, SEND SS is expected to be handled by modem and
+ // UNSOLICITED_STK_EVENT_NOTIFY will be sent to application layer to
+ // indicate appropriate messages to users. TR is not required in this
+ // case.
+ // Hence, we simplify the structure of |additionalInformation| to a
+ // numeric value instead of a octet array.
+ GsmPDUHelper.writeHexOctet(2);
+ GsmPDUHelper.writeHexOctet(response.resultCode);
+ GsmPDUHelper.writeHexOctet(response.additionalInformation);
+ } else {
+ GsmPDUHelper.writeHexOctet(1);
+ GsmPDUHelper.writeHexOctet(response.resultCode);
+ }
+
+ // Item Identifier
+ if (response.itemIdentifier != null) {
+ GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_ITEM_ID |
+ COMPREHENSIONTLV_FLAG_CR);
+ GsmPDUHelper.writeHexOctet(1);
+ GsmPDUHelper.writeHexOctet(response.itemIdentifier);
+ }
+
+ // No need to process Text data if user requests help information.
+ if (response.resultCode != STK_RESULT_HELP_INFO_REQUIRED) {
+ let text;
+ let coding = command.options.isUCS2 ?
+ STK_TEXT_CODING_UCS2 :
+ (command.options.isPacked ?
+ STK_TEXT_CODING_GSM_7BIT_PACKED :
+ STK_TEXT_CODING_GSM_8BIT);
+ if (response.isYesNo !== undefined) {
+ // Tag: GET_INKEY
+ // When the ME issues a successful TERMINAL RESPONSE for a GET INKEY
+ // ("Yes/No") command with command qualifier set to "Yes/No", it shall
+ // supply the value '01' when the answer is "positive" and the value
+ // '00' when the answer is "negative" in the Text string data object.
+ GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_TEXT_STRING |
+ COMPREHENSIONTLV_FLAG_CR);
+ // Length: 2
+ GsmPDUHelper.writeHexOctet(2);
+ // Value: Coding, Yes/No.
+ GsmPDUHelper.writeHexOctet(coding);
+ GsmPDUHelper.writeHexOctet(response.isYesNo ? 0x01 : 0x00);
+ } else {
+ if (response.input !== undefined) {
+ ComprehensionTlvHelper.writeTextStringTlv(response.input, coding);
+ }
+ }
+ }
+
+ // Duration
+ if (response.resultCode === STK_RESULT_NO_RESPONSE_FROM_USER) {
+ // In TS102 223, 6.4.2 GET INKEY, "if the UICC requests a variable timeout,
+ // the terminal shall wait until either the user enters a single character
+ // or the timeout expires. The timer starts when the text is displayed on
+ // the screen and stops when the TERMINAL RESPONSE is sent. The terminal
+ // shall pass the total display text duration (command execution duration)
+ // to the UICC using the TERMINAL RESPONSE. The time unit of the response
+ // is identical to the time unit of the requested variable timeout."
+ let duration = command && command.options && command.options.duration;
+ if (duration) {
+ GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_DURATION);
+ GsmPDUHelper.writeHexOctet(2);
+ GsmPDUHelper.writeHexOctet(duration.timeUnit);
+ GsmPDUHelper.writeHexOctet(duration.timeInterval);
+ }
+ }
+
+ // Local Information
+ if (response.localInfo) {
+ let localInfo = response.localInfo;
+
+ // Location Infomation
+ if (localInfo.locationInfo) {
+ ComprehensionTlvHelper.writeLocationInfoTlv(localInfo.locationInfo);
+ }
+
+ // IMEI
+ if (localInfo.imei != null) {
+ let imei = localInfo.imei;
+ if (imei.length == 15) {
+ imei = imei + "0";
+ }
+
+ GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_IMEI);
+ GsmPDUHelper.writeHexOctet(8);
+ for (let i = 0; i < imei.length / 2; i++) {
+ GsmPDUHelper.writeHexOctet(parseInt(imei.substr(i * 2, 2), 16));
+ }
+ }
+
+ // Date and Time Zone
+ if (localInfo.date != null) {
+ ComprehensionTlvHelper.writeDateTimeZoneTlv(localInfo.date);
+ }
+
+ // Language
+ if (localInfo.language) {
+ ComprehensionTlvHelper.writeLanguageTlv(localInfo.language);
+ }
+ }
+
+ // Timer
+ if (response.timer) {
+ let timer = response.timer;
+
+ if (timer.timerId) {
+ GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_TIMER_IDENTIFIER);
+ GsmPDUHelper.writeHexOctet(1);
+ GsmPDUHelper.writeHexOctet(timer.timerId);
+ }
+
+ if (timer.timerValue) {
+ ComprehensionTlvHelper.writeTimerValueTlv(timer.timerValue, false);
+ }
+ }
+
+ // Calculate and write Parcel size to 1st mark
+ Buf.stopCalOutgoingSize();
+
+ Buf.writeInt32(0);
+ Buf.sendParcel();
+ },
+
+ /**
+ * Send STK Envelope(Menu Selection) command.
+ *
+ * @param itemIdentifier
+ * @param helpRequested
+ */
+ sendStkMenuSelection: function(command) {
+ command.tag = BER_MENU_SELECTION_TAG;
+ command.deviceId = {
+ sourceId :STK_DEVICE_ID_KEYPAD,
+ destinationId: STK_DEVICE_ID_SIM
+ };
+ this.sendICCEnvelopeCommand(command);
+ },
+
+ /**
+ * Send STK Envelope(Timer Expiration) command.
+ *
+ * @param timer
+ */
+ sendStkTimerExpiration: function(command) {
+ command.tag = BER_TIMER_EXPIRATION_TAG;
+ command.deviceId = {
+ sourceId: STK_DEVICE_ID_ME,
+ destinationId: STK_DEVICE_ID_SIM
+ };
+ command.timerId = command.timer.timerId;
+ command.timerValue = command.timer.timerValue;
+ this.sendICCEnvelopeCommand(command);
+ },
+
+ /**
+ * Send STK Envelope(Event Download) command.
+ * @param event
+ */
+ sendStkEventDownload: function(command) {
+ command.tag = BER_EVENT_DOWNLOAD_TAG;
+ command.eventList = command.event.eventType;
+ switch (command.eventList) {
+ case STK_EVENT_TYPE_LOCATION_STATUS:
+ command.deviceId = {
+ sourceId :STK_DEVICE_ID_ME,
+ destinationId: STK_DEVICE_ID_SIM
+ };
+ command.locationStatus = command.event.locationStatus;
+ // Location info should only be provided when locationStatus is normal.
+ if (command.locationStatus == STK_SERVICE_STATE_NORMAL) {
+ command.locationInfo = command.event.locationInfo;
+ }
+ break;
+ case STK_EVENT_TYPE_MT_CALL:
+ command.deviceId = {
+ sourceId: STK_DEVICE_ID_NETWORK,
+ destinationId: STK_DEVICE_ID_SIM
+ };
+ command.transactionId = 0;
+ command.address = command.event.number;
+ break;
+ case STK_EVENT_TYPE_CALL_DISCONNECTED:
+ command.cause = command.event.error;
+ // Fall through.
+ case STK_EVENT_TYPE_CALL_CONNECTED:
+ command.deviceId = {
+ sourceId: (command.event.isIssuedByRemote ?
+ STK_DEVICE_ID_NETWORK : STK_DEVICE_ID_ME),
+ destinationId: STK_DEVICE_ID_SIM
+ };
+ command.transactionId = 0;
+ break;
+ case STK_EVENT_TYPE_USER_ACTIVITY:
+ command.deviceId = {
+ sourceId: STK_DEVICE_ID_ME,
+ destinationId: STK_DEVICE_ID_SIM
+ };
+ break;
+ case STK_EVENT_TYPE_IDLE_SCREEN_AVAILABLE:
+ command.deviceId = {
+ sourceId: STK_DEVICE_ID_DISPLAY,
+ destinationId: STK_DEVICE_ID_SIM
+ };
+ break;
+ case STK_EVENT_TYPE_LANGUAGE_SELECTION:
+ command.deviceId = {
+ sourceId: STK_DEVICE_ID_ME,
+ destinationId: STK_DEVICE_ID_SIM
+ };
+ command.language = command.event.language;
+ break;
+ case STK_EVENT_TYPE_BROWSER_TERMINATION:
+ command.deviceId = {
+ sourceId: STK_DEVICE_ID_ME,
+ destinationId: STK_DEVICE_ID_SIM
+ };
+ command.terminationCause = command.event.terminationCause;
+ break;
+ }
+ this.sendICCEnvelopeCommand(command);
+ },
+
+ /**
+ * Send REQUEST_STK_SEND_ENVELOPE_COMMAND to ICC.
+ *
+ * @param tag
+ * @patam deviceId
+ * @param [optioanl] itemIdentifier
+ * @param [optional] helpRequested
+ * @param [optional] eventList
+ * @param [optional] locationStatus
+ * @param [optional] locationInfo
+ * @param [optional] address
+ * @param [optional] transactionId
+ * @param [optional] cause
+ * @param [optional] timerId
+ * @param [optional] timerValue
+ * @param [optional] terminationCause
+ */
+ sendICCEnvelopeCommand: function(options) {
+ if (DEBUG) {
+ this.context.debug("Stk Envelope " + JSON.stringify(options));
+ }
+
+ let Buf = this.context.Buf;
+ let ComprehensionTlvHelper = this.context.ComprehensionTlvHelper;
+ let GsmPDUHelper = this.context.GsmPDUHelper;
+
+ Buf.newParcel(REQUEST_STK_SEND_ENVELOPE_COMMAND);
+
+ // 1st mark for Parcel size
+ Buf.startCalOutgoingSize(function(size) {
+ // Parcel size is in string length, which costs 2 uint8 per char.
+ Buf.writeInt32(size / 2);
+ });
+
+ // Write a BER-TLV
+ GsmPDUHelper.writeHexOctet(options.tag);
+ // 2nd mark for BER length
+ Buf.startCalOutgoingSize(function(size) {
+ // BER length is in number of hexOctets, which costs 4 uint8 per hexOctet.
+ GsmPDUHelper.writeHexOctet(size / 4);
+ });
+
+ // Event List
+ if (options.eventList != null) {
+ GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_EVENT_LIST |
+ COMPREHENSIONTLV_FLAG_CR);
+ GsmPDUHelper.writeHexOctet(1);
+ GsmPDUHelper.writeHexOctet(options.eventList);
+ }
+
+ // Device Identifies
+ GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_DEVICE_ID |
+ COMPREHENSIONTLV_FLAG_CR);
+ GsmPDUHelper.writeHexOctet(2);
+ GsmPDUHelper.writeHexOctet(options.deviceId.sourceId);
+ GsmPDUHelper.writeHexOctet(options.deviceId.destinationId);
+
+ // Item Identifier
+ if (options.itemIdentifier != null) {
+ GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_ITEM_ID |
+ COMPREHENSIONTLV_FLAG_CR);
+ GsmPDUHelper.writeHexOctet(1);
+ GsmPDUHelper.writeHexOctet(options.itemIdentifier);
+ }
+
+ // Help Request
+ if (options.helpRequested) {
+ GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_HELP_REQUEST |
+ COMPREHENSIONTLV_FLAG_CR);
+ GsmPDUHelper.writeHexOctet(0);
+ // Help Request doesn't have value
+ }
+
+ // Location Status
+ if (options.locationStatus != null) {
+ let len = options.locationStatus.length;
+ GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_LOCATION_STATUS |
+ COMPREHENSIONTLV_FLAG_CR);
+ GsmPDUHelper.writeHexOctet(1);
+ GsmPDUHelper.writeHexOctet(options.locationStatus);
+ }
+
+ // Location Info
+ if (options.locationInfo) {
+ ComprehensionTlvHelper.writeLocationInfoTlv(options.locationInfo);
+ }
+
+ // Transaction Id
+ if (options.transactionId != null) {
+ GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_TRANSACTION_ID |
+ COMPREHENSIONTLV_FLAG_CR);
+ GsmPDUHelper.writeHexOctet(1);
+ GsmPDUHelper.writeHexOctet(options.transactionId);
+ }
+
+ // Address
+ if (options.address) {
+ GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_ADDRESS |
+ COMPREHENSIONTLV_FLAG_CR);
+ let addressLength = options.address[0] == '+' ? options.address.length - 1
+ : options.address.length;
+ ComprehensionTlvHelper.writeLength(
+ Math.ceil(addressLength / 2) + 1 // address BCD + TON
+ );
+ this.context.ICCPDUHelper.writeDiallingNumber(options.address);
+ }
+
+ // Cause of disconnection.
+ if (options.cause != null) {
+ ComprehensionTlvHelper.writeCauseTlv(options.cause);
+ }
+
+ // Timer Identifier
+ if (options.timerId != null) {
+ GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_TIMER_IDENTIFIER |
+ COMPREHENSIONTLV_FLAG_CR);
+ GsmPDUHelper.writeHexOctet(1);
+ GsmPDUHelper.writeHexOctet(options.timerId);
+ }
+
+ // Timer Value
+ if (options.timerValue != null) {
+ ComprehensionTlvHelper.writeTimerValueTlv(options.timerValue, true);
+ }
+
+ // Language
+ if (options.language) {
+ ComprehensionTlvHelper.writeLanguageTlv(options.language);
+ }
+
+ // Browser Termination
+ if (options.terminationCause != null) {
+ GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_BROWSER_TERMINATION_CAUSE |
+ COMPREHENSIONTLV_FLAG_CR);
+ GsmPDUHelper.writeHexOctet(1);
+ GsmPDUHelper.writeHexOctet(options.terminationCause);
+ }
+
+ // Calculate and write BER length to 2nd mark
+ Buf.stopCalOutgoingSize();
+
+ // Calculate and write Parcel size to 1st mark
+ Buf.stopCalOutgoingSize();
+
+ Buf.writeInt32(0);
+ Buf.sendParcel();
+ },
+
+ /**
+ * Report STK Service is running.
+ */
+ reportStkServiceIsRunning: function() {
+ this.context.Buf.simpleRequest(REQUEST_REPORT_STK_SERVICE_IS_RUNNING);
+ },
+
+ /**
+ * Process ICC status.
+ */
+ _processICCStatus: function(iccStatus) {
+ // If |_waitingRadioTech| is true, we should not get app information because
+ // the |_isCdma| flag is not ready yet. Otherwise we may use wrong index to
+ // get app information, especially for the case that icc card has both cdma
+ // and gsm subscription.
+ if (this._waitingRadioTech) {
+ return;
+ }
+
+ // When |iccStatus.cardState| is not CARD_STATE_PRESENT, set cardState to
+ // undetected.
+ if (iccStatus.cardState !== CARD_STATE_PRESENT) {
+ if (this.cardState !== GECKO_CARDSTATE_UNDETECTED) {
+ this.operator = null;
+ // We should send |cardstatechange| before |iccinfochange|, otherwise we
+ // may lost cardstatechange event when icc card becomes undetected.
+ this.cardState = GECKO_CARDSTATE_UNDETECTED;
+ this.sendChromeMessage({rilMessageType: "cardstatechange",
+ cardState: this.cardState});
+
+ this.iccInfo = {iccType: null};
+ this.context.ICCUtilsHelper.handleICCInfoChange();
+ }
+ return;
+ }
+
+ if (RILQUIRKS_SUBSCRIPTION_CONTROL) {
+ // All appIndex is -1 means the subscription is not activated yet.
+ // Note that we don't support "ims" for now, so we don't take it into
+ // account.
+ let neetToActivate = iccStatus.cdmaSubscriptionAppIndex === -1 &&
+ iccStatus.gsmUmtsSubscriptionAppIndex === -1;
+ if (neetToActivate &&
+ // Note: setUiccSubscription works abnormally when RADIO is OFF,
+ // which causes SMS function broken in Flame.
+ // See bug 1008557 for detailed info.
+ this.radioState === GECKO_RADIOSTATE_ENABLED) {
+ for (let i = 0; i < iccStatus.apps.length; i++) {
+ this.setUiccSubscription({appIndex: i, enabled: true});
+ }
+ }
+ }
+
+ let newCardState;
+ let index = this._isCdma ? iccStatus.cdmaSubscriptionAppIndex
+ : iccStatus.gsmUmtsSubscriptionAppIndex;
+ let app = iccStatus.apps[index];
+ if (app) {
+ // fetchICCRecords will need to read aid, so read aid here.
+ this.aid = app.aid;
+ this.appType = app.app_type;
+ this.iccInfo.iccType = GECKO_CARD_TYPE[this.appType];
+
+ switch (app.app_state) {
+ case CARD_APPSTATE_ILLEGAL:
+ newCardState = GECKO_CARDSTATE_ILLEGAL;
+ break;
+ case CARD_APPSTATE_PIN:
+ newCardState = GECKO_CARDSTATE_PIN_REQUIRED;
+ break;
+ case CARD_APPSTATE_PUK:
+ newCardState = GECKO_CARDSTATE_PUK_REQUIRED;
+ break;
+ case CARD_APPSTATE_SUBSCRIPTION_PERSO:
+ newCardState = PERSONSUBSTATE[app.perso_substate];
+ break;
+ case CARD_APPSTATE_READY:
+ newCardState = GECKO_CARDSTATE_READY;
+ break;
+ case CARD_APPSTATE_UNKNOWN:
+ case CARD_APPSTATE_DETECTED:
+ // Fall through.
+ default:
+ newCardState = GECKO_CARDSTATE_UNKNOWN;
+ }
+
+ let pin1State = app.pin1_replaced ? iccStatus.universalPINState :
+ app.pin1;
+ if (pin1State === CARD_PINSTATE_ENABLED_PERM_BLOCKED) {
+ newCardState = GECKO_CARDSTATE_PERMANENT_BLOCKED;
+ }
+ } else {
+ // Having incorrect app information, set card state to unknown.
+ newCardState = GECKO_CARDSTATE_UNKNOWN;
+ }
+
+ let ICCRecordHelper = this.context.ICCRecordHelper;
+ // Try to get iccId only when cardState left GECKO_CARDSTATE_UNDETECTED.
+ if (iccStatus.cardState === CARD_STATE_PRESENT &&
+ (this.cardState === GECKO_CARDSTATE_UNINITIALIZED ||
+ this.cardState === GECKO_CARDSTATE_UNDETECTED)) {
+ ICCRecordHelper.readICCID();
+ }
+
+ if (this.cardState == newCardState) {
+ return;
+ }
+
+ // This was moved down from CARD_APPSTATE_READY
+ this.requestNetworkInfo();
+ if (newCardState == GECKO_CARDSTATE_READY) {
+ // For type SIM, we need to check EF_phase first.
+ // Other types of ICC we can send Terminal_Profile immediately.
+ if (this.appType == CARD_APPTYPE_SIM) {
+ this.context.SimRecordHelper.readSimPhase();
+ } else if (RILQUIRKS_SEND_STK_PROFILE_DOWNLOAD) {
+ this.sendStkTerminalProfile(STK_SUPPORTED_TERMINAL_PROFILE);
+ }
+
+ ICCRecordHelper.fetchICCRecords();
+ }
+
+ this.cardState = newCardState;
+ this.sendChromeMessage({rilMessageType: "cardstatechange",
+ cardState: this.cardState});
+ },
+
+ /**
+ * Helper for processing responses of functions such as enterICC* and changeICC*.
+ */
+ _processEnterAndChangeICCResponses: function(length, options) {
+ options.retryCount = length ? this.context.Buf.readInt32List()[0] : -1;
+ this.sendChromeMessage(options);
+ },
+
+ // We combine all of the NETWORK_INFO_MESSAGE_TYPES into one "networkinfochange"
+ // message to the RadioInterfaceLayer, so we can avoid sending multiple
+ // VoiceInfoChanged events for both operator / voice_data_registration
+ //
+ // State management here is a little tricky. We need to know both:
+ // 1. Whether or not a response was received for each of the
+ // NETWORK_INFO_MESSAGE_TYPES
+ // 2. The outbound message that corresponds with that response -- but this
+ // only happens when internal state changes (i.e. it isn't guaranteed)
+ //
+ // To collect this state, each message response function first calls
+ // _receivedNetworkInfo, to mark the response as received. When the
+ // final response is received, a call to _sendPendingNetworkInfo is placed
+ // on the next tick of the worker thread.
+ //
+ // Since the original call to _receivedNetworkInfo happens at the top
+ // of the response handler, this gives the final handler a chance to
+ // queue up it's "changed" message by calling _sendNetworkInfoMessage if/when
+ // the internal state has actually changed.
+ _sendNetworkInfoMessage: function(type, message) {
+ if (!this._processingNetworkInfo) {
+ // We only combine these messages in the case of the combined request
+ // in requestNetworkInfo()
+ this.sendChromeMessage(message);
+ return;
+ }
+
+ if (DEBUG) {
+ this.context.debug("Queuing " + type + " network info message: " +
+ JSON.stringify(message));
+ }
+ this._pendingNetworkInfo[type] = message;
+ },
+
+ _receivedNetworkInfo: function(type) {
+ if (DEBUG) this.context.debug("Received " + type + " network info.");
+ if (!this._processingNetworkInfo) {
+ return;
+ }
+
+ let pending = this._pendingNetworkInfo;
+
+ // We still need to track states for events that aren't fired.
+ if (!(type in pending)) {
+ pending[type] = this.pendingNetworkType;
+ }
+
+ // Pending network info is ready to be sent when no more messages
+ // are waiting for responses, but the combined payload hasn't been sent.
+ for (let i = 0; i < NETWORK_INFO_MESSAGE_TYPES.length; i++) {
+ let msgType = NETWORK_INFO_MESSAGE_TYPES[i];
+ if (!(msgType in pending)) {
+ if (DEBUG) {
+ this.context.debug("Still missing some more network info, not " +
+ "notifying main thread.");
+ }
+ return;
+ }
+ }
+
+ // Do a pass to clean up the processed messages that didn't create
+ // a response message, so we don't have unused keys in the outbound
+ // networkinfochanged message.
+ for (let key in pending) {
+ if (pending[key] == this.pendingNetworkType) {
+ delete pending[key];
+ }
+ }
+
+ if (DEBUG) {
+ this.context.debug("All pending network info has been received: " +
+ JSON.stringify(pending));
+ }
+
+ // Send the message on the next tick of the worker's loop, so we give the
+ // last message a chance to call _sendNetworkInfoMessage first.
+ setTimeout(this._sendPendingNetworkInfo.bind(this), 0);
+ },
+
+ _sendPendingNetworkInfo: function() {
+ this.sendChromeMessage(this._pendingNetworkInfo);
+
+ this._processingNetworkInfo = false;
+ for (let i = 0; i < NETWORK_INFO_MESSAGE_TYPES.length; i++) {
+ delete this._pendingNetworkInfo[NETWORK_INFO_MESSAGE_TYPES[i]];
+ }
+
+ if (this._needRepollNetworkInfo) {
+ this._needRepollNetworkInfo = false;
+ this.requestNetworkInfo();
+ }
+ },
+
+ /**
+ * Normalize the signal strength in dBm to the signal level from 0 to 100.
+ *
+ * @param signal
+ * The signal strength in dBm to normalize.
+ * @param min
+ * The signal strength in dBm maps to level 0.
+ * @param max
+ * The signal strength in dBm maps to level 100.
+ *
+ * @return level
+ * The signal level from 0 to 100.
+ */
+ _processSignalLevel: function(signal, min, max) {
+ if (signal <= min) {
+ return 0;
+ }
+
+ if (signal >= max) {
+ return 100;
+ }
+
+ return Math.floor((signal - min) * 100 / (max - min));
+ },
+
+ /**
+ * Process LTE signal strength to the signal info object.
+ *
+ * @param signal
+ * The signal object reported from RIL/modem.
+ *
+ * @return The object of signal strength info.
+ * Or null if invalid signal input.
+ *
+ * TODO: Bug 982013: reconsider the format of signal strength APIs for
+ * GSM/CDMA/LTE to expose details, such as rsrp and rsnnr,
+ * individually.
+ */
+ _processLteSignal: function(signal) {
+ let info = {
+ voice: {
+ signalStrength: null,
+ relSignalStrength: null
+ },
+ data: {
+ signalStrength: null,
+ relSignalStrength: null
+ }
+ };
+
+ // Referring to AOSP, use lteRSRP for signalStrength in dBm.
+ let signalStrength = (signal.lteRSRP === undefined || signal.lteRSRP === 0x7FFFFFFF) ?
+ null : signal.lteRSRP;
+ info.voice.signalStrength = info.data.signalStrength = signalStrength;
+
+ // Referring to AOSP, first determine signalLevel based on RSRP and RSSNR,
+ // then on lteSignalStrength if RSRP and RSSNR are invalid.
+ let rsrpLevel = -1;
+ let rssnrLevel = -1;
+ if (signal.lteRSRP !== undefined &&
+ signal.lteRSRP !== 0x7FFFFFFF &&
+ signal.lteRSRP >= 44 &&
+ signal.lteRSRP <= 140) {
+ rsrpLevel = this._processSignalLevel(signal.lteRSRP * -1, -115, -85);
+ }
+
+ if (signal.lteRSSNR !== undefined &&
+ signal.lteRSSNR !== 0x7FFFFFFF &&
+ signal.lteRSSNR >= -200 &&
+ signal.lteRSSNR <= 300) {
+ rssnrLevel = this._processSignalLevel(signal.lteRSSNR, -30, 130);
+ }
+
+ if (rsrpLevel !== -1 && rssnrLevel !== -1) {
+ info.voice.relSignalStrength = info.data.relSignalStrength =
+ Math.min(rsrpLevel, rssnrLevel);
+ return info;
+ }
+
+ let level = Math.max(rsrpLevel, rssnrLevel);
+ if (level !== -1) {
+ info.voice.relSignalStrength = info.data.relSignalStrength = level;
+ return info;
+ }
+
+ // Valid values are 0-63 as defined in TS 27.007 clause 8.69.
+ if (signal.lteSignalStrength !== undefined &&
+ signal.lteSignalStrength >= 0 &&
+ signal.lteSignalStrength <= 63) {
+ level = this._processSignalLevel(signal.lteSignalStrength, 0, 12);
+ info.voice.relSignalStrength = info.data.relSignalStrength = level;
+ return info;
+ }
+
+ return null;
+ },
+
+ _processSignalStrength: function(signal) {
+ let info = {
+ voice: {
+ signalStrength: null,
+ relSignalStrength: null
+ },
+ data: {
+ signalStrength: null,
+ relSignalStrength: null
+ }
+ };
+
+ // During startup, |radioTech| is not yet defined, so we need to
+ // check it separately.
+ if (("radioTech" in this.voiceRegistrationState) &&
+ !this._isGsmTechGroup(this.voiceRegistrationState.radioTech)) {
+ // CDMA RSSI.
+ // Valid values are positive integers. This value is the actual RSSI value
+ // multiplied by -1. Example: If the actual RSSI is -75, then this
+ // response value will be 75.
+ if (signal.cdmaDBM && signal.cdmaDBM > 0) {
+ let signalStrength = -1 * signal.cdmaDBM;
+ info.voice.signalStrength = signalStrength;
+
+ // -105 and -70 are referred to AOSP's implementation. These values are
+ // not constants and can be customized based on different requirement.
+ let signalLevel = this._processSignalLevel(signalStrength, -105, -70);
+ info.voice.relSignalStrength = signalLevel;
+ }
+
+ // EVDO RSSI.
+ // Valid values are positive integers. This value is the actual RSSI value
+ // multiplied by -1. Example: If the actual RSSI is -75, then this
+ // response value will be 75.
+ if (signal.evdoDBM && signal.evdoDBM > 0) {
+ let signalStrength = -1 * signal.evdoDBM;
+ info.data.signalStrength = signalStrength;
+
+ // -105 and -70 are referred to AOSP's implementation. These values are
+ // not constants and can be customized based on different requirement.
+ let signalLevel = this._processSignalLevel(signalStrength, -105, -70);
+ info.data.relSignalStrength = signalLevel;
+ }
+ } else {
+ // Check LTE level first, and check GSM/UMTS level next if LTE one is not
+ // valid.
+ let lteInfo = this._processLteSignal(signal);
+ if (lteInfo) {
+ info = lteInfo;
+ } else {
+ // GSM signal strength.
+ // Valid values are 0-31 as defined in TS 27.007 8.5.
+ // 0 : -113 dBm or less
+ // 1 : -111 dBm
+ // 2...30: -109...-53 dBm
+ // 31 : -51 dBm
+ if (signal.gsmSignalStrength &&
+ signal.gsmSignalStrength >= 0 &&
+ signal.gsmSignalStrength <= 31) {
+ let signalStrength = -113 + 2 * signal.gsmSignalStrength;
+ info.voice.signalStrength = info.data.signalStrength = signalStrength;
+
+ // -115 and -85 are referred to AOSP's implementation. These values are
+ // not constants and can be customized based on different requirement.
+ let signalLevel = this._processSignalLevel(signalStrength, -110, -85);
+ info.voice.relSignalStrength = info.data.relSignalStrength = signalLevel;
+ }
+ }
+ }
+
+ info.rilMessageType = "signalstrengthchange";
+ this._sendNetworkInfoMessage(NETWORK_INFO_SIGNAL, info);
+ },
+
+ /**
+ * Process the network registration flags.
+ *
+ * @return true if the state changed, false otherwise.
+ */
+ _processCREG: function(curState, newState) {
+ let changed = false;
+
+ let regState = this.parseInt(newState[0], NETWORK_CREG_STATE_UNKNOWN);
+ if (curState.regState === undefined || curState.regState !== regState) {
+ changed = true;
+ curState.regState = regState;
+
+ curState.state = NETWORK_CREG_TO_GECKO_MOBILE_CONNECTION_STATE[regState];
+ curState.connected = regState == NETWORK_CREG_STATE_REGISTERED_HOME ||
+ regState == NETWORK_CREG_STATE_REGISTERED_ROAMING;
+ curState.roaming = regState == NETWORK_CREG_STATE_REGISTERED_ROAMING;
+ curState.emergencyCallsOnly = !curState.connected;
+ }
+
+ if (!curState.cell) {
+ curState.cell = {};
+ }
+
+ // From TS 23.003, 0000 and 0xfffe are indicated that no valid LAI exists
+ // in MS. So we still need to report the '0000' as well.
+ let lac = this.parseInt(newState[1], -1, 16);
+ if (curState.cell.gsmLocationAreaCode === undefined ||
+ curState.cell.gsmLocationAreaCode !== lac) {
+ curState.cell.gsmLocationAreaCode = lac;
+ changed = true;
+ }
+
+ let cid = this.parseInt(newState[2], -1, 16);
+ if (curState.cell.gsmCellId === undefined ||
+ curState.cell.gsmCellId !== cid) {
+ curState.cell.gsmCellId = cid;
+ changed = true;
+ }
+
+ let radioTech = (newState[3] === undefined ?
+ NETWORK_CREG_TECH_UNKNOWN :
+ this.parseInt(newState[3], NETWORK_CREG_TECH_UNKNOWN));
+ if (curState.radioTech === undefined || curState.radioTech !== radioTech) {
+ changed = true;
+ curState.radioTech = radioTech;
+ curState.type = GECKO_RADIO_TECH[radioTech] || null;
+ }
+ return changed;
+ },
+
+ _processVoiceRegistrationState: function(state) {
+ let rs = this.voiceRegistrationState;
+ let stateChanged = this._processCREG(rs, state);
+ if (stateChanged && rs.connected) {
+ this.getSmscAddress();
+ }
+
+ let cell = rs.cell;
+ if (this._isCdma) {
+ // Some variables below are not used. Comment them instead of removing to
+ // keep the information about state[x].
+ let cdmaBaseStationId = this.parseInt(state[4], -1);
+ let cdmaBaseStationLatitude = this.parseInt(state[5], -2147483648);
+ let cdmaBaseStationLongitude = this.parseInt(state[6], -2147483648);
+ // let cssIndicator = this.parseInt(state[7]);
+ let cdmaSystemId = this.parseInt(state[8], -1);
+ let cdmaNetworkId = this.parseInt(state[9], -1);
+ // let roamingIndicator = this.parseInt(state[10]);
+ // let systemIsInPRL = this.parseInt(state[11]);
+ // let defaultRoamingIndicator = this.parseInt(state[12]);
+ // let reasonForDenial = this.parseInt(state[13]);
+
+ if (cell.cdmaBaseStationId !== cdmaBaseStationId ||
+ cell.cdmaBaseStationLatitude !== cdmaBaseStationLatitude ||
+ cell.cdmaBaseStationLongitude !== cdmaBaseStationLongitude ||
+ cell.cdmaSystemId !== cdmaSystemId ||
+ cell.cdmaNetworkId !== cdmaNetworkId) {
+ stateChanged = true;
+ cell.cdmaBaseStationId = cdmaBaseStationId;
+ cell.cdmaBaseStationLatitude = cdmaBaseStationLatitude;
+ cell.cdmaBaseStationLongitude = cdmaBaseStationLongitude;
+ cell.cdmaSystemId = cdmaSystemId;
+ cell.cdmaNetworkId = cdmaNetworkId;
+ }
+ }
+
+ if (stateChanged) {
+ rs.rilMessageType = "voiceregistrationstatechange";
+ this._sendNetworkInfoMessage(NETWORK_INFO_VOICE_REGISTRATION_STATE, rs);
+ }
+ },
+
+ _processDataRegistrationState: function(state) {
+ let rs = this.dataRegistrationState;
+ let stateChanged = this._processCREG(rs, state);
+ if (stateChanged) {
+ rs.rilMessageType = "dataregistrationstatechange";
+ this._sendNetworkInfoMessage(NETWORK_INFO_DATA_REGISTRATION_STATE, rs);
+ }
+ },
+
+ _processOperator: function(operatorData) {
+ if (operatorData.length < 3) {
+ if (DEBUG) {
+ this.context.debug("Expected at least 3 strings for operator.");
+ }
+ }
+
+ if (!this.operator) {
+ this.operator = {
+ rilMessageType: "operatorchange",
+ longName: null,
+ shortName: null
+ };
+ }
+
+ let [longName, shortName, networkTuple] = operatorData;
+ let thisTuple = (this.operator.mcc || "") + (this.operator.mnc || "");
+
+ if (this.operator.longName !== longName ||
+ this.operator.shortName !== shortName ||
+ thisTuple !== networkTuple) {
+
+ this.operator.mcc = null;
+ this.operator.mnc = null;
+
+ if (networkTuple) {
+ try {
+ this._processNetworkTuple(networkTuple, this.operator);
+ } catch (e) {
+ if (DEBUG) this.context.debug("Error processing operator tuple: " + e);
+ }
+ } else {
+ // According to ril.h, the operator fields will be NULL when the operator
+ // is not currently registered. We can avoid trying to parse the numeric
+ // tuple in that case.
+ if (DEBUG) {
+ this.context.debug("Operator is currently unregistered");
+ }
+ }
+
+ this.operator.longName = longName;
+ this.operator.shortName = shortName;
+
+ let ICCUtilsHelper = this.context.ICCUtilsHelper;
+ if (ICCUtilsHelper.updateDisplayCondition()) {
+ ICCUtilsHelper.handleICCInfoChange();
+ }
+
+ // NETWORK_INFO_OPERATOR message will be sent out by overrideICCNetworkName
+ // itself if operator name is overridden after checking, or we have to
+ // do it by ourself.
+ if (!this.overrideICCNetworkName()) {
+ this._sendNetworkInfoMessage(NETWORK_INFO_OPERATOR, this.operator);
+ }
+ }
+ },
+
+ _processSuppSvcNotification: function(info) {
+ if (DEBUG) {
+ this.context.debug("handle supp svc notification: " + JSON.stringify(info));
+ }
+
+ if (info.notificationType !== 1) {
+ // We haven't supported MO intermediate result code, i.e.
+ // info.notificationType === 0, which refers to code1 defined in 3GPP
+ // 27.007 7.17. We only support partial MT unsolicited result code,
+ // referring to code2, for now.
+ return;
+ }
+
+ let notification = null;
+
+ switch (info.code) {
+ case SUPP_SVC_NOTIFICATION_CODE2_PUT_ON_HOLD:
+ case SUPP_SVC_NOTIFICATION_CODE2_RETRIEVED:
+ notification = GECKO_SUPP_SVC_NOTIFICATION_FROM_CODE2[info.code];
+ break;
+ default:
+ // Notification type not supported.
+ return;
+ }
+
+ let message = {rilMessageType: "suppSvcNotification",
+ number: info.number, // could be empty.
+ notification: notification};
+ this.sendChromeMessage(message);
+ },
+
+ _cancelEmergencyCbModeTimeout: function() {
+ if (this._exitEmergencyCbModeTimeoutID) {
+ clearTimeout(this._exitEmergencyCbModeTimeoutID);
+ this._exitEmergencyCbModeTimeoutID = null;
+ }
+ },
+
+ _handleChangedEmergencyCbMode: function(active) {
+ this._isInEmergencyCbMode = active;
+
+ // Clear the existed timeout event.
+ this._cancelEmergencyCbModeTimeout();
+
+ // Start a new timeout event when entering the mode.
+ if (active) {
+ this._exitEmergencyCbModeTimeoutID = setTimeout(
+ this.exitEmergencyCbMode.bind(this), EMERGENCY_CB_MODE_TIMEOUT_MS);
+ }
+
+ let message = {rilMessageType: "emergencyCbModeChange",
+ active: active,
+ timeoutMs: EMERGENCY_CB_MODE_TIMEOUT_MS};
+ this.sendChromeMessage(message);
+ },
+
+ _updateNetworkSelectionMode: function(mode) {
+ if (this.networkSelectionMode === mode) {
+ return;
+ }
+
+ let options = {
+ rilMessageType: "networkselectionmodechange",
+ mode: mode
+ };
+ this.networkSelectionMode = mode;
+ this._sendNetworkInfoMessage(NETWORK_INFO_NETWORK_SELECTION_MODE, options);
+ },
+
+ _processNetworks: function() {
+ let strings = this.context.Buf.readStringList();
+ let networks = [];
+
+ for (let i = 0; i < strings.length;
+ i += RILQUIRKS_AVAILABLE_NETWORKS_EXTRA_STRING ? 5 : 4) {
+ let network = {
+ longName: strings[i],
+ shortName: strings[i + 1],
+ mcc: null,
+ mnc: null,
+ state: null
+ };
+
+ let networkTuple = strings[i + 2];
+ try {
+ this._processNetworkTuple(networkTuple, network);
+ } catch (e) {
+ if (DEBUG) this.context.debug("Error processing operator tuple: " + e);
+ }
+
+ let state = strings[i + 3];
+ network.state = RIL_QAN_STATE_TO_GECKO_STATE[state];
+
+ networks.push(network);
+ }
+ return networks;
+ },
+
+ /**
+ * The "numeric" portion of the operator info is a tuple
+ * containing MCC (country code) and MNC (network code).
+ * AFAICT, MCC should always be 3 digits, making the remaining
+ * portion the MNC.
+ */
+ _processNetworkTuple: function(networkTuple, network) {
+ let tupleLen = networkTuple.length;
+
+ if (tupleLen == 5 || tupleLen == 6) {
+ network.mcc = networkTuple.substr(0, 3);
+ network.mnc = networkTuple.substr(3);
+ } else {
+ network.mcc = null;
+ network.mnc = null;
+
+ throw new Error("Invalid network tuple (should be 5 or 6 digits): " + networkTuple);
+ }
+ },
+
+ /**
+ * Check if GSM radio access technology group.
+ */
+ _isGsmTechGroup: function(radioTech) {
+ if (!radioTech) {
+ return true;
+ }
+
+ switch(radioTech) {
+ case NETWORK_CREG_TECH_GPRS:
+ case NETWORK_CREG_TECH_EDGE:
+ case NETWORK_CREG_TECH_UMTS:
+ case NETWORK_CREG_TECH_HSDPA:
+ case NETWORK_CREG_TECH_HSUPA:
+ case NETWORK_CREG_TECH_HSPA:
+ case NETWORK_CREG_TECH_LTE:
+ case NETWORK_CREG_TECH_HSPAP:
+ case NETWORK_CREG_TECH_GSM:
+ case NETWORK_CREG_TECH_DCHSPAP_1:
+ case NETWORK_CREG_TECH_DCHSPAP_2:
+ return true;
+ }
+
+ return false;
+ },
+
+ /**
+ * Process radio technology change.
+ */
+ _processRadioTech: function(radioTech) {
+ let isCdma = !this._isGsmTechGroup(radioTech);
+ this.radioTech = radioTech;
+
+ if (DEBUG) {
+ this.context.debug("Radio tech is set to: " + GECKO_RADIO_TECH[radioTech] +
+ ", it is a " + (isCdma?"cdma":"gsm") + " technology");
+ }
+
+ // We should request SIM information when
+ // 1. Radio state has been changed, so we are waiting for radioTech or
+ // 2. isCdma is different from this._isCdma.
+ if (this._waitingRadioTech || isCdma != this._isCdma) {
+ this._isCdma = isCdma;
+ this._waitingRadioTech = false;
+ this.getICCStatus();
+ }
+ },
+
+ /**
+ * Helper for returning the TOA for the given dial string.
+ */
+ _toaFromString: function(number) {
+ let toa = TOA_UNKNOWN;
+ if (number && number.length > 0 && number[0] == '+') {
+ toa = TOA_INTERNATIONAL;
+ }
+ return toa;
+ },
+
+ /**
+ * @param message A decoded SMS-DELIVER message.
+ *
+ * @see 3GPP TS 31.111 section 7.1.1
+ */
+ dataDownloadViaSMSPP: function(message) {
+ let Buf = this.context.Buf;
+ let GsmPDUHelper = this.context.GsmPDUHelper;
+
+ let options = {
+ pid: message.pid,
+ dcs: message.dcs,
+ encoding: message.encoding,
+ };
+ Buf.newParcel(REQUEST_STK_SEND_ENVELOPE_WITH_STATUS, options);
+
+ Buf.seekIncoming(-1 * (Buf.getCurrentParcelSize() - Buf.getReadAvailable()
+ - 2 * Buf.UINT32_SIZE)); // Skip response_type & request_type.
+ let messageStringLength = Buf.readInt32(); // In semi-octets
+ let smscLength = GsmPDUHelper.readHexOctet(); // In octets, inclusive of TOA
+ let tpduLength = (messageStringLength / 2) - (smscLength + 1); // In octets
+
+ // Device identities: 4 bytes
+ // Address: 0 or (2 + smscLength)
+ // SMS TPDU: (2 or 3) + tpduLength
+ let berLen = 4 +
+ (smscLength ? (2 + smscLength) : 0) +
+ (tpduLength <= 127 ? 2 : 3) + tpduLength; // In octets
+
+ let parcelLength = (berLen <= 127 ? 2 : 3) + berLen; // In octets
+ Buf.writeInt32(parcelLength * 2); // In semi-octets
+
+ // Write a BER-TLV
+ GsmPDUHelper.writeHexOctet(BER_SMS_PP_DOWNLOAD_TAG);
+ if (berLen > 127) {
+ GsmPDUHelper.writeHexOctet(0x81);
+ }
+ GsmPDUHelper.writeHexOctet(berLen);
+
+ // Device Identifies-TLV
+ GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_DEVICE_ID |
+ COMPREHENSIONTLV_FLAG_CR);
+ GsmPDUHelper.writeHexOctet(0x02);
+ GsmPDUHelper.writeHexOctet(STK_DEVICE_ID_NETWORK);
+ GsmPDUHelper.writeHexOctet(STK_DEVICE_ID_SIM);
+
+ // Address-TLV
+ if (smscLength) {
+ GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_ADDRESS);
+ GsmPDUHelper.writeHexOctet(smscLength);
+ Buf.copyIncomingToOutgoing(Buf.PDU_HEX_OCTET_SIZE * smscLength);
+ }
+
+ // SMS TPDU-TLV
+ GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_SMS_TPDU |
+ COMPREHENSIONTLV_FLAG_CR);
+ if (tpduLength > 127) {
+ GsmPDUHelper.writeHexOctet(0x81);
+ }
+ GsmPDUHelper.writeHexOctet(tpduLength);
+ Buf.copyIncomingToOutgoing(Buf.PDU_HEX_OCTET_SIZE * tpduLength);
+
+ // Write 2 string delimitors for the total string length must be even.
+ Buf.writeStringDelimiter(0);
+
+ Buf.sendParcel();
+ },
+
+ /**
+ * @param success A boolean value indicating the result of previous
+ * SMS-DELIVER message handling.
+ * @param responsePduLen ICC IO response PDU length in octets.
+ * @param options An object that contains four attributes: `pid`, `dcs`,
+ * `encoding` and `responsePduLen`.
+ *
+ * @see 3GPP TS 23.040 section 9.2.2.1a
+ */
+ acknowledgeIncomingGsmSmsWithPDU: function(success, responsePduLen, options) {
+ let Buf = this.context.Buf;
+ let GsmPDUHelper = this.context.GsmPDUHelper;
+
+ Buf.newParcel(REQUEST_ACKNOWLEDGE_INCOMING_GSM_SMS_WITH_PDU);
+
+ // Two strings.
+ Buf.writeInt32(2);
+
+ // String 1: Success
+ Buf.writeString(success ? "1" : "0");
+
+ // String 2: RP-ACK/RP-ERROR PDU
+ Buf.writeInt32(2 * (responsePduLen + (success ? 5 : 6))); // In semi-octet
+ // 1. TP-MTI & TP-UDHI
+ GsmPDUHelper.writeHexOctet(PDU_MTI_SMS_DELIVER);
+ if (!success) {
+ // 2. TP-FCS
+ GsmPDUHelper.writeHexOctet(PDU_FCS_USIM_DATA_DOWNLOAD_ERROR);
+ }
+ // 3. TP-PI
+ GsmPDUHelper.writeHexOctet(PDU_PI_USER_DATA_LENGTH |
+ PDU_PI_DATA_CODING_SCHEME |
+ PDU_PI_PROTOCOL_IDENTIFIER);
+ // 4. TP-PID
+ GsmPDUHelper.writeHexOctet(options.pid);
+ // 5. TP-DCS
+ GsmPDUHelper.writeHexOctet(options.dcs);
+ // 6. TP-UDL
+ if (options.encoding == PDU_DCS_MSG_CODING_7BITS_ALPHABET) {
+ GsmPDUHelper.writeHexOctet(Math.floor(responsePduLen * 8 / 7));
+ } else {
+ GsmPDUHelper.writeHexOctet(responsePduLen);
+ }
+ // TP-UD
+ Buf.copyIncomingToOutgoing(Buf.PDU_HEX_OCTET_SIZE * responsePduLen);
+ // Write 2 string delimitors for the total string length must be even.
+ Buf.writeStringDelimiter(0);
+
+ Buf.sendParcel();
+ },
+
+ /**
+ * @param message A decoded SMS-DELIVER message.
+ */
+ writeSmsToSIM: function(message) {
+ let Buf = this.context.Buf;
+ let GsmPDUHelper = this.context.GsmPDUHelper;
+
+ Buf.newParcel(REQUEST_WRITE_SMS_TO_SIM);
+
+ // Write EFsms Status
+ Buf.writeInt32(EFSMS_STATUS_FREE);
+
+ Buf.seekIncoming(-1 * (Buf.getCurrentParcelSize() - Buf.getReadAvailable()
+ - 2 * Buf.UINT32_SIZE)); // Skip response_type & request_type.
+ let messageStringLength = Buf.readInt32(); // In semi-octets
+ let smscLength = GsmPDUHelper.readHexOctet(); // In octets, inclusive of TOA
+ let pduLength = (messageStringLength / 2) - (smscLength + 1); // In octets
+
+ // 1. Write PDU first.
+ if (smscLength > 0) {
+ Buf.seekIncoming(smscLength * Buf.PDU_HEX_OCTET_SIZE);
+ }
+ // Write EFsms PDU string length
+ Buf.writeInt32(2 * pduLength); // In semi-octets
+ if (pduLength) {
+ Buf.copyIncomingToOutgoing(Buf.PDU_HEX_OCTET_SIZE * pduLength);
+ }
+ // Write 2 string delimitors for the total string length must be even.
+ Buf.writeStringDelimiter(0);
+
+ // 2. Write SMSC
+ // Write EFsms SMSC string length
+ Buf.writeInt32(2 * (smscLength + 1)); // Plus smscLength itself, in semi-octets
+ // Write smscLength
+ GsmPDUHelper.writeHexOctet(smscLength);
+ // Write TOA & SMSC Address
+ if (smscLength) {
+ Buf.seekIncoming(-1 * (Buf.getCurrentParcelSize() - Buf.getReadAvailable()
+ - 2 * Buf.UINT32_SIZE // Skip response_type, request_type.
+ - 2 * Buf.PDU_HEX_OCTET_SIZE)); // Skip messageStringLength & smscLength.
+ Buf.copyIncomingToOutgoing(Buf.PDU_HEX_OCTET_SIZE * smscLength);
+ }
+ // Write 2 string delimitors for the total string length must be even.
+ Buf.writeStringDelimiter(0);
+
+ Buf.sendParcel();
+ },
+
+ /**
+ * Helper to delegate the received sms segment to RadioInterface to process.
+ *
+ * @param message
+ * Received sms message.
+ *
+ * @return MOZ_FCS_WAIT_FOR_EXPLICIT_ACK
+ */
+ _processSmsMultipart: function(message) {
+ message.rilMessageType = "sms-received";
+
+ this.sendChromeMessage(message);
+
+ return MOZ_FCS_WAIT_FOR_EXPLICIT_ACK;
+ },
+
+ /**
+ * Helper for processing SMS-STATUS-REPORT PDUs.
+ *
+ * @param length
+ * Length of SMS string in the incoming parcel.
+ *
+ * @return A failure cause defined in 3GPP 23.040 clause 9.2.3.22.
+ */
+ _processSmsStatusReport: function(length) {
+ let [message, result] = this.context.GsmPDUHelper.processReceivedSms(length);
+ if (!message) {
+ if (DEBUG) this.context.debug("invalid SMS-STATUS-REPORT");
+ return PDU_FCS_UNSPECIFIED;
+ }
+
+ let options = this._pendingSentSmsMap[message.messageRef];
+ if (!options) {
+ if (DEBUG) this.context.debug("no pending SMS-SUBMIT message");
+ return PDU_FCS_OK;
+ }
+
+ let status = message.status;
+
+ // 3GPP TS 23.040 9.2.3.15 `The MS shall interpret any reserved values as
+ // "Service Rejected"(01100011) but shall store them exactly as received.`
+ if ((status >= 0x80)
+ || ((status >= PDU_ST_0_RESERVED_BEGIN)
+ && (status < PDU_ST_0_SC_SPECIFIC_BEGIN))
+ || ((status >= PDU_ST_1_RESERVED_BEGIN)
+ && (status < PDU_ST_1_SC_SPECIFIC_BEGIN))
+ || ((status >= PDU_ST_2_RESERVED_BEGIN)
+ && (status < PDU_ST_2_SC_SPECIFIC_BEGIN))
+ || ((status >= PDU_ST_3_RESERVED_BEGIN)
+ && (status < PDU_ST_3_SC_SPECIFIC_BEGIN))
+ ) {
+ status = PDU_ST_3_SERVICE_REJECTED;
+ }
+
+ // Pending. Waiting for next status report.
+ if ((status >>> 5) == 0x01) {
+ if (DEBUG) this.context.debug("SMS-STATUS-REPORT: delivery still pending");
+ return PDU_FCS_OK;
+ }
+
+ delete this._pendingSentSmsMap[message.messageRef];
+
+ let deliveryStatus = ((status >>> 5) === 0x00)
+ ? GECKO_SMS_DELIVERY_STATUS_SUCCESS
+ : GECKO_SMS_DELIVERY_STATUS_ERROR;
+ this.sendChromeMessage({
+ rilMessageType: options.rilMessageType,
+ rilMessageToken: options.rilMessageToken,
+ deliveryStatus: deliveryStatus
+ });
+
+ return PDU_FCS_OK;
+ },
+
+ /**
+ * Helper for processing CDMA SMS Delivery Acknowledgment Message
+ *
+ * @param message
+ * decoded SMS Delivery ACK message from CdmaPDUHelper.
+ *
+ * @return A failure cause defined in 3GPP 23.040 clause 9.2.3.22.
+ */
+ _processCdmaSmsStatusReport: function(message) {
+ let options = this._pendingSentSmsMap[message.msgId];
+ if (!options) {
+ if (DEBUG) this.context.debug("no pending SMS-SUBMIT message");
+ return PDU_FCS_OK;
+ }
+
+ if (message.errorClass === 2) {
+ if (DEBUG) {
+ this.context.debug("SMS-STATUS-REPORT: delivery still pending, " +
+ "msgStatus: " + message.msgStatus);
+ }
+ return PDU_FCS_OK;
+ }
+
+ delete this._pendingSentSmsMap[message.msgId];
+
+ if (message.errorClass === -1 && message.body) {
+ // Process as normal incoming SMS, if errorClass is invalid
+ // but message body is available.
+ return this._processSmsMultipart(message);
+ }
+
+ let deliveryStatus = (message.errorClass === 0)
+ ? GECKO_SMS_DELIVERY_STATUS_SUCCESS
+ : GECKO_SMS_DELIVERY_STATUS_ERROR;
+ this.sendChromeMessage({
+ rilMessageType: options.rilMessageType,
+ rilMessageToken: options.rilMessageToken,
+ deliveryStatus: deliveryStatus
+ });
+
+ return PDU_FCS_OK;
+ },
+
+ /**
+ * Helper for processing CDMA SMS WAP Push Message
+ *
+ * @param message
+ * decoded WAP message from CdmaPDUHelper.
+ *
+ * @return A failure cause defined in 3GPP 23.040 clause 9.2.3.22.
+ */
+ _processCdmaSmsWapPush: function(message) {
+ if (!message.data) {
+ if (DEBUG) this.context.debug("no data inside WAP Push message.");
+ return PDU_FCS_OK;
+ }
+
+ // See 6.5. MAPPING OF WDP TO CDMA SMS in WAP-295-WDP.
+ //
+ // Field | Length (bits)
+ // -----------------------------------------
+ // MSG_TYPE | 8
+ // TOTAL_SEGMENTS | 8
+ // SEGMENT_NUMBER | 8
+ // DATAGRAM | (NUM_FIELDS - 3) * 8
+ let index = 0;
+ if (message.data[index++] !== 0) {
+ if (DEBUG) this.context.debug("Ignore a WAP Message which is not WDP.");
+ return PDU_FCS_OK;
+ }
+
+ // 1. Originator Address in SMS-TL + Message_Id in SMS-TS are used to identify a unique WDP datagram.
+ // 2. TOTAL_SEGMENTS, SEGMENT_NUMBER are used to verify that a complete
+ // datagram has been received and is ready to be passed to a higher layer.
+ message.header = {
+ segmentRef: message.msgId,
+ segmentMaxSeq: message.data[index++],
+ segmentSeq: message.data[index++] + 1 // It's zero-based in CDMA WAP Push.
+ };
+
+ if (message.header.segmentSeq > message.header.segmentMaxSeq) {
+ if (DEBUG) this.context.debug("Wrong WDP segment info.");
+ return PDU_FCS_OK;
+ }
+
+ // Ports are only specified in 1st segment.
+ if (message.header.segmentSeq == 1) {
+ message.header.originatorPort = message.data[index++] << 8;
+ message.header.originatorPort |= message.data[index++];
+ message.header.destinationPort = message.data[index++] << 8;
+ message.header.destinationPort |= message.data[index++];
+ }
+
+ message.data = message.data.subarray(index);
+
+ return this._processSmsMultipart(message);
+ },
+
+ /**
+ * Helper for processing sent multipart SMS.
+ */
+ _processSentSmsSegment: function(options) {
+ // Setup attributes for sending next segment
+ let next = options.segmentSeq;
+ options.body = options.segments[next].body;
+ options.encodedBodyLength = options.segments[next].encodedBodyLength;
+ options.segmentSeq = next + 1;
+
+ this.sendSMS(options);
+ },
+
+ /**
+ * Helper for processing result of send SMS.
+ *
+ * @param length
+ * Length of SMS string in the incoming parcel.
+ * @param options
+ * Sms information.
+ */
+ _processSmsSendResult: function(length, options) {
+ if (options.errorMsg) {
+ if (DEBUG) {
+ this.context.debug("_processSmsSendResult: errorMsg = " +
+ options.errorMsg);
+ }
+
+ this.sendChromeMessage({
+ rilMessageType: options.rilMessageType,
+ rilMessageToken: options.rilMessageToken,
+ errorMsg: options.errorMsg,
+ });
+ return;
+ }
+
+ let Buf = this.context.Buf;
+ options.messageRef = Buf.readInt32();
+ options.ackPDU = Buf.readString();
+ options.errorCode = Buf.readInt32();
+
+ if ((options.segmentMaxSeq > 1)
+ && (options.segmentSeq < options.segmentMaxSeq)) {
+ // Not last segment
+ this._processSentSmsSegment(options);
+ } else {
+ // Last segment sent with success.
+ if (options.requestStatusReport) {
+ if (DEBUG) {
+ this.context.debug("waiting SMS-STATUS-REPORT for messageRef " +
+ options.messageRef);
+ }
+ this._pendingSentSmsMap[options.messageRef] = options;
+ }
+
+ this.sendChromeMessage({
+ rilMessageType: options.rilMessageType,
+ rilMessageToken: options.rilMessageToken,
+ });
+ }
+ },
+
+ _processReceivedSmsCbPage: function(original) {
+ if (original.numPages <= 1) {
+ if (original.body) {
+ original.fullBody = original.body;
+ delete original.body;
+ } else if (original.data) {
+ original.fullData = original.data;
+ delete original.data;
+ }
+ return original;
+ }
+
+ // Hash = <serial>:<mcc>:<mnc>:<lac>:<cid>
+ let hash = original.serial + ":" + this.iccInfo.mcc + ":"
+ + this.iccInfo.mnc + ":";
+ switch (original.geographicalScope) {
+ case CB_GSM_GEOGRAPHICAL_SCOPE_CELL_WIDE_IMMEDIATE:
+ case CB_GSM_GEOGRAPHICAL_SCOPE_CELL_WIDE:
+ hash += this.voiceRegistrationState.cell.gsmLocationAreaCode + ":"
+ + this.voiceRegistrationState.cell.gsmCellId;
+ break;
+ case CB_GSM_GEOGRAPHICAL_SCOPE_LOCATION_AREA_WIDE:
+ hash += this.voiceRegistrationState.cell.gsmLocationAreaCode + ":";
+ break;
+ default:
+ hash += ":";
+ break;
+ }
+
+ let index = original.pageIndex;
+
+ let options = this._receivedSmsCbPagesMap[hash];
+ if (!options) {
+ options = original;
+ this._receivedSmsCbPagesMap[hash] = options;
+
+ options.receivedPages = 0;
+ options.pages = [];
+ } else if (options.pages[index]) {
+ // Duplicated page?
+ if (DEBUG) {
+ this.context.debug("Got duplicated page no." + index +
+ " of a multipage SMSCB: " + JSON.stringify(original));
+ }
+ return null;
+ }
+
+ if (options.encoding == PDU_DCS_MSG_CODING_8BITS_ALPHABET) {
+ options.pages[index] = original.data;
+ delete original.data;
+ } else {
+ options.pages[index] = original.body;
+ delete original.body;
+ }
+ options.receivedPages++;
+ if (options.receivedPages < options.numPages) {
+ if (DEBUG) {
+ this.context.debug("Got page no." + index + " of a multipage SMSCB: " +
+ JSON.stringify(options));
+ }
+ return null;
+ }
+
+ // Remove from map
+ delete this._receivedSmsCbPagesMap[hash];
+
+ // Rebuild full body
+ if (options.encoding == PDU_DCS_MSG_CODING_8BITS_ALPHABET) {
+ // Uint8Array doesn't have `concat`, so we have to merge all pages by hand.
+ let fullDataLen = 0;
+ for (let i = 1; i <= options.numPages; i++) {
+ fullDataLen += options.pages[i].length;
+ }
+
+ options.fullData = new Uint8Array(fullDataLen);
+ for (let d= 0, i = 1; i <= options.numPages; i++) {
+ let data = options.pages[i];
+ for (let j = 0; j < data.length; j++) {
+ options.fullData[d++] = data[j];
+ }
+ }
+ } else {
+ options.fullBody = options.pages.join("");
+ }
+
+ if (DEBUG) {
+ this.context.debug("Got full multipage SMSCB: " + JSON.stringify(options));
+ }
+
+ return options;
+ },
+
+ _mergeCellBroadcastConfigs: function(list, from, to) {
+ if (!list) {
+ return [from, to];
+ }
+
+ for (let i = 0, f1, t1; i < list.length;) {
+ f1 = list[i++];
+ t1 = list[i++];
+ if (to == f1) {
+ // ...[from]...[to|f1]...(t1)
+ list[i - 2] = from;
+ return list;
+ }
+
+ if (to < f1) {
+ // ...[from]...(to)...[f1] or ...[from]...(to)[f1]
+ if (i > 2) {
+ // Not the first range pair, merge three arrays.
+ return list.slice(0, i - 2).concat([from, to]).concat(list.slice(i - 2));
+ } else {
+ return [from, to].concat(list);
+ }
+ }
+
+ if (from > t1) {
+ // ...[f1]...(t1)[from] or ...[f1]...(t1)...[from]
+ continue;
+ }
+
+ // Have overlap or merge-able adjacency with [f1]...(t1). Replace it
+ // with [min(from, f1)]...(max(to, t1)).
+
+ let changed = false;
+ if (from < f1) {
+ // [from]...[f1]...(t1) or [from][f1]...(t1)
+ // Save minimum from value.
+ list[i - 2] = from;
+ changed = true;
+ }
+
+ if (to <= t1) {
+ // [from]...[to](t1) or [from]...(to|t1)
+ // Can't have further merge-able adjacency. Return.
+ return list;
+ }
+
+ // Try merging possible next adjacent range.
+ let j = i;
+ for (let f2, t2; j < list.length;) {
+ f2 = list[j++];
+ t2 = list[j++];
+ if (to > t2) {
+ // [from]...[f2]...[t2]...(to) or [from]...[f2]...[t2](to)
+ // Merge next adjacent range again.
+ continue;
+ }
+
+ if (to < t2) {
+ if (to < f2) {
+ // [from]...(to)[f2] or [from]...(to)...[f2]
+ // Roll back and give up.
+ j -= 2;
+ } else if (to < t2) {
+ // [from]...[to|f2]...(t2), or [from]...[f2]...[to](t2)
+ // Merge to [from]...(t2) and give up.
+ to = t2;
+ }
+ }
+
+ break;
+ }
+
+ // Save maximum to value.
+ list[i - 1] = to;
+
+ if (j != i) {
+ // Remove merged adjacent ranges.
+ let ret = list.slice(0, i);
+ if (j < list.length) {
+ ret = ret.concat(list.slice(j));
+ }
+ return ret;
+ }
+
+ return list;
+ }
+
+ // Append to the end.
+ list.push(from);
+ list.push(to);
+
+ return list;
+ },
+
+ _isCellBroadcastConfigReady: function() {
+ if (!("MMI" in this.cellBroadcastConfigs)) {
+ return false;
+ }
+
+ // CBMI should be ready in GSM.
+ if (!this._isCdma &&
+ (!("CBMI" in this.cellBroadcastConfigs) ||
+ !("CBMID" in this.cellBroadcastConfigs) ||
+ !("CBMIR" in this.cellBroadcastConfigs))) {
+ return false;
+ }
+
+ return true;
+ },
+
+ /**
+ * Merge all members of cellBroadcastConfigs into mergedCellBroadcastConfig.
+ */
+ _mergeAllCellBroadcastConfigs: function() {
+ if (!this._isCellBroadcastConfigReady()) {
+ if (DEBUG) {
+ this.context.debug("cell broadcast configs not ready, waiting ...");
+ }
+ return;
+ }
+
+ // Prepare cell broadcast config. CBMI* are only used in GSM.
+ let usedCellBroadcastConfigs = {MMI: this.cellBroadcastConfigs.MMI};
+ if (!this._isCdma) {
+ usedCellBroadcastConfigs.CBMI = this.cellBroadcastConfigs.CBMI;
+ usedCellBroadcastConfigs.CBMID = this.cellBroadcastConfigs.CBMID;
+ usedCellBroadcastConfigs.CBMIR = this.cellBroadcastConfigs.CBMIR;
+ }
+
+ if (DEBUG) {
+ this.context.debug("Cell Broadcast search lists: " +
+ JSON.stringify(usedCellBroadcastConfigs));
+ }
+
+ let list = null;
+ for (let key in usedCellBroadcastConfigs) {
+ let ll = usedCellBroadcastConfigs[key];
+ if (ll == null) {
+ continue;
+ }
+
+ for (let i = 0; i < ll.length; i += 2) {
+ list = this._mergeCellBroadcastConfigs(list, ll[i], ll[i + 1]);
+ }
+ }
+
+ if (DEBUG) {
+ this.context.debug("Cell Broadcast search lists(merged): " +
+ JSON.stringify(list));
+ }
+ this.mergedCellBroadcastConfig = list;
+ this.updateCellBroadcastConfig();
+ },
+
+ /**
+ * Check whether search list from settings is settable by MMI, that is,
+ * whether the range is bounded in any entries of CB_NON_MMI_SETTABLE_RANGES.
+ */
+ _checkCellBroadcastMMISettable: function(from, to) {
+ if ((to <= from) || (from >= 65536) || (from < 0)) {
+ return false;
+ }
+
+ if (!this._isCdma) {
+ // GSM not settable ranges.
+ for (let i = 0, f, t; i < CB_NON_MMI_SETTABLE_RANGES.length;) {
+ f = CB_NON_MMI_SETTABLE_RANGES[i++];
+ t = CB_NON_MMI_SETTABLE_RANGES[i++];
+ if ((from < t) && (to > f)) {
+ // Have overlap.
+ return false;
+ }
+ }
+ }
+
+ return true;
+ },
+
+ /**
+ * Convert Cell Broadcast settings string into search list.
+ */
+ _convertCellBroadcastSearchList: function(searchListStr) {
+ let parts = searchListStr && searchListStr.split(",");
+ if (!parts) {
+ return null;
+ }
+
+ let list = null;
+ let result, from, to;
+ for (let range of parts) {
+ // Match "12" or "12-34". The result will be ["12", "12", null] or
+ // ["12-34", "12", "34"].
+ result = range.match(/^(\d+)(?:-(\d+))?$/);
+ if (!result) {
+ throw "Invalid format";
+ }
+
+ from = parseInt(result[1], 10);
+ to = (result[2]) ? parseInt(result[2], 10) + 1 : from + 1;
+ if (!this._checkCellBroadcastMMISettable(from, to)) {
+ throw "Invalid range";
+ }
+
+ if (list == null) {
+ list = [];
+ }
+ list.push(from);
+ list.push(to);
+ }
+
+ return list;
+ },
+
+ /**
+ * Handle incoming messages from the main UI thread.
+ *
+ * @param message
+ * Object containing the message. Messages are supposed
+ */
+ handleChromeMessage: function(message) {
+ if (DEBUG) {
+ this.context.debug("Received chrome message " + JSON.stringify(message));
+ }
+ let method = this[message.rilMessageType];
+ if (typeof method != "function") {
+ if (DEBUG) {
+ this.context.debug("Don't know what to do with message " +
+ JSON.stringify(message));
+ }
+ return;
+ }
+ method.call(this, message);
+ },
+
+ /**
+ * Process STK Proactive Command.
+ */
+ processStkProactiveCommand: function() {
+ let Buf = this.context.Buf;
+ let length = Buf.readInt32();
+ let berTlv;
+ try {
+ berTlv = this.context.BerTlvHelper.decode(length / 2);
+ } catch (e) {
+ if (DEBUG) this.context.debug("processStkProactiveCommand : " + e);
+ this.sendStkTerminalResponse({
+ resultCode: STK_RESULT_CMD_DATA_NOT_UNDERSTOOD});
+ return;
+ }
+
+ Buf.readStringDelimiter(length);
+
+ let ctlvs = berTlv.value;
+ let ctlv = this.context.StkProactiveCmdHelper.searchForTag(
+ COMPREHENSIONTLV_TAG_COMMAND_DETAILS, ctlvs);
+ if (!ctlv) {
+ this.sendStkTerminalResponse({
+ resultCode: STK_RESULT_CMD_DATA_NOT_UNDERSTOOD});
+ throw new Error("Can't find COMMAND_DETAILS ComprehensionTlv");
+ }
+
+ let cmdDetails = ctlv.value;
+ if (DEBUG) {
+ this.context.debug("commandNumber = " + cmdDetails.commandNumber +
+ " typeOfCommand = " + cmdDetails.typeOfCommand.toString(16) +
+ " commandQualifier = " + cmdDetails.commandQualifier);
+ }
+
+ // STK_CMD_MORE_TIME need not to propagate event to chrome.
+ if (cmdDetails.typeOfCommand == STK_CMD_MORE_TIME) {
+ this.sendStkTerminalResponse({
+ command: cmdDetails,
+ resultCode: STK_RESULT_OK});
+ return;
+ }
+
+ this.context.StkCommandParamsFactory.createParam(cmdDetails,
+ ctlvs,
+ (aResult) => {
+ cmdDetails.options = aResult;
+ cmdDetails.rilMessageType = "stkcommand";
+ this.sendChromeMessage(cmdDetails);
+ });
+ },
+
+ sendDefaultResponse: function(options) {
+ if (!options.rilMessageType) {
+ return;
+ }
+
+ this.sendChromeMessage(options);
+ },
+
+ /**
+ * Send messages to the main thread.
+ */
+ sendChromeMessage: function(message) {
+ message.rilMessageClientId = this.context.clientId;
+ postMessage(message);
+ },
+
+ /**
+ * Handle incoming requests from the RIL. We find the method that
+ * corresponds to the request type. Incidentally, the request type
+ * _is_ the method name, so that's easy.
+ */
+
+ handleParcel: function(request_type, length, options) {
+ let method = this[request_type];
+ if (typeof method == "function") {
+ if (DEBUG) this.context.debug("Handling parcel as " + method.name);
+ method.call(this, length, options);
+ }
+
+ if (this.telephonyRequestQueue.isValidRequest(request_type)) {
+ this.telephonyRequestQueue.pop(request_type);
+ }
+ }
+};
+
+RilObject.prototype[REQUEST_GET_SIM_STATUS] = function REQUEST_GET_SIM_STATUS(length, options) {
+ if (options.errorMsg) {
+ return;
+ }
+
+ let iccStatus = {};
+ let Buf = this.context.Buf;
+ iccStatus.cardState = Buf.readInt32(); // CARD_STATE_*
+ iccStatus.universalPINState = Buf.readInt32(); // CARD_PINSTATE_*
+ iccStatus.gsmUmtsSubscriptionAppIndex = Buf.readInt32();
+ iccStatus.cdmaSubscriptionAppIndex = Buf.readInt32();
+ iccStatus.imsSubscriptionAppIndex = Buf.readInt32();
+
+ let apps_length = Buf.readInt32();
+ if (apps_length > CARD_MAX_APPS) {
+ apps_length = CARD_MAX_APPS;
+ }
+
+ iccStatus.apps = [];
+ for (let i = 0 ; i < apps_length ; i++) {
+ iccStatus.apps.push({
+ app_type: Buf.readInt32(), // CARD_APPTYPE_*
+ app_state: Buf.readInt32(), // CARD_APPSTATE_*
+ perso_substate: Buf.readInt32(), // CARD_PERSOSUBSTATE_*
+ aid: Buf.readString(),
+ app_label: Buf.readString(),
+ pin1_replaced: Buf.readInt32(),
+ pin1: Buf.readInt32(),
+ pin2: Buf.readInt32()
+ });
+ if (RILQUIRKS_SIM_APP_STATE_EXTRA_FIELDS) {
+ Buf.readInt32();
+ Buf.readInt32();
+ Buf.readInt32();
+ Buf.readInt32();
+ }
+ }
+
+ if (DEBUG) this.context.debug("iccStatus: " + JSON.stringify(iccStatus));
+ this._processICCStatus(iccStatus);
+};
+RilObject.prototype[REQUEST_ENTER_SIM_PIN] = function REQUEST_ENTER_SIM_PIN(length, options) {
+ this._processEnterAndChangeICCResponses(length, options);
+};
+RilObject.prototype[REQUEST_ENTER_SIM_PUK] = function REQUEST_ENTER_SIM_PUK(length, options) {
+ this._processEnterAndChangeICCResponses(length, options);
+};
+RilObject.prototype[REQUEST_ENTER_SIM_PIN2] = function REQUEST_ENTER_SIM_PIN2(length, options) {
+ this._processEnterAndChangeICCResponses(length, options);
+};
+RilObject.prototype[REQUEST_ENTER_SIM_PUK2] = function REQUEST_ENTER_SIM_PUK(length, options) {
+ this._processEnterAndChangeICCResponses(length, options);
+};
+RilObject.prototype[REQUEST_CHANGE_SIM_PIN] = function REQUEST_CHANGE_SIM_PIN(length, options) {
+ this._processEnterAndChangeICCResponses(length, options);
+};
+RilObject.prototype[REQUEST_CHANGE_SIM_PIN2] = function REQUEST_CHANGE_SIM_PIN2(length, options) {
+ this._processEnterAndChangeICCResponses(length, options);
+};
+RilObject.prototype[REQUEST_ENTER_NETWORK_DEPERSONALIZATION_CODE] =
+ function REQUEST_ENTER_NETWORK_DEPERSONALIZATION_CODE(length, options) {
+ this._processEnterAndChangeICCResponses(length, options);
+};
+RilObject.prototype[REQUEST_GET_CURRENT_CALLS] = function REQUEST_GET_CURRENT_CALLS(length, options) {
+ // Retry getCurrentCalls several times when error occurs.
+ if (options.errorMsg) {
+ if (this._getCurrentCallsRetryCount < GET_CURRENT_CALLS_RETRY_MAX) {
+ this._getCurrentCallsRetryCount++;
+ this.getCurrentCalls(options);
+ } else {
+ this.sendDefaultResponse(options);
+ }
+ return;
+ }
+
+ this._getCurrentCallsRetryCount = 0;
+
+ let Buf = this.context.Buf;
+ let calls_length = 0;
+ // The RIL won't even send us the length integer if there are no active calls.
+ // So only read this integer if the parcel actually has it.
+ if (length) {
+ calls_length = Buf.readInt32();
+ }
+
+ let calls = {};
+ for (let i = 0; i < calls_length; i++) {
+ let call = {};
+
+ // Extra uint32 field to get correct callIndex and rest of call data for
+ // call waiting feature.
+ if (RILQUIRKS_EXTRA_UINT32_2ND_CALL && i > 0) {
+ Buf.readInt32();
+ }
+
+ call.state = Buf.readInt32(); // CALL_STATE_*
+ call.callIndex = Buf.readInt32(); // GSM index (1-based)
+ call.toa = Buf.readInt32();
+ call.isMpty = Boolean(Buf.readInt32());
+ call.isMT = Boolean(Buf.readInt32());
+ call.als = Buf.readInt32();
+ call.isVoice = Boolean(Buf.readInt32());
+ call.isVoicePrivacy = Boolean(Buf.readInt32());
+ if (RILQUIRKS_CALLSTATE_EXTRA_UINT32) {
+ Buf.readInt32();
+ }
+ call.number = Buf.readString();
+ call.numberPresentation = Buf.readInt32(); // CALL_PRESENTATION_*
+ call.name = Buf.readString();
+ call.namePresentation = Buf.readInt32();
+
+ call.uusInfo = null;
+ let uusInfoPresent = Buf.readInt32();
+ if (uusInfoPresent == 1) {
+ call.uusInfo = {
+ type: Buf.readInt32(),
+ dcs: Buf.readInt32(),
+ userData: null //XXX TODO byte array?!?
+ };
+ }
+
+ if (call.isVoice) {
+ calls[call.callIndex] = call;
+ }
+ }
+
+ options.calls = calls;
+ options.rilMessageType = options.rilMessageType || "currentCalls";
+ this.sendChromeMessage(options);
+};
+RilObject.prototype[REQUEST_DIAL] = function REQUEST_DIAL(length, options) {
+ this.sendDefaultResponse(options);
+};
+RilObject.prototype[REQUEST_DIAL_EMERGENCY_CALL] = function REQUEST_DIAL_EMERGENCY_CALL(length, options) {
+ RilObject.prototype[REQUEST_DIAL].call(this, length, options);
+};
+RilObject.prototype[REQUEST_GET_IMSI] = function REQUEST_GET_IMSI(length, options) {
+ if (options.errorMsg) {
+ return;
+ }
+
+ this.iccInfoPrivate.imsi = this.context.Buf.readString();
+ if (DEBUG) {
+ this.context.debug("IMSI: " + this.iccInfoPrivate.imsi);
+ }
+
+ options.rilMessageType = "iccimsi";
+ options.imsi = this.iccInfoPrivate.imsi;
+ this.sendChromeMessage(options);
+};
+RilObject.prototype[REQUEST_HANGUP] = function REQUEST_HANGUP(length, options) {
+ this.sendDefaultResponse(options);
+};
+RilObject.prototype[REQUEST_HANGUP_WAITING_OR_BACKGROUND] = function REQUEST_HANGUP_WAITING_OR_BACKGROUND(length, options) {
+ this.sendDefaultResponse(options);
+};
+RilObject.prototype[REQUEST_HANGUP_FOREGROUND_RESUME_BACKGROUND] = function REQUEST_HANGUP_FOREGROUND_RESUME_BACKGROUND(length, options) {
+ this.sendDefaultResponse(options);
+};
+RilObject.prototype[REQUEST_SWITCH_WAITING_OR_HOLDING_AND_ACTIVE] = function REQUEST_SWITCH_WAITING_OR_HOLDING_AND_ACTIVE(length, options) {
+ this.sendDefaultResponse(options);
+};
+RilObject.prototype[REQUEST_CONFERENCE] = function REQUEST_CONFERENCE(length, options) {
+ this.sendDefaultResponse(options);
+};
+RilObject.prototype[REQUEST_UDUB] = function REQUEST_UDUB(length, options) {
+ this.sendDefaultResponse(options);
+};
+RilObject.prototype[REQUEST_LAST_CALL_FAIL_CAUSE] = function REQUEST_LAST_CALL_FAIL_CAUSE(length, options) {
+ // Treat it as CALL_FAIL_ERROR_UNSPECIFIED if the request failed.
+ let failCause = CALL_FAIL_ERROR_UNSPECIFIED;
+
+ if (!options.errorMsg) {
+ let Buf = this.context.Buf;
+ let num = length ? Buf.readInt32() : 0;
+
+ if (num) {
+ let causeNum = Buf.readInt32();
+ failCause = RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[causeNum] || failCause;
+ }
+ if (DEBUG) this.context.debug("Last call fail cause: " + failCause);
+ }
+
+ options.failCause = failCause;
+ this.sendChromeMessage(options);
+};
+RilObject.prototype[REQUEST_SIGNAL_STRENGTH] = function REQUEST_SIGNAL_STRENGTH(length, options) {
+ this._receivedNetworkInfo(NETWORK_INFO_SIGNAL);
+
+ if (options.errorMsg) {
+ return;
+ }
+
+ let Buf = this.context.Buf;
+ let signal = {};
+
+ signal.gsmSignalStrength = Buf.readInt32();
+ signal.gsmBitErrorRate = Buf.readInt32();
+ if (RILQUIRKS_SIGNAL_EXTRA_INT32) {
+ Buf.readInt32();
+ }
+ signal.cdmaDBM = Buf.readInt32();
+ signal.cdmaECIO = Buf.readInt32();
+ signal.evdoDBM = Buf.readInt32();
+ signal.evdoECIO = Buf.readInt32();
+ signal.evdoSNR = Buf.readInt32();
+
+ signal.lteSignalStrength = Buf.readInt32();
+ signal.lteRSRP = Buf.readInt32();
+ signal.lteRSRQ = Buf.readInt32();
+ signal.lteRSSNR = Buf.readInt32();
+ signal.lteCQI = Buf.readInt32();
+
+ if (DEBUG) this.context.debug("signal strength: " + JSON.stringify(signal));
+
+ this._processSignalStrength(signal);
+};
+RilObject.prototype[REQUEST_VOICE_REGISTRATION_STATE] = function REQUEST_VOICE_REGISTRATION_STATE(length, options) {
+ this._receivedNetworkInfo(NETWORK_INFO_VOICE_REGISTRATION_STATE);
+
+ if (options.errorMsg) {
+ return;
+ }
+
+ let state = this.context.Buf.readStringList();
+ if (DEBUG) this.context.debug("voice registration state: " + state);
+
+ this._processVoiceRegistrationState(state);
+};
+RilObject.prototype[REQUEST_DATA_REGISTRATION_STATE] = function REQUEST_DATA_REGISTRATION_STATE(length, options) {
+ this._receivedNetworkInfo(NETWORK_INFO_DATA_REGISTRATION_STATE);
+
+ if (options.errorMsg) {
+ return;
+ }
+
+ let state = this.context.Buf.readStringList();
+ this._processDataRegistrationState(state);
+};
+RilObject.prototype[REQUEST_OPERATOR] = function REQUEST_OPERATOR(length, options) {
+ this._receivedNetworkInfo(NETWORK_INFO_OPERATOR);
+
+ if (options.errorMsg) {
+ return;
+ }
+
+ let operatorData = this.context.Buf.readStringList();
+ if (DEBUG) this.context.debug("Operator: " + operatorData);
+ this._processOperator(operatorData);
+};
+RilObject.prototype[REQUEST_RADIO_POWER] = function REQUEST_RADIO_POWER(length, options) {
+ this.sendDefaultResponse(options);
+};
+RilObject.prototype[REQUEST_DTMF] = null;
+RilObject.prototype[REQUEST_SEND_SMS] = function REQUEST_SEND_SMS(length, options) {
+ this._processSmsSendResult(length, options);
+};
+RilObject.prototype[REQUEST_SEND_SMS_EXPECT_MORE] = null;
+
+RilObject.prototype[REQUEST_SETUP_DATA_CALL] = function REQUEST_SETUP_DATA_CALL(length, options) {
+ if (options.errorMsg) {
+ this.sendChromeMessage(options);
+ return;
+ }
+
+ let Buf = this.context.Buf;
+ let version = Buf.readInt32();
+ // Skip number of data calls.
+ Buf.readInt32();
+
+ this.readDataCall(options, version);
+ this.sendChromeMessage(options);
+};
+RilObject.prototype[REQUEST_SIM_IO] = function REQUEST_SIM_IO(length, options) {
+ if (options.errorMsg) {
+ if (options.onerror) {
+ options.onerror(options.errorMsg);
+ }
+ return;
+ }
+
+ let Buf = this.context.Buf;
+ options.sw1 = Buf.readInt32();
+ options.sw2 = Buf.readInt32();
+
+ // See 3GPP TS 11.11, clause 9.4.1 for operation success results.
+ if (options.sw1 !== ICC_STATUS_NORMAL_ENDING &&
+ options.sw1 !== ICC_STATUS_NORMAL_ENDING_WITH_EXTRA &&
+ options.sw1 !== ICC_STATUS_WITH_SIM_DATA &&
+ options.sw1 !== ICC_STATUS_WITH_RESPONSE_DATA) {
+ if (DEBUG) {
+ this.context.debug("ICC I/O Error EF id = 0x" + options.fileId.toString(16) +
+ ", command = 0x" + options.command.toString(16) +
+ ", sw1 = 0x" + options.sw1.toString(16) +
+ ", sw2 = 0x" + options.sw2.toString(16));
+ }
+ if (options.onerror) {
+ // We can get fail cause from sw1/sw2 (See TS 11.11 clause 9.4.1 and
+ // ISO 7816-4 clause 6). But currently no one needs this information,
+ // so simply reports "GenericFailure" for now.
+ options.onerror(GECKO_ERROR_GENERIC_FAILURE);
+ }
+ return;
+ }
+ this.context.ICCIOHelper.processICCIO(options);
+};
+RilObject.prototype[REQUEST_SEND_USSD] = function REQUEST_SEND_USSD(length, options) {
+ if (DEBUG) {
+ this.context.debug("REQUEST_SEND_USSD " + JSON.stringify(options));
+ }
+ this.sendChromeMessage(options);
+};
+RilObject.prototype[REQUEST_CANCEL_USSD] = function REQUEST_CANCEL_USSD(length, options) {
+ if (DEBUG) {
+ this.context.debug("REQUEST_CANCEL_USSD" + JSON.stringify(options));
+ }
+ this.sendChromeMessage(options);
+};
+RilObject.prototype[REQUEST_GET_CLIR] = function REQUEST_GET_CLIR(length, options) {
+ if (options.errorMsg) {
+ this.sendChromeMessage(options);
+ return;
+ }
+
+ let Buf = this.context.Buf;
+ let bufLength = Buf.readInt32();
+ if (!bufLength || bufLength < 2) {
+ options.errorMsg = GECKO_ERROR_GENERIC_FAILURE;
+ this.sendChromeMessage(options);
+ return;
+ }
+
+ options.n = Buf.readInt32(); // Will be TS 27.007 +CLIR parameter 'n'.
+ options.m = Buf.readInt32(); // Will be TS 27.007 +CLIR parameter 'm'.
+ this.sendChromeMessage(options);
+};
+RilObject.prototype[REQUEST_SET_CLIR] = function REQUEST_SET_CLIR(length, options) {
+ if (options.rilMessageType == null) {
+ // The request was made by ril_worker itself automatically. Don't report.
+ return;
+ }
+
+ this.sendChromeMessage(options);
+};
+
+RilObject.prototype[REQUEST_QUERY_CALL_FORWARD_STATUS] =
+ function REQUEST_QUERY_CALL_FORWARD_STATUS(length, options) {
+ if (options.errorMsg) {
+ this.sendChromeMessage(options);
+ return;
+ }
+
+ let Buf = this.context.Buf;
+ let rulesLength = 0;
+ if (length) {
+ rulesLength = Buf.readInt32();
+ }
+ if (!rulesLength) {
+ options.errorMsg = GECKO_ERROR_GENERIC_FAILURE;
+ this.sendChromeMessage(options);
+ return;
+ }
+ let rules = new Array(rulesLength);
+ for (let i = 0; i < rulesLength; i++) {
+ let rule = {};
+ rule.active = Buf.readInt32() == 1; // CALL_FORWARD_STATUS_*
+ rule.reason = Buf.readInt32(); // CALL_FORWARD_REASON_*
+ rule.serviceClass = Buf.readInt32();
+ rule.toa = Buf.readInt32();
+ rule.number = Buf.readString();
+ rule.timeSeconds = Buf.readInt32();
+ rules[i] = rule;
+ }
+ options.rules = rules;
+ this.sendChromeMessage(options);
+};
+RilObject.prototype[REQUEST_SET_CALL_FORWARD] =
+ function REQUEST_SET_CALL_FORWARD(length, options) {
+ this.sendChromeMessage(options);
+};
+RilObject.prototype[REQUEST_QUERY_CALL_WAITING] =
+ function REQUEST_QUERY_CALL_WAITING(length, options) {
+ if (options.errorMsg) {
+ this.sendChromeMessage(options);
+ return;
+ }
+
+ let Buf = this.context.Buf;
+ let results = Buf.readInt32List();
+ let enabled = (results[0] === 1);
+ options.serviceClass = enabled ? results[1] : ICC_SERVICE_CLASS_NONE;
+ this.sendChromeMessage(options);
+};
+
+RilObject.prototype[REQUEST_SET_CALL_WAITING] = function REQUEST_SET_CALL_WAITING(length, options) {
+ this.sendChromeMessage(options);
+};
+RilObject.prototype[REQUEST_SMS_ACKNOWLEDGE] = null;
+RilObject.prototype[REQUEST_GET_IMEI] = null;
+RilObject.prototype[REQUEST_GET_IMEISV] = null;
+RilObject.prototype[REQUEST_ANSWER] = function REQUEST_ANSWER(length, options) {
+ this.sendDefaultResponse(options);
+};
+RilObject.prototype[REQUEST_DEACTIVATE_DATA_CALL] = function REQUEST_DEACTIVATE_DATA_CALL(length, options) {
+ this.sendChromeMessage(options);
+};
+RilObject.prototype[REQUEST_QUERY_FACILITY_LOCK] = function REQUEST_QUERY_FACILITY_LOCK(length, options) {
+ if (options.errorMsg) {
+ this.sendChromeMessage(options);
+ return;
+ }
+
+ if (!length) {
+ options.errorMsg = GECKO_ERROR_GENERIC_FAILURE;
+ this.sendChromeMessage(options);
+ return;
+ }
+
+ // Buf.readInt32List()[0] for Call Barring is a bit vector of services.
+ options.serviceClass = this.context.Buf.readInt32List()[0];
+ if (options.queryServiceClass) {
+ options.enabled = (options.serviceClass & options.queryServiceClass) ? true : false;
+ options.serviceClass = options.queryServiceClass;
+ } else {
+ options.enabled = options.serviceClass ? true : false;
+ }
+
+ this.sendChromeMessage(options);
+};
+RilObject.prototype[REQUEST_SET_FACILITY_LOCK] = function REQUEST_SET_FACILITY_LOCK(length, options) {
+ options.retryCount = length ? this.context.Buf.readInt32List()[0] : -1;
+ this.sendChromeMessage(options);
+};
+RilObject.prototype[REQUEST_CHANGE_BARRING_PASSWORD] =
+ function REQUEST_CHANGE_BARRING_PASSWORD(length, options) {
+ this.sendChromeMessage(options);
+};
+RilObject.prototype[REQUEST_QUERY_NETWORK_SELECTION_MODE] = function REQUEST_QUERY_NETWORK_SELECTION_MODE(length, options) {
+ this._receivedNetworkInfo(NETWORK_INFO_NETWORK_SELECTION_MODE);
+
+ if (options.errorMsg) {
+ return;
+ }
+
+ let mode = this.context.Buf.readInt32List();
+ let selectionMode;
+
+ switch (mode[0]) {
+ case NETWORK_SELECTION_MODE_AUTOMATIC:
+ selectionMode = GECKO_NETWORK_SELECTION_AUTOMATIC;
+ break;
+ case NETWORK_SELECTION_MODE_MANUAL:
+ selectionMode = GECKO_NETWORK_SELECTION_MANUAL;
+ break;
+ default:
+ selectionMode = GECKO_NETWORK_SELECTION_UNKNOWN;
+ break;
+ }
+
+ this._updateNetworkSelectionMode(selectionMode);
+};
+RilObject.prototype[REQUEST_SET_NETWORK_SELECTION_AUTOMATIC] = function REQUEST_SET_NETWORK_SELECTION_AUTOMATIC(length, options) {
+ if (!options.errorMsg) {
+ this._updateNetworkSelectionMode(GECKO_NETWORK_SELECTION_AUTOMATIC);
+ }
+ this.sendChromeMessage(options);
+};
+RilObject.prototype[REQUEST_SET_NETWORK_SELECTION_MANUAL] = function REQUEST_SET_NETWORK_SELECTION_MANUAL(length, options) {
+ if (!options.errorMsg) {
+ this._updateNetworkSelectionMode(GECKO_NETWORK_SELECTION_MANUAL);
+ }
+ this.sendChromeMessage(options);
+};
+RilObject.prototype[REQUEST_QUERY_AVAILABLE_NETWORKS] = function REQUEST_QUERY_AVAILABLE_NETWORKS(length, options) {
+ if (!options.errorMsg) {
+ options.networks = this._processNetworks();
+ }
+ this.sendChromeMessage(options);
+};
+RilObject.prototype[REQUEST_DTMF_START] = function REQUEST_DTMF_START(length, options) {
+ this.sendChromeMessage(options);
+};
+RilObject.prototype[REQUEST_DTMF_STOP] = null;
+RilObject.prototype[REQUEST_BASEBAND_VERSION] = function REQUEST_BASEBAND_VERSION(length, options) {
+ if (options.errorMsg) {
+ return;
+ }
+
+ this.basebandVersion = this.context.Buf.readString();
+ if (DEBUG) this.context.debug("Baseband version: " + this.basebandVersion);
+};
+RilObject.prototype[REQUEST_SEPARATE_CONNECTION] = function REQUEST_SEPARATE_CONNECTION(length, options) {
+ this.sendDefaultResponse(options);
+};
+RilObject.prototype[REQUEST_SET_MUTE] = null;
+RilObject.prototype[REQUEST_GET_MUTE] = null;
+RilObject.prototype[REQUEST_QUERY_CLIP] = function REQUEST_QUERY_CLIP(length, options) {
+ if (options.errorMsg) {
+ this.sendChromeMessage(options);
+ return;
+ }
+
+ let Buf = this.context.Buf;
+ let bufLength = Buf.readInt32();
+ if (!bufLength) {
+ options.errorMsg = GECKO_ERROR_GENERIC_FAILURE;
+ this.sendChromeMessage(options);
+ return;
+ }
+
+ options.provisioned = Buf.readInt32();
+ this.sendChromeMessage(options);
+};
+RilObject.prototype[REQUEST_LAST_DATA_CALL_FAIL_CAUSE] = null;
+
+/**
+ * V6:
+ * # addresses - A space-delimited list of addresses with optional "/" prefix
+ * length.
+ * # dnses - A space-delimited list of DNS server addresses.
+ * # gateways - A space-delimited list of default gateway addresses.
+ *
+ * V10:
+ * # pcscf - A space-delimited list of Proxy Call State Control Function
+ * addresses.
+ */
+
+RilObject.prototype.readDataCall = function(options, version) {
+ if (!options) {
+ options = {};
+ }
+ let Buf = this.context.Buf;
+ options.failCause = Buf.readInt32(); // DATACALL_FAIL_*
+ options.suggestedRetryTime = Buf.readInt32();
+ options.cid = Buf.readInt32().toString();
+ options.active = Buf.readInt32(); // DATACALL_ACTIVE_*
+ options.type = Buf.readString();
+ options.ifname = Buf.readString();
+ options.addresses = Buf.readString();
+ options.dnses = Buf.readString();
+ options.gateways = Buf.readString();
+
+ if (version >= 10) {
+ options.pcscf = Buf.readString();
+ }
+
+ if (version >= 11) {
+ let mtu = Buf.readInt32();
+ options.mtu = (mtu > 0) ? mtu : -1 ;
+ }
+
+ return options;
+};
+
+RilObject.prototype[REQUEST_DATA_CALL_LIST] = function REQUEST_DATA_CALL_LIST(length, options) {
+ if (options.errorMsg) {
+ if (options.rilMessageType) {
+ this.sendChromeMessage(options);
+ }
+ return;
+ }
+
+ if (!options.rilMessageType) {
+ // This is an unsolicited data call list changed.
+ options.rilMessageType = "datacalllistchanged";
+ }
+
+ if (!length) {
+ options.datacalls = [];
+ this.sendChromeMessage(options);
+ return;
+ }
+
+ let Buf = this.context.Buf;
+ let version = Buf.readInt32();
+ let num = Buf.readInt32();
+ let datacalls = [];
+ for (let i = 0; i < num; i++) {
+ let datacall;
+ datacall = this.readDataCall({}, version);
+ datacalls.push(datacall);
+ }
+
+ options.datacalls = datacalls;
+ this.sendChromeMessage(options);
+};
+RilObject.prototype[REQUEST_RESET_RADIO] = null;
+RilObject.prototype[REQUEST_OEM_HOOK_RAW] = null;
+RilObject.prototype[REQUEST_OEM_HOOK_STRINGS] = null;
+RilObject.prototype[REQUEST_SCREEN_STATE] = null;
+RilObject.prototype[REQUEST_SET_SUPP_SVC_NOTIFICATION] = null;
+RilObject.prototype[REQUEST_WRITE_SMS_TO_SIM] = function REQUEST_WRITE_SMS_TO_SIM(length, options) {
+ if (options.errorMsg) {
+ // `The MS shall return a "protocol error, unspecified" error message if
+ // the short message cannot be stored in the (U)SIM, and there is other
+ // message storage available at the MS` ~ 3GPP TS 23.038 section 4. Here
+ // we assume we always have indexed db as another storage.
+ this.acknowledgeGsmSms(false, PDU_FCS_PROTOCOL_ERROR);
+ } else {
+ this.acknowledgeGsmSms(true, PDU_FCS_OK);
+ }
+};
+RilObject.prototype[REQUEST_DELETE_SMS_ON_SIM] = null;
+RilObject.prototype[REQUEST_SET_BAND_MODE] = null;
+RilObject.prototype[REQUEST_QUERY_AVAILABLE_BAND_MODE] = null;
+RilObject.prototype[REQUEST_STK_GET_PROFILE] = null;
+RilObject.prototype[REQUEST_STK_SET_PROFILE] = null;
+RilObject.prototype[REQUEST_STK_SEND_ENVELOPE_COMMAND] = null;
+RilObject.prototype[REQUEST_STK_SEND_TERMINAL_RESPONSE] = null;
+RilObject.prototype[REQUEST_STK_HANDLE_CALL_SETUP_REQUESTED_FROM_SIM] = null;
+RilObject.prototype[REQUEST_EXPLICIT_CALL_TRANSFER] = null;
+RilObject.prototype[REQUEST_SET_PREFERRED_NETWORK_TYPE] = function REQUEST_SET_PREFERRED_NETWORK_TYPE(length, options) {
+ this.sendChromeMessage(options);
+};
+RilObject.prototype[REQUEST_GET_PREFERRED_NETWORK_TYPE] = function REQUEST_GET_PREFERRED_NETWORK_TYPE(length, options) {
+ if (options.errorMsg) {
+ this.sendChromeMessage(options);
+ return;
+ }
+
+ options.type = this.context.Buf.readInt32List()[0];
+ this.sendChromeMessage(options);
+};
+RilObject.prototype[REQUEST_GET_NEIGHBORING_CELL_IDS] = function REQUEST_GET_NEIGHBORING_CELL_IDS(length, options) {
+ if (options.errorMsg) {
+ this.sendChromeMessage(options);
+ return;
+ }
+
+ let radioTech = this.voiceRegistrationState.radioTech;
+ if (radioTech == undefined || radioTech == NETWORK_CREG_TECH_UNKNOWN) {
+ options.errorMsg = "RadioTechUnavailable";
+ this.sendChromeMessage(options);
+ return;
+ }
+ if (!this._isGsmTechGroup(radioTech) || radioTech == NETWORK_CREG_TECH_LTE) {
+ options.errorMsg = "UnsupportedRadioTech";
+ this.sendChromeMessage(options);
+ return;
+ }
+
+ let Buf = this.context.Buf;
+ let neighboringCellIds = [];
+ let num = Buf.readInt32();
+
+ for (let i = 0; i < num; i++) {
+ let cellId = {};
+ cellId.networkType = GECKO_RADIO_TECH[radioTech];
+ cellId.signalStrength = Buf.readInt32();
+
+ let cid = Buf.readString();
+ // pad cid string with leading "0"
+ let length = cid.length;
+ if (length > 8) {
+ continue;
+ }
+ if (length < 8) {
+ for (let j = 0; j < (8-length); j++) {
+ cid = "0" + cid;
+ }
+ }
+
+ switch (radioTech) {
+ case NETWORK_CREG_TECH_GPRS:
+ case NETWORK_CREG_TECH_EDGE:
+ case NETWORK_CREG_TECH_GSM:
+ cellId.gsmCellId = this.parseInt(cid.substring(4), -1, 16);
+ cellId.gsmLocationAreaCode = this.parseInt(cid.substring(0, 4), -1, 16);
+ break;
+ case NETWORK_CREG_TECH_UMTS:
+ case NETWORK_CREG_TECH_HSDPA:
+ case NETWORK_CREG_TECH_HSUPA:
+ case NETWORK_CREG_TECH_HSPA:
+ case NETWORK_CREG_TECH_HSPAP:
+ case NETWORK_CREG_TECH_DCHSPAP_1:
+ case NETWORK_CREG_TECH_DCHSPAP_2:
+ cellId.wcdmaPsc = this.parseInt(cid, -1, 16);
+ break;
+ }
+
+ neighboringCellIds.push(cellId);
+ }
+
+ options.result = neighboringCellIds;
+ this.sendChromeMessage(options);
+};
+RilObject.prototype[REQUEST_GET_CELL_INFO_LIST] = function REQUEST_GET_CELL_INFO_LIST(length, options) {
+ if (options.errorMsg) {
+ this.sendChromeMessage(options);
+ return;
+ }
+
+ let Buf = this.context.Buf;
+ let cellInfoList = [];
+ let num = Buf.readInt32();
+ for (let i = 0; i < num; i++) {
+ let cellInfo = {};
+ cellInfo.type = Buf.readInt32();
+ cellInfo.registered = Buf.readInt32() ? true : false;
+ cellInfo.timestampType = Buf.readInt32();
+ cellInfo.timestamp = Buf.readInt64();
+
+ switch(cellInfo.type) {
+ case CELL_INFO_TYPE_GSM:
+ case CELL_INFO_TYPE_WCDMA:
+ cellInfo.mcc = Buf.readInt32();
+ cellInfo.mnc = Buf.readInt32();
+ cellInfo.lac = Buf.readInt32();
+ cellInfo.cid = Buf.readInt32();
+ if (cellInfo.type == CELL_INFO_TYPE_WCDMA) {
+ cellInfo.psc = Buf.readInt32();
+ }
+ cellInfo.signalStrength = Buf.readInt32();
+ cellInfo.bitErrorRate = Buf.readInt32();
+ break;
+ case CELL_INFO_TYPE_CDMA:
+ cellInfo.networkId = Buf.readInt32();
+ cellInfo.systemId = Buf.readInt32();
+ cellInfo.basestationId = Buf.readInt32();
+ cellInfo.longitude = Buf.readInt32();
+ cellInfo.latitude = Buf.readInt32();
+ cellInfo.cdmaDbm = Buf.readInt32();
+ cellInfo.cdmaEcio = Buf.readInt32();
+ cellInfo.evdoDbm = Buf.readInt32();
+ cellInfo.evdoEcio = Buf.readInt32();
+ cellInfo.evdoSnr = Buf.readInt32();
+ break;
+ case CELL_INFO_TYPE_LTE:
+ cellInfo.mcc = Buf.readInt32();
+ cellInfo.mnc = Buf.readInt32();
+ cellInfo.cid = Buf.readInt32();
+ cellInfo.pcid = Buf.readInt32();
+ cellInfo.tac = Buf.readInt32();
+ cellInfo.signalStrength = Buf.readInt32();
+ cellInfo.rsrp = Buf.readInt32();
+ cellInfo.rsrq = Buf.readInt32();
+ cellInfo.rssnr = Buf.readInt32();
+ cellInfo.cqi = Buf.readInt32();
+ break;
+ }
+ cellInfoList.push(cellInfo);
+ }
+ options.result = cellInfoList;
+ this.sendChromeMessage(options);
+};
+RilObject.prototype[REQUEST_SET_LOCATION_UPDATES] = null;
+RilObject.prototype[REQUEST_CDMA_SET_SUBSCRIPTION_SOURCE] = null;
+RilObject.prototype[REQUEST_CDMA_SET_ROAMING_PREFERENCE] = function REQUEST_CDMA_SET_ROAMING_PREFERENCE(length, options) {
+ this.sendChromeMessage(options);
+};
+RilObject.prototype[REQUEST_CDMA_QUERY_ROAMING_PREFERENCE] = function REQUEST_CDMA_QUERY_ROAMING_PREFERENCE(length, options) {
+ if (!options.errorMsg) {
+ options.mode = this.context.Buf.readInt32List()[0];
+ }
+ this.sendChromeMessage(options);
+};
+RilObject.prototype[REQUEST_SET_TTY_MODE] = null;
+RilObject.prototype[REQUEST_QUERY_TTY_MODE] = null;
+RilObject.prototype[REQUEST_CDMA_SET_PREFERRED_VOICE_PRIVACY_MODE] = function REQUEST_CDMA_SET_PREFERRED_VOICE_PRIVACY_MODE(length, options) {
+ this.sendChromeMessage(options);
+};
+RilObject.prototype[REQUEST_CDMA_QUERY_PREFERRED_VOICE_PRIVACY_MODE] = function REQUEST_CDMA_QUERY_PREFERRED_VOICE_PRIVACY_MODE(length, options) {
+ if (options.errorMsg) {
+ this.sendChromeMessage(options);
+ return;
+ }
+
+ let enabled = this.context.Buf.readInt32List();
+ options.enabled = enabled[0] ? true : false;
+ this.sendChromeMessage(options);
+};
+RilObject.prototype[REQUEST_CDMA_FLASH] = function REQUEST_CDMA_FLASH(length, options) {
+ this.sendDefaultResponse(options);
+};
+RilObject.prototype[REQUEST_CDMA_BURST_DTMF] = null;
+RilObject.prototype[REQUEST_CDMA_VALIDATE_AND_WRITE_AKEY] = null;
+RilObject.prototype[REQUEST_CDMA_SEND_SMS] = function REQUEST_CDMA_SEND_SMS(length, options) {
+ this._processSmsSendResult(length, options);
+};
+RilObject.prototype[REQUEST_CDMA_SMS_ACKNOWLEDGE] = null;
+RilObject.prototype[REQUEST_GSM_GET_BROADCAST_SMS_CONFIG] = null;
+RilObject.prototype[REQUEST_GSM_SET_BROADCAST_SMS_CONFIG] = function REQUEST_GSM_SET_BROADCAST_SMS_CONFIG(length, options) {
+ if (options.errorMsg) {
+ return;
+ }
+ this.setSmsBroadcastActivation(true);
+};
+RilObject.prototype[REQUEST_GSM_SMS_BROADCAST_ACTIVATION] = null;
+RilObject.prototype[REQUEST_CDMA_GET_BROADCAST_SMS_CONFIG] = null;
+RilObject.prototype[REQUEST_CDMA_SET_BROADCAST_SMS_CONFIG] = function REQUEST_CDMA_SET_BROADCAST_SMS_CONFIG(length, options) {
+ if (options.errorMsg) {
+ return;
+ }
+ this.setSmsBroadcastActivation(true);
+};
+RilObject.prototype[REQUEST_CDMA_SMS_BROADCAST_ACTIVATION] = null;
+RilObject.prototype[REQUEST_CDMA_SUBSCRIPTION] = function REQUEST_CDMA_SUBSCRIPTION(length, options) {
+ if (options.errorMsg) {
+ return;
+ }
+
+ let result = this.context.Buf.readStringList();
+
+ this.iccInfo.mdn = result[0];
+ // The result[1] is Home SID. (Already be handled in readCDMAHome())
+ // The result[2] is Home NID. (Already be handled in readCDMAHome())
+ // The result[3] is MIN.
+ this.iccInfo.prlVersion = parseInt(result[4], 10);
+
+ this.context.ICCUtilsHelper.handleICCInfoChange();
+};
+RilObject.prototype[REQUEST_CDMA_WRITE_SMS_TO_RUIM] = null;
+RilObject.prototype[REQUEST_CDMA_DELETE_SMS_ON_RUIM] = null;
+RilObject.prototype[REQUEST_DEVICE_IDENTITY] = function REQUEST_DEVICE_IDENTITY(length, options) {
+ if (options.errorMsg) {
+ this.context.debug("Failed to get device identities:" + options.errorMsg);
+ return;
+ }
+
+ let result = this.context.Buf.readStringList();
+ this.deviceIdentities = {
+ imei: result[0] || null,
+ imeisv: result[1] || null,
+ esn: result[2] || null,
+ meid: result[3] || null,
+ };
+
+ this.sendChromeMessage({
+ rilMessageType: "deviceidentitieschange",
+ deviceIdentities: this.deviceIdentities
+ });
+};
+RilObject.prototype[REQUEST_EXIT_EMERGENCY_CALLBACK_MODE] = function REQUEST_EXIT_EMERGENCY_CALLBACK_MODE(length, options) {
+ if (options.internal) {
+ return;
+ }
+
+ this.sendChromeMessage(options);
+};
+RilObject.prototype[REQUEST_GET_SMSC_ADDRESS] = function REQUEST_GET_SMSC_ADDRESS(length, options) {
+ if (!options.rilMessageType || options.rilMessageType !== "getSmscAddress") {
+ return;
+ }
+
+ if (options.errorMsg) {
+ this.sendChromeMessage(options);
+ return;
+ }
+
+ let tosca = TOA_UNKNOWN;
+ let smsc = "";
+ let Buf = this.context.Buf;
+ if (RILQUIRKS_SMSC_ADDRESS_FORMAT === "pdu") {
+ let pduHelper = this.context.GsmPDUHelper;
+ let strlen = Buf.readInt32();
+ let length = pduHelper.readHexOctet();
+
+ // As defined in |8.2.5.2 Destination address element| of 3GPP TS 24.011,
+ // the value of length field can not exceed 11. Since the content might be
+ // filled with 12 'F' when SMSC is cleared, we don't parse the TOA and
+ // address fields if reported length exceeds 11 here. Instead, keep the
+ // default value (TOA_UNKNOWN with an empty address) in this case.
+ const MAX_LENGTH = 11
+ if (length <= MAX_LENGTH) {
+ tosca = pduHelper.readHexOctet();
+
+ // Read and covert the decimal values back to special BCD digits defined in
+ // |Called party BCD number| of 3GPP TS 24.008 (refer the following table).
+ //
+ // +=========+=======+=====+
+ // | value | digit | hex |
+ // +========================
+ // | 1 0 1 0 | * | 0xA |
+ // | 1 0 1 1 | # | 0xB |
+ // | 1 1 0 0 | a | 0xC |
+ // | 1 1 0 1 | b | 0xD |
+ // | 1 1 1 0 | c | 0xE |
+ // +=========+=======+=====+
+ smsc = pduHelper.readSwappedNibbleBcdString(length - 1, true)
+ .replace(/a/ig, "*")
+ .replace(/b/ig, "#")
+ .replace(/c/ig, "a")
+ .replace(/d/ig, "b")
+ .replace(/e/ig, "c");
+
+ Buf.readStringDelimiter(strlen);
+ }
+ } else /* RILQUIRKS_SMSC_ADDRESS_FORMAT === "text" */ {
+ let text = Buf.readString();
+ let segments = text.split(",", 2);
+ // Parse TOA only if it presents since some devices might omit the TOA
+ // segment in the reported SMSC address. If TOA does not present, keep the
+ // default value TOA_UNKNOWN.
+ if (segments.length === 2) {
+ tosca = this.parseInt(segments[1], TOA_UNKNOWN, 10);
+ }
+
+ smsc = segments[0].replace(/\"/g, "");
+ }
+
+ // Convert the NPI value to the corresponding index of CALLED_PARTY_BCD_NPI
+ // array. If the value does not present in the array, use
+ // CALLED_PARTY_BCD_NPI_ISDN.
+ let npi = CALLED_PARTY_BCD_NPI.indexOf(tosca & 0xf);
+ if (npi === -1) {
+ npi = CALLED_PARTY_BCD_NPI.indexOf(CALLED_PARTY_BCD_NPI_ISDN);
+ }
+
+ // Extract TON.
+ let ton = (tosca & 0x70) >> 4;
+
+ // Ensure + sign if TON is international, and vice versa.
+ const TON_INTERNATIONAL = (TOA_INTERNATIONAL & 0x70) >> 4;
+ if (ton === TON_INTERNATIONAL && smsc.charAt(0) !== "+") {
+ smsc = "+" + smsc;
+ } else if (smsc.charAt(0) === "+" && ton !== TON_INTERNATIONAL) {
+ if (DEBUG) {
+ this.context.debug("SMSC address number begins with '+' while the TON is not international. Change TON to international.");
+ }
+ ton = TON_INTERNATIONAL;
+ }
+
+ options.smscAddress = smsc;
+ options.typeOfNumber = ton;
+ options.numberPlanIdentification = npi;
+ this.sendChromeMessage(options);
+};
+RilObject.prototype[REQUEST_SET_SMSC_ADDRESS] = function REQUEST_SET_SMSC_ADDRESS(length, options) {
+ if (!options.rilMessageType || options.rilMessageType !== "setSmscAddress") {
+ return;
+ }
+
+ this.sendChromeMessage(options);
+};
+RilObject.prototype[REQUEST_REPORT_SMS_MEMORY_STATUS] = function REQUEST_REPORT_SMS_MEMORY_STATUS(length, options) {
+ this.pendingToReportSmsMemoryStatus = !!options.errorMsg;
+};
+RilObject.prototype[REQUEST_REPORT_STK_SERVICE_IS_RUNNING] = null;
+RilObject.prototype[REQUEST_CDMA_GET_SUBSCRIPTION_SOURCE] = null;
+RilObject.prototype[REQUEST_ISIM_AUTHENTICATION] = null;
+RilObject.prototype[REQUEST_ACKNOWLEDGE_INCOMING_GSM_SMS_WITH_PDU] = null;
+RilObject.prototype[REQUEST_STK_SEND_ENVELOPE_WITH_STATUS] = function REQUEST_STK_SEND_ENVELOPE_WITH_STATUS(length, options) {
+ if (options.errorMsg) {
+ this.acknowledgeGsmSms(false, PDU_FCS_UNSPECIFIED);
+ return;
+ }
+
+ let Buf = this.context.Buf;
+ let sw1 = Buf.readInt32();
+ let sw2 = Buf.readInt32();
+ if ((sw1 == ICC_STATUS_SAT_BUSY) && (sw2 === 0x00)) {
+ this.acknowledgeGsmSms(false, PDU_FCS_USAT_BUSY);
+ return;
+ }
+
+ let success = ((sw1 == ICC_STATUS_NORMAL_ENDING) && (sw2 === 0x00))
+ || (sw1 == ICC_STATUS_NORMAL_ENDING_WITH_EXTRA);
+
+ let messageStringLength = Buf.readInt32(); // In semi-octets
+ let responsePduLen = messageStringLength / 2; // In octets
+ if (!responsePduLen) {
+ this.acknowledgeGsmSms(success, success ? PDU_FCS_OK
+ : PDU_FCS_USIM_DATA_DOWNLOAD_ERROR);
+ return;
+ }
+
+ this.acknowledgeIncomingGsmSmsWithPDU(success, responsePduLen, options);
+};
+RilObject.prototype[REQUEST_VOICE_RADIO_TECH] = function REQUEST_VOICE_RADIO_TECH(length, options) {
+ if (options.errorMsg) {
+ if (DEBUG) {
+ this.context.debug("Error when getting voice radio tech: " +
+ options.errorMsg);
+ }
+ return;
+ }
+ let radioTech = this.context.Buf.readInt32List();
+ this._processRadioTech(radioTech[0]);
+};
+RilObject.prototype[REQUEST_SET_UNSOL_CELL_INFO_LIST_RATE] = null;
+RilObject.prototype[REQUEST_SET_INITIAL_ATTACH_APN] = null;
+RilObject.prototype[REQUEST_IMS_REGISTRATION_STATE] = null;
+RilObject.prototype[REQUEST_IMS_SEND_SMS] = null;
+RilObject.prototype[REQUEST_SIM_TRANSMIT_APDU_BASIC] = null;
+RilObject.prototype[REQUEST_SIM_OPEN_CHANNEL] = function REQUEST_SIM_OPEN_CHANNEL(length, options) {
+ if (options.errorMsg) {
+ this.sendChromeMessage(options);
+ return;
+ }
+
+ options.channel = this.context.Buf.readInt32List()[0];
+ // onwards may optionally contain the select response for the open channel
+ // command with one byte per integer.
+ this.sendChromeMessage(options);
+};
+RilObject.prototype[REQUEST_SIM_CLOSE_CHANNEL] = function REQUEST_SIM_CLOSE_CHANNEL(length, options) {
+ this.sendDefaultResponse(options);
+};
+RilObject.prototype[REQUEST_SIM_TRANSMIT_APDU_CHANNEL] = function REQUEST_SIM_TRANSMIT_APDU_CHANNEL(length, options) {
+ if (options.errorMsg) {
+ this.sendChromeMessage(options);
+ return;
+ }
+
+ let Buf = this.context.Buf;
+ options.sw1 = Buf.readInt32();
+ options.sw2 = Buf.readInt32();
+ options.simResponse = Buf.readString();
+ if (DEBUG) {
+ this.context.debug("Setting return values for RIL[REQUEST_SIM_TRANSMIT_APDU_CHANNEL]: [" +
+ options.sw1 + "," +
+ options.sw2 + ", " +
+ options.simResponse + "]");
+ }
+ this.sendChromeMessage(options);
+};
+RilObject.prototype[REQUEST_NV_READ_ITEM] = null;
+RilObject.prototype[REQUEST_NV_WRITE_ITEM] = null;
+RilObject.prototype[REQUEST_NV_WRITE_CDMA_PRL] = null;
+RilObject.prototype[REQUEST_NV_RESET_CONFIG] = null;
+RilObject.prototype[REQUEST_SET_UICC_SUBSCRIPTION] = function REQUEST_SET_UICC_SUBSCRIPTION(length, options) {
+ // Resend data subscription after uicc subscription.
+ if (this._attachDataRegistration) {
+ this.setDataRegistration({attach: true});
+ }
+};
+RilObject.prototype[REQUEST_ALLOW_DATA] = null;
+RilObject.prototype[REQUEST_GET_HARDWARE_CONFIG] = null;
+RilObject.prototype[REQUEST_SIM_AUTHENTICATION] = null;
+RilObject.prototype[REQUEST_GET_DC_RT_INFO] = null;
+RilObject.prototype[REQUEST_SET_DC_RT_INFO_RATE] = null;
+RilObject.prototype[REQUEST_SET_DATA_PROFILE] = null;
+RilObject.prototype[REQUEST_SHUTDOWN] = null;
+RilObject.prototype[REQUEST_SET_DATA_SUBSCRIPTION] = function REQUEST_SET_DATA_SUBSCRIPTION(length, options) {
+ if (!options.rilMessageType) {
+ // The request was made by ril_worker itself. Don't report.
+ return;
+ }
+ this.sendChromeMessage(options);
+};
+RilObject.prototype[REQUEST_GET_UNLOCK_RETRY_COUNT] = function REQUEST_GET_UNLOCK_RETRY_COUNT(length, options) {
+ options.retryCount = length ? this.context.Buf.readInt32List()[0] : -1;
+ this.sendChromeMessage(options);
+};
+RilObject.prototype[RIL_REQUEST_GPRS_ATTACH] = function RIL_REQUEST_GPRS_ATTACH(length, options) {
+ if (!options.rilMessageType) {
+ // The request was made by ril_worker itself. Don't report.
+ return;
+ }
+ this.sendChromeMessage(options);
+};
+RilObject.prototype[RIL_REQUEST_GPRS_DETACH] = function RIL_REQUEST_GPRS_DETACH(length, options) {
+ this.sendChromeMessage(options);
+};
+RilObject.prototype[UNSOLICITED_RESPONSE_RADIO_STATE_CHANGED] = function UNSOLICITED_RESPONSE_RADIO_STATE_CHANGED() {
+ let radioState = this.context.Buf.readInt32();
+ let newState;
+ switch (radioState) {
+ case RADIO_STATE_UNAVAILABLE:
+ newState = GECKO_RADIOSTATE_UNKNOWN;
+ break;
+ case RADIO_STATE_OFF:
+ newState = GECKO_RADIOSTATE_DISABLED;
+ break;
+ default:
+ newState = GECKO_RADIOSTATE_ENABLED;
+ }
+
+ if (DEBUG) {
+ this.context.debug("Radio state changed from '" + this.radioState +
+ "' to '" + newState + "'");
+ }
+ if (this.radioState == newState) {
+ return;
+ }
+
+ if (radioState !== RADIO_STATE_UNAVAILABLE) {
+ // Retrieve device identities once radio is available.
+ this.getDeviceIdentity();
+ }
+
+ if (radioState == RADIO_STATE_ON) {
+ // This value is defined in RIL v7, we will retrieve radio tech by another
+ // request. We leave _isCdma untouched, and it will be set once we get the
+ // radio technology.
+ this._waitingRadioTech = true;
+ this.getVoiceRadioTechnology();
+ }
+
+ if ((this.radioState == GECKO_RADIOSTATE_UNKNOWN ||
+ this.radioState == GECKO_RADIOSTATE_DISABLED) &&
+ newState == GECKO_RADIOSTATE_ENABLED) {
+ // The radio became available, let's get its info.
+ this.getBasebandVersion();
+ this.updateCellBroadcastConfig();
+ if ((RILQUIRKS_DATA_REGISTRATION_ON_DEMAND ||
+ RILQUIRKS_SUBSCRIPTION_CONTROL) &&
+ this._attachDataRegistration) {
+ this.setDataRegistration({attach: true});
+ }
+
+ if (this.pendingToReportSmsMemoryStatus) {
+ this._updateSmsMemoryStatus();
+ }
+ }
+
+ this.radioState = newState;
+ this.sendChromeMessage({
+ rilMessageType: "radiostatechange",
+ radioState: newState
+ });
+
+ // If the radio is up and on, so let's query the card state.
+ // On older RILs only if the card is actually ready, though.
+ // If _waitingRadioTech is set, we don't need to get icc status now.
+ if (radioState == RADIO_STATE_UNAVAILABLE ||
+ radioState == RADIO_STATE_OFF ||
+ this._waitingRadioTech) {
+ return;
+ }
+ this.getICCStatus();
+};
+RilObject.prototype[UNSOLICITED_RESPONSE_CALL_STATE_CHANGED] = function UNSOLICITED_RESPONSE_CALL_STATE_CHANGED() {
+ this.getCurrentCalls();
+};
+RilObject.prototype[UNSOLICITED_RESPONSE_VOICE_NETWORK_STATE_CHANGED] = function UNSOLICITED_RESPONSE_VOICE_NETWORK_STATE_CHANGED() {
+ if (DEBUG) {
+ this.context.debug("Network state changed, re-requesting phone state and " +
+ "ICC status");
+ }
+ this.getICCStatus();
+ this.requestNetworkInfo();
+};
+RilObject.prototype[UNSOLICITED_RESPONSE_NEW_SMS] = function UNSOLICITED_RESPONSE_NEW_SMS(length) {
+ let [message, result] = this.context.GsmPDUHelper.processReceivedSms(length);
+
+ if (message) {
+ result = this._processSmsMultipart(message);
+ }
+
+ if (result == PDU_FCS_RESERVED || result == MOZ_FCS_WAIT_FOR_EXPLICIT_ACK) {
+ return;
+ }
+
+ // Not reserved FCS values, send ACK now.
+ this.acknowledgeGsmSms(result == PDU_FCS_OK, result);
+};
+RilObject.prototype[UNSOLICITED_RESPONSE_NEW_SMS_STATUS_REPORT] = function UNSOLICITED_RESPONSE_NEW_SMS_STATUS_REPORT(length) {
+ let result = this._processSmsStatusReport(length);
+ this.acknowledgeGsmSms(result == PDU_FCS_OK, result);
+};
+RilObject.prototype[UNSOLICITED_RESPONSE_NEW_SMS_ON_SIM] = function UNSOLICITED_RESPONSE_NEW_SMS_ON_SIM(length) {
+ let recordNumber = this.context.Buf.readInt32List()[0];
+
+ this.context.SimRecordHelper.readSMS(
+ recordNumber,
+ function onsuccess(message) {
+ if (message && message.simStatus === 3) { //New Unread SMS
+ this._processSmsMultipart(message);
+ }
+ }.bind(this),
+ function onerror(errorMsg) {
+ if (DEBUG) {
+ this.context.debug("Failed to Read NEW SMS on SIM #" + recordNumber +
+ ", errorMsg: " + errorMsg);
+ }
+ });
+};
+RilObject.prototype[UNSOLICITED_ON_USSD] = function UNSOLICITED_ON_USSD() {
+ let [typeCode, message] = this.context.Buf.readStringList();
+ if (DEBUG) {
+ this.context.debug("On USSD. Type Code: " + typeCode + " Message: " + message);
+ }
+
+ this.sendChromeMessage({rilMessageType: "ussdreceived",
+ message: message,
+ // Per ril.h the USSD session is assumed to persist if
+ // the type code is "1", otherwise the current session
+ // (if any) is assumed to have terminated.
+ sessionEnded: typeCode !== "1"});
+};
+RilObject.prototype[UNSOLICITED_ON_USSD_REQUEST] = null;
+RilObject.prototype[UNSOLICITED_NITZ_TIME_RECEIVED] = function UNSOLICITED_NITZ_TIME_RECEIVED() {
+ let dateString = this.context.Buf.readString();
+
+ // The data contained in the NITZ message is
+ // in the form "yy/mm/dd,hh:mm:ss(+/-)tz,dt"
+ // for example: 12/02/16,03:36:08-20,00,310410
+ // See also bug 714352 - Listen for NITZ updates from rild.
+
+ if (DEBUG) this.context.debug("DateTimeZone string " + dateString);
+
+ let now = Date.now();
+
+ let year = parseInt(dateString.substr(0, 2), 10);
+ let month = parseInt(dateString.substr(3, 2), 10);
+ let day = parseInt(dateString.substr(6, 2), 10);
+ let hours = parseInt(dateString.substr(9, 2), 10);
+ let minutes = parseInt(dateString.substr(12, 2), 10);
+ let seconds = parseInt(dateString.substr(15, 2), 10);
+ // Note that |tz| is in 15-min units.
+ let tz = parseInt(dateString.substr(17, 3), 10);
+ // Note that |dst| is in 1-hour units and is already applied in |tz|.
+ let dst = parseInt(dateString.substr(21, 2), 10);
+
+ let timeInMS = Date.UTC(year + PDU_TIMESTAMP_YEAR_OFFSET, month - 1, day,
+ hours, minutes, seconds);
+
+ if (isNaN(timeInMS)) {
+ if (DEBUG) this.context.debug("NITZ failed to convert date");
+ return;
+ }
+
+ this.sendChromeMessage({rilMessageType: "nitzTime",
+ networkTimeInMS: timeInMS,
+ networkTimeZoneInMinutes: -(tz * 15),
+ networkDSTInMinutes: -(dst * 60),
+ receiveTimeInMS: now});
+};
+
+RilObject.prototype[UNSOLICITED_SIGNAL_STRENGTH] = function UNSOLICITED_SIGNAL_STRENGTH(length) {
+ this[REQUEST_SIGNAL_STRENGTH](length, {});
+};
+RilObject.prototype[UNSOLICITED_DATA_CALL_LIST_CHANGED] = function UNSOLICITED_DATA_CALL_LIST_CHANGED(length) {
+ this[REQUEST_DATA_CALL_LIST](length, {});
+};
+RilObject.prototype[UNSOLICITED_SUPP_SVC_NOTIFICATION] = function UNSOLICITED_SUPP_SVC_NOTIFICATION(length) {
+ let Buf = this.context.Buf;
+ let info = {};
+ info.notificationType = Buf.readInt32();
+ info.code = Buf.readInt32();
+ info.index = Buf.readInt32();
+ info.type = Buf.readInt32();
+ info.number = Buf.readString();
+
+ this._processSuppSvcNotification(info);
+};
+
+RilObject.prototype[UNSOLICITED_STK_SESSION_END] = function UNSOLICITED_STK_SESSION_END() {
+ this.sendChromeMessage({rilMessageType: "stksessionend"});
+};
+RilObject.prototype[UNSOLICITED_STK_PROACTIVE_COMMAND] = function UNSOLICITED_STK_PROACTIVE_COMMAND() {
+ this.processStkProactiveCommand();
+};
+RilObject.prototype[UNSOLICITED_STK_EVENT_NOTIFY] = function UNSOLICITED_STK_EVENT_NOTIFY() {
+ this.processStkProactiveCommand();
+};
+RilObject.prototype[UNSOLICITED_STK_CALL_SETUP] = null;
+RilObject.prototype[UNSOLICITED_SIM_SMS_STORAGE_FULL] = null;
+RilObject.prototype[UNSOLICITED_SIM_REFRESH] = null;
+RilObject.prototype[UNSOLICITED_CALL_RING] = function UNSOLICITED_CALL_RING() {
+ let Buf = this.context.Buf;
+ let info = {rilMessageType: "callRing"};
+ let isCDMA = false; //XXX TODO hard-code this for now
+ if (isCDMA) {
+ info.isPresent = Buf.readInt32();
+ info.signalType = Buf.readInt32();
+ info.alertPitch = Buf.readInt32();
+ info.signal = Buf.readInt32();
+ }
+ // At this point we don't know much other than the fact there's an incoming
+ // call, but that's enough to bring up the Phone app already. We'll know
+ // details once we get a call state changed notification and can then
+ // dispatch DOM events etc.
+ this.sendChromeMessage(info);
+};
+RilObject.prototype[UNSOLICITED_RESPONSE_SIM_STATUS_CHANGED] = function UNSOLICITED_RESPONSE_SIM_STATUS_CHANGED() {
+ this.getICCStatus();
+};
+RilObject.prototype[UNSOLICITED_RESPONSE_CDMA_NEW_SMS] = function UNSOLICITED_RESPONSE_CDMA_NEW_SMS(length) {
+ let [message, result] = this.context.CdmaPDUHelper.processReceivedSms(length);
+
+ if (message) {
+ if (message.teleservice === PDU_CDMA_MSG_TELESERIVCIE_ID_WAP) {
+ result = this._processCdmaSmsWapPush(message);
+ } else if (message.subMsgType === PDU_CDMA_MSG_TYPE_DELIVER_ACK) {
+ result = this._processCdmaSmsStatusReport(message);
+ } else {
+ result = this._processSmsMultipart(message);
+ }
+ }
+
+ if (result == PDU_FCS_RESERVED || result == MOZ_FCS_WAIT_FOR_EXPLICIT_ACK) {
+ return;
+ }
+
+ // Not reserved FCS values, send ACK now.
+ this.acknowledgeCdmaSms(result == PDU_FCS_OK, result);
+};
+RilObject.prototype[UNSOLICITED_RESPONSE_NEW_BROADCAST_SMS] = function UNSOLICITED_RESPONSE_NEW_BROADCAST_SMS(length) {
+ let message;
+ try {
+ message =
+ this.context.GsmPDUHelper.readCbMessage(this.context.Buf.readInt32());
+
+ // "Data-Download" message is expected to be handled by the modem.
+ // Ignore it here to prevent any garbage messages to be displayed.
+ // See 9.4.1.2.2 Message Identifier of TS 32.041 for the range of
+ // Message-identifier of the Data-Download CB messages.
+ if (message.messageId >= 0x1000 && message.messageId <= 0x10FF) {
+ if (DEBUG) {
+ this.context.debug("Ignore a Data-Download message, messageId: " +
+ message.messageId);
+ }
+ return;
+ }
+ } catch (e) {
+ if (DEBUG) {
+ this.context.debug("Failed to parse Cell Broadcast message: " + e);
+ }
+ return;
+ }
+
+ message = this._processReceivedSmsCbPage(message);
+ if (!message) {
+ return;
+ }
+
+ // Bug 1235697, failed to deactivate CBS in some modem.
+ // Workaround it according to the settings.
+ // Note: ETWS/CMAS/PWS can be received even disabled.
+ // It will be displayed according to the setting in application layer.
+ if (this.cellBroadcastDisabled && (
+ !(message.messageId >= 0x1100 && message.messageId <= 0x1107) && // ETWS
+ !(message.messageId >= 0x1112 && message.messageId <= 0x112F) && // CMAS
+ !(message.messageId >= 0x1130 && message.messageId <= 0x18FF) // PWS
+ )) {
+ if (DEBUG) {
+ this.context.debug("Ignore a CB message when disabled, messageId: " +
+ message.messageId);
+ }
+ return;
+ }
+
+ message.rilMessageType = "cellbroadcast-received";
+ this.sendChromeMessage(message);
+};
+RilObject.prototype[UNSOLICITED_CDMA_RUIM_SMS_STORAGE_FULL] = null;
+RilObject.prototype[UNSOLICITED_RESTRICTED_STATE_CHANGED] = null;
+RilObject.prototype[UNSOLICITED_ENTER_EMERGENCY_CALLBACK_MODE] = function UNSOLICITED_ENTER_EMERGENCY_CALLBACK_MODE() {
+ this._handleChangedEmergencyCbMode(true);
+};
+RilObject.prototype[UNSOLICITED_CDMA_CALL_WAITING] = function UNSOLICITED_CDMA_CALL_WAITING(length) {
+ let Buf = this.context.Buf;
+ let call = {};
+ call.number = Buf.readString();
+ call.numberPresentation = Buf.readInt32();
+ call.name = Buf.readString();
+ call.namePresentation = Buf.readInt32();
+ call.isPresent = Buf.readInt32();
+ call.signalType = Buf.readInt32();
+ call.alertPitch = Buf.readInt32();
+ call.signal = Buf.readInt32();
+ this.sendChromeMessage({rilMessageType: "cdmaCallWaiting",
+ waitingCall: call});
+};
+RilObject.prototype[UNSOLICITED_CDMA_OTA_PROVISION_STATUS] = function UNSOLICITED_CDMA_OTA_PROVISION_STATUS() {
+ let status =
+ CDMA_OTA_PROVISION_STATUS_TO_GECKO[this.context.Buf.readInt32List()[0]];
+ if (!status) {
+ return;
+ }
+ this.sendChromeMessage({rilMessageType: "otastatuschange",
+ status: status});
+};
+RilObject.prototype[UNSOLICITED_CDMA_INFO_REC] = function UNSOLICITED_CDMA_INFO_REC(length) {
+ this.sendChromeMessage({
+ rilMessageType: "cdma-info-rec-received",
+ records: this.context.CdmaPDUHelper.decodeInformationRecord()
+ });
+};
+RilObject.prototype[UNSOLICITED_OEM_HOOK_RAW] = null;
+RilObject.prototype[UNSOLICITED_RINGBACK_TONE] = null;
+RilObject.prototype[UNSOLICITED_RESEND_INCALL_MUTE] = null;
+RilObject.prototype[UNSOLICITED_CDMA_SUBSCRIPTION_SOURCE_CHANGED] = null;
+RilObject.prototype[UNSOLICITED_CDMA_PRL_CHANGED] = function UNSOLICITED_CDMA_PRL_CHANGED(length) {
+ let version = this.context.Buf.readInt32List()[0];
+ if (version !== this.iccInfo.prlVersion) {
+ this.iccInfo.prlVersion = version;
+ this.context.ICCUtilsHelper.handleICCInfoChange();
+ }
+};
+RilObject.prototype[UNSOLICITED_EXIT_EMERGENCY_CALLBACK_MODE] = function UNSOLICITED_EXIT_EMERGENCY_CALLBACK_MODE() {
+ this._handleChangedEmergencyCbMode(false);
+};
+RilObject.prototype[UNSOLICITED_RIL_CONNECTED] = function UNSOLICITED_RIL_CONNECTED(length) {
+ // Prevent response id collision between UNSOLICITED_RIL_CONNECTED and
+ // UNSOLICITED_VOICE_RADIO_TECH_CHANGED for Akami on gingerbread branch.
+ if (!length) {
+ return;
+ }
+
+ this.version = this.context.Buf.readInt32List()[0];
+ if (DEBUG) {
+ this.context.debug("Detected RIL version " + this.version);
+ }
+
+ this.initRILState();
+ // rild might have restarted, ensure data call list.
+ this.getDataCallList();
+ // Always ensure that we are not in emergency callback mode when init.
+ this.exitEmergencyCbMode();
+ // Reset radio in the case that b2g restart (or crash).
+ this.setRadioEnabled({enabled: false});
+};
+RilObject.prototype[UNSOLICITED_VOICE_RADIO_TECH_CHANGED] = function UNSOLICITED_VOICE_RADIO_TECH_CHANGED(length) {
+ // This unsolicited response will be sent when the technology of a multi-tech
+ // modem is changed, ex. switch between gsm and cdma.
+ // TODO: We may need to do more on updating data when switching between gsm
+ // and cdma mode, e.g. IMEI, ESN, iccInfo, iccType ... etc.
+ // See Bug 866038.
+ this._processRadioTech(this.context.Buf.readInt32List()[0]);
+};
+RilObject.prototype[UNSOLICITED_CELL_INFO_LIST] = null;
+RilObject.prototype[UNSOLICITED_RESPONSE_IMS_NETWORK_STATE_CHANGED] = null;
+RilObject.prototype[UNSOLICITED_UICC_SUBSCRIPTION_STATUS_CHANGED] = null;
+RilObject.prototype[UNSOLICITED_SRVCC_STATE_NOTIFY] = null;
+RilObject.prototype[UNSOLICITED_HARDWARE_CONFIG_CHANGED] = null;
+RilObject.prototype[UNSOLICITED_DC_RT_INFO_CHANGED] = null;
+
+/**
+ * This object exposes the functionality to parse and serialize PDU strings
+ *
+ * A PDU is a string containing a series of hexadecimally encoded octets
+ * or nibble-swapped binary-coded decimals (BCDs). It contains not only the
+ * message text but information about the sender, the SMS service center,
+ * timestamp, etc.
+ */
+function GsmPDUHelperObject(aContext) {
+ this.context = aContext;
+}
+GsmPDUHelperObject.prototype = {
+ context: null,
+
+ /**
+ * Read one character (2 bytes) from a RIL string and decode as hex.
+ *
+ * @return the nibble as a number.
+ */
+ readHexNibble: function() {
+ let nibble = this.context.Buf.readUint16();
+ if (nibble >= 48 && nibble <= 57) {
+ nibble -= 48; // ASCII '0'..'9'
+ } else if (nibble >= 65 && nibble <= 70) {
+ nibble -= 55; // ASCII 'A'..'F'
+ } else if (nibble >= 97 && nibble <= 102) {
+ nibble -= 87; // ASCII 'a'..'f'
+ } else {
+ throw "Found invalid nibble during PDU parsing: " +
+ String.fromCharCode(nibble);
+ }
+ return nibble;
+ },
+
+ /**
+ * Encode a nibble as one hex character in a RIL string (2 bytes).
+ *
+ * @param nibble
+ * The nibble to encode (represented as a number)
+ */
+ writeHexNibble: function(nibble) {
+ nibble &= 0x0f;
+ if (nibble < 10) {
+ nibble += 48; // ASCII '0'
+ } else {
+ nibble += 55; // ASCII 'A'
+ }
+ this.context.Buf.writeUint16(nibble);
+ },
+
+ /**
+ * Read a hex-encoded octet (two nibbles).
+ *
+ * @return the octet as a number.
+ */
+ readHexOctet: function() {
+ return (this.readHexNibble() << 4) | this.readHexNibble();
+ },
+
+ /**
+ * Write an octet as two hex-encoded nibbles.
+ *
+ * @param octet
+ * The octet (represented as a number) to encode.
+ */
+ writeHexOctet: function(octet) {
+ this.writeHexNibble(octet >> 4);
+ this.writeHexNibble(octet);
+ },
+
+ /**
+ * Read an array of hex-encoded octets.
+ */
+ readHexOctetArray: function(length) {
+ let array = new Uint8Array(length);
+ for (let i = 0; i < length; i++) {
+ array[i] = this.readHexOctet();
+ }
+ return array;
+ },
+
+ /**
+ * Helper to write data into a temporary buffer for easier length encoding when
+ * the number of octets for the length encoding is varied.
+ *
+ * @param writeFunction
+ * Function of how the data to be written into temporary buffer.
+ *
+ * @return array of written octets.
+ **/
+ writeWithBuffer: function(writeFunction) {
+ let buf = [];
+ let writeHexOctet = this.writeHexOctet;
+ this.writeHexOctet = function(octet) {
+ buf.push(octet);
+ }
+
+ try {
+ writeFunction();
+ } catch (e) {
+ if (DEBUG) {
+ debug("Error when writeWithBuffer: " + e);
+ }
+ buf = [];
+ } finally {
+ this.writeHexOctet = writeHexOctet;
+ }
+
+ return buf;
+ },
+
+ /**
+ * Convert an octet (number) to a BCD number.
+ *
+ * Any nibbles that are not in the BCD range count as 0.
+ *
+ * @param octet
+ * The octet (a number, as returned by getOctet())
+ *
+ * @return the corresponding BCD number.
+ */
+ octetToBCD: function(octet) {
+ return ((octet & 0xf0) <= 0x90) * ((octet >> 4) & 0x0f) +
+ ((octet & 0x0f) <= 0x09) * (octet & 0x0f) * 10;
+ },
+
+ /**
+ * Convert a BCD number to an octet (number)
+ *
+ * Only take two digits with absolute value.
+ *
+ * @param bcd
+ *
+ * @return the corresponding octet.
+ */
+ BCDToOctet: function(bcd) {
+ bcd = Math.abs(bcd);
+ return ((bcd % 10) << 4) + (Math.floor(bcd / 10) % 10);
+ },
+
+ /**
+ * Convert a semi-octet (number) to a GSM BCD char, or return empty
+ * string if invalid semiOctet and suppressException is set to true.
+ *
+ * @param semiOctet
+ * Nibble to be converted to.
+ * @param suppressException [optional]
+ * Suppress exception if invalid semiOctet and suppressException is set
+ * to true.
+ *
+ * @return GSM BCD char, or empty string.
+ */
+ bcdChars: "0123456789",
+ semiOctetToBcdChar: function(semiOctet, suppressException) {
+ if (semiOctet >= this.bcdChars.length) {
+ if (suppressException) {
+ return "";
+ } else {
+ throw new RangeError();
+ }
+ }
+
+ return this.bcdChars.charAt(semiOctet);
+ },
+
+ /**
+ * Convert a semi-octet (number) to a GSM extended BCD char, or return empty
+ * string if invalid semiOctet and suppressException is set to true.
+ *
+ * @param semiOctet
+ * Nibble to be converted to.
+ * @param suppressException [optional]
+ * Suppress exception if invalid semiOctet and suppressException is set
+ * to true.
+ *
+ * @return GSM extended BCD char, or empty string.
+ */
+ extendedBcdChars: "0123456789*#,;",
+ semiOctetToExtendedBcdChar: function(semiOctet, suppressException) {
+ if (semiOctet >= this.extendedBcdChars.length) {
+ if (suppressException) {
+ return "";
+ } else {
+ throw new RangeError();
+ }
+ }
+
+ return this.extendedBcdChars.charAt(semiOctet);
+ },
+
+ /**
+ * Convert string to a GSM extended BCD string
+ */
+ stringToExtendedBcd: function(string) {
+ return string.replace(/[^0-9*#,]/g, "")
+ .replace(/\*/g, "a")
+ .replace(/\#/g, "b")
+ .replace(/\,/g, "c");
+ },
+
+ /**
+ * Read a *swapped nibble* binary coded decimal (BCD)
+ *
+ * @param pairs
+ * Number of nibble *pairs* to read.
+ *
+ * @return the decimal as a number.
+ */
+ readSwappedNibbleBcdNum: function(pairs) {
+ let number = 0;
+ for (let i = 0; i < pairs; i++) {
+ let octet = this.readHexOctet();
+ // Ignore 'ff' octets as they're often used as filler.
+ if (octet == 0xff) {
+ continue;
+ }
+ // If the first nibble is an "F" , only the second nibble is to be taken
+ // into account.
+ if ((octet & 0xf0) == 0xf0) {
+ number *= 10;
+ number += octet & 0x0f;
+ continue;
+ }
+ number *= 100;
+ number += this.octetToBCD(octet);
+ }
+ return number;
+ },
+
+ /**
+ * Read a *swapped nibble* binary coded decimal (BCD) string
+ *
+ * @param pairs
+ * Number of nibble *pairs* to read.
+ * @param suppressException [optional]
+ * Suppress exception if invalid semiOctet and suppressException is set
+ * to true.
+ *
+ * @return The BCD string.
+ */
+ readSwappedNibbleBcdString: function(pairs, suppressException) {
+ let str = "";
+ for (let i = 0; i < pairs; i++) {
+ let nibbleH = this.readHexNibble();
+ let nibbleL = this.readHexNibble();
+ if (nibbleL == 0x0F) {
+ break;
+ }
+
+ str += this.semiOctetToBcdChar(nibbleL, suppressException);
+ if (nibbleH != 0x0F) {
+ str += this.semiOctetToBcdChar(nibbleH, suppressException);
+ }
+ }
+
+ return str;
+ },
+
+ /**
+ * Read a *swapped nibble* extended binary coded decimal (BCD) string
+ *
+ * @param pairs
+ * Number of nibble *pairs* to read.
+ * @param suppressException [optional]
+ * Suppress exception if invalid semiOctet and suppressException is set
+ * to true.
+ *
+ * @return The BCD string.
+ */
+ readSwappedNibbleExtendedBcdString: function(pairs, suppressException) {
+ let str = "";
+ for (let i = 0; i < pairs; i++) {
+ let nibbleH = this.readHexNibble();
+ let nibbleL = this.readHexNibble();
+ if (nibbleL == 0x0F) {
+ break;
+ }
+
+ str += this.semiOctetToExtendedBcdChar(nibbleL, suppressException);
+ if (nibbleH != 0x0F) {
+ str += this.semiOctetToExtendedBcdChar(nibbleH, suppressException);
+ }
+ }
+
+ return str;
+ },
+
+ /**
+ * Write numerical data as swapped nibble BCD.
+ *
+ * @param data
+ * Data to write (as a string or a number)
+ */
+ writeSwappedNibbleBCD: function(data) {
+ data = data.toString();
+ if (data.length % 2) {
+ data += "F";
+ }
+ let Buf = this.context.Buf;
+ for (let i = 0; i < data.length; i += 2) {
+ Buf.writeUint16(data.charCodeAt(i + 1));
+ Buf.writeUint16(data.charCodeAt(i));
+ }
+ },
+
+ /**
+ * Write numerical data as swapped nibble BCD.
+ * If the number of digit of data is even, add '0' at the beginning.
+ *
+ * @param data
+ * Data to write (as a string or a number)
+ */
+ writeSwappedNibbleBCDNum: function(data) {
+ data = data.toString();
+ if (data.length % 2) {
+ data = "0" + data;
+ }
+ let Buf = this.context.Buf;
+ for (let i = 0; i < data.length; i += 2) {
+ Buf.writeUint16(data.charCodeAt(i + 1));
+ Buf.writeUint16(data.charCodeAt(i));
+ }
+ },
+
+ /**
+ * Read user data, convert to septets, look up relevant characters in a
+ * 7-bit alphabet, and construct string.
+ *
+ * @param length
+ * Number of septets to read (*not* octets)
+ * @param paddingBits
+ * Number of padding bits in the first byte of user data.
+ * @param langIndex
+ * Table index used for normal 7-bit encoded character lookup.
+ * @param langShiftIndex
+ * Table index used for escaped 7-bit encoded character lookup.
+ *
+ * @return a string.
+ */
+ readSeptetsToString: function(length, paddingBits, langIndex, langShiftIndex) {
+ let ret = "";
+ let byteLength = Math.ceil((length * 7 + paddingBits) / 8);
+
+ /**
+ * |<- last byte in header ->|
+ * |<- incompleteBits ->|<- last header septet->|
+ * +===7===|===6===|===5===|===4===|===3===|===2===|===1===|===0===|
+ *
+ * |<- 1st byte in user data ->|
+ * |<- data septet 1 ->|<-paddingBits->|
+ * +===7===|===6===|===5===|===4===|===3===|===2===|===1===|===0===|
+ *
+ * |<- 2nd byte in user data ->|
+ * |<- data spetet 2 ->|<-ds1->|
+ * +===7===|===6===|===5===|===4===|===3===|===2===|===1===|===0===|
+ */
+ let data = 0;
+ let dataBits = 0;
+ if (paddingBits) {
+ data = this.readHexOctet() >> paddingBits;
+ dataBits = 8 - paddingBits;
+ --byteLength;
+ }
+
+ let escapeFound = false;
+ const langTable = PDU_NL_LOCKING_SHIFT_TABLES[langIndex];
+ const langShiftTable = PDU_NL_SINGLE_SHIFT_TABLES[langShiftIndex];
+ do {
+ // Read as much as fits in 32bit word
+ let bytesToRead = Math.min(byteLength, dataBits ? 3 : 4);
+ for (let i = 0; i < bytesToRead; i++) {
+ data |= this.readHexOctet() << dataBits;
+ dataBits += 8;
+ --byteLength;
+ }
+
+ // Consume available full septets
+ for (; dataBits >= 7; dataBits -= 7) {
+ let septet = data & 0x7F;
+ data >>>= 7;
+
+ if (escapeFound) {
+ escapeFound = false;
+ if (septet == PDU_NL_EXTENDED_ESCAPE) {
+ // According to 3GPP TS 23.038, section 6.2.1.1, NOTE 1, "On
+ // receipt of this code, a receiving entity shall display a space
+ // until another extensiion table is defined."
+ ret += " ";
+ } else if (septet == PDU_NL_RESERVED_CONTROL) {
+ // According to 3GPP TS 23.038 B.2, "This code represents a control
+ // character and therefore must not be used for language specific
+ // characters."
+ ret += " ";
+ } else {
+ ret += langShiftTable[septet];
+ }
+ } else if (septet == PDU_NL_EXTENDED_ESCAPE) {
+ escapeFound = true;
+
+ // <escape> is not an effective character
+ --length;
+ } else {
+ ret += langTable[septet];
+ }
+ }
+ } while (byteLength);
+
+ if (ret.length != length) {
+ /**
+ * If num of effective characters does not equal to the length of read
+ * string, cut the tail off. This happens when the last octet of user
+ * data has following layout:
+ *
+ * |<- penultimate octet in user data ->|
+ * |<- data septet N ->|<- dsN-1 ->|
+ * +===7===|===6===|===5===|===4===|===3===|===2===|===1===|===0===|
+ *
+ * |<- last octet in user data ->|
+ * |<- fill bits ->|<-dsN->|
+ * +===7===|===6===|===5===|===4===|===3===|===2===|===1===|===0===|
+ *
+ * The fill bits in the last octet may happen to form a full septet and
+ * be appended at the end of result string.
+ */
+ ret = ret.slice(0, length);
+ }
+ return ret;
+ },
+
+ writeStringAsSeptets: function(message, paddingBits, langIndex, langShiftIndex) {
+ const langTable = PDU_NL_LOCKING_SHIFT_TABLES[langIndex];
+ const langShiftTable = PDU_NL_SINGLE_SHIFT_TABLES[langShiftIndex];
+
+ let dataBits = paddingBits;
+ let data = 0;
+ for (let i = 0; i < message.length; i++) {
+ let c = message.charAt(i);
+ let septet = langTable.indexOf(c);
+ if (septet == PDU_NL_EXTENDED_ESCAPE) {
+ continue;
+ }
+
+ if (septet >= 0) {
+ data |= septet << dataBits;
+ dataBits += 7;
+ } else {
+ septet = langShiftTable.indexOf(c);
+ if (septet == -1) {
+ throw new Error("'" + c + "' is not in 7 bit alphabet "
+ + langIndex + ":" + langShiftIndex + "!");
+ }
+
+ if (septet == PDU_NL_RESERVED_CONTROL) {
+ continue;
+ }
+
+ data |= PDU_NL_EXTENDED_ESCAPE << dataBits;
+ dataBits += 7;
+ data |= septet << dataBits;
+ dataBits += 7;
+ }
+
+ for (; dataBits >= 8; dataBits -= 8) {
+ this.writeHexOctet(data & 0xFF);
+ data >>>= 8;
+ }
+ }
+
+ if (dataBits !== 0) {
+ this.writeHexOctet(data & 0xFF);
+ }
+ },
+
+ writeStringAs8BitUnpacked: function(text) {
+ const langTable = PDU_NL_LOCKING_SHIFT_TABLES[PDU_NL_IDENTIFIER_DEFAULT];
+ const langShiftTable = PDU_NL_SINGLE_SHIFT_TABLES[PDU_NL_IDENTIFIER_DEFAULT];
+
+ let len = text ? text.length : 0;
+ for (let i = 0; i < len; i++) {
+ let c = text.charAt(i);
+ let octet = langTable.indexOf(c);
+
+ if (octet == -1) {
+ octet = langShiftTable.indexOf(c);
+ if (octet == -1) {
+ // Fallback to ASCII space.
+ octet = langTable.indexOf(' ');
+ } else {
+ this.writeHexOctet(PDU_NL_EXTENDED_ESCAPE);
+ }
+ }
+ this.writeHexOctet(octet);
+ }
+ },
+
+ /**
+ * Read user data and decode as a UCS2 string.
+ *
+ * @param numOctets
+ * Number of octets to be read as UCS2 string.
+ *
+ * @return a string.
+ */
+ readUCS2String: function(numOctets) {
+ let str = "";
+ let length = numOctets / 2;
+ for (let i = 0; i < length; ++i) {
+ let code = (this.readHexOctet() << 8) | this.readHexOctet();
+ str += String.fromCharCode(code);
+ }
+
+ if (DEBUG) this.context.debug("Read UCS2 string: " + str);
+
+ return str;
+ },
+
+ /**
+ * Write user data as a UCS2 string.
+ *
+ * @param message
+ * Message string to encode as UCS2 in hex-encoded octets.
+ */
+ writeUCS2String: function(message) {
+ for (let i = 0; i < message.length; ++i) {
+ let code = message.charCodeAt(i);
+ this.writeHexOctet((code >> 8) & 0xFF);
+ this.writeHexOctet(code & 0xFF);
+ }
+ },
+
+ /**
+ * Read 1 + UDHL octets and construct user data header.
+ *
+ * @param msg
+ * message object for output.
+ *
+ * @see 3GPP TS 23.040 9.2.3.24
+ */
+ readUserDataHeader: function(msg) {
+ /**
+ * A header object with properties contained in received message.
+ * The properties set include:
+ *
+ * length: totoal length of the header, default 0.
+ * langIndex: used locking shift table index, default
+ * PDU_NL_IDENTIFIER_DEFAULT.
+ * langShiftIndex: used locking shift table index, default
+ * PDU_NL_IDENTIFIER_DEFAULT.
+ *
+ */
+ let header = {
+ length: 0,
+ langIndex: PDU_NL_IDENTIFIER_DEFAULT,
+ langShiftIndex: PDU_NL_IDENTIFIER_DEFAULT
+ };
+
+ header.length = this.readHexOctet();
+ if (DEBUG) this.context.debug("Read UDH length: " + header.length);
+
+ let dataAvailable = header.length;
+ while (dataAvailable >= 2) {
+ let id = this.readHexOctet();
+ let length = this.readHexOctet();
+ if (DEBUG) this.context.debug("Read UDH id: " + id + ", length: " + length);
+
+ dataAvailable -= 2;
+
+ switch (id) {
+ case PDU_IEI_CONCATENATED_SHORT_MESSAGES_8BIT: {
+ let ref = this.readHexOctet();
+ let max = this.readHexOctet();
+ let seq = this.readHexOctet();
+ dataAvailable -= 3;
+ if (max && seq && (seq <= max)) {
+ header.segmentRef = ref;
+ header.segmentMaxSeq = max;
+ header.segmentSeq = seq;
+ }
+ break;
+ }
+ case PDU_IEI_APPLICATION_PORT_ADDRESSING_SCHEME_8BIT: {
+ let dstp = this.readHexOctet();
+ let orip = this.readHexOctet();
+ dataAvailable -= 2;
+ if ((dstp < PDU_APA_RESERVED_8BIT_PORTS)
+ || (orip < PDU_APA_RESERVED_8BIT_PORTS)) {
+ // 3GPP TS 23.040 clause 9.2.3.24.3: "A receiving entity shall
+ // ignore any information element where the value of the
+ // Information-Element-Data is Reserved or not supported"
+ break;
+ }
+ header.destinationPort = dstp;
+ header.originatorPort = orip;
+ break;
+ }
+ case PDU_IEI_APPLICATION_PORT_ADDRESSING_SCHEME_16BIT: {
+ let dstp = (this.readHexOctet() << 8) | this.readHexOctet();
+ let orip = (this.readHexOctet() << 8) | this.readHexOctet();
+ dataAvailable -= 4;
+ if ((dstp >= PDU_APA_VALID_16BIT_PORTS) ||
+ (orip >= PDU_APA_VALID_16BIT_PORTS)) {
+ // 3GPP TS 23.040 clause 9.2.3.24.4: "A receiving entity shall
+ // ignore any information element where the value of the
+ // Information-Element-Data is Reserved or not supported"
+ // Bug 1130292, some carriers set originatorPort to reserved port
+ // numbers for wap push. We rise this as a warning in debug message
+ // instead of ingoring this IEI to allow user to receive Wap Push
+ // under these carriers.
+ this.context.debug("Warning: Invalid port numbers [dstp, orip]: " +
+ JSON.stringify([dstp, orip]));
+ }
+ header.destinationPort = dstp;
+ header.originatorPort = orip;
+ break;
+ }
+ case PDU_IEI_CONCATENATED_SHORT_MESSAGES_16BIT: {
+ let ref = (this.readHexOctet() << 8) | this.readHexOctet();
+ let max = this.readHexOctet();
+ let seq = this.readHexOctet();
+ dataAvailable -= 4;
+ if (max && seq && (seq <= max)) {
+ header.segmentRef = ref;
+ header.segmentMaxSeq = max;
+ header.segmentSeq = seq;
+ }
+ break;
+ }
+ case PDU_IEI_NATIONAL_LANGUAGE_SINGLE_SHIFT:
+ let langShiftIndex = this.readHexOctet();
+ --dataAvailable;
+ if (langShiftIndex < PDU_NL_SINGLE_SHIFT_TABLES.length) {
+ header.langShiftIndex = langShiftIndex;
+ }
+ break;
+ case PDU_IEI_NATIONAL_LANGUAGE_LOCKING_SHIFT:
+ let langIndex = this.readHexOctet();
+ --dataAvailable;
+ if (langIndex < PDU_NL_LOCKING_SHIFT_TABLES.length) {
+ header.langIndex = langIndex;
+ }
+ break;
+ case PDU_IEI_SPECIAL_SMS_MESSAGE_INDICATION:
+ let msgInd = this.readHexOctet() & 0xFF;
+ let msgCount = this.readHexOctet();
+ dataAvailable -= 2;
+
+
+ /*
+ * TS 23.040 V6.8.1 Sec 9.2.3.24.2
+ * bits 1 0 : basic message indication type
+ * bits 4 3 2 : extended message indication type
+ * bits 6 5 : Profile id
+ * bit 7 : storage type
+ */
+ let storeType = msgInd & PDU_MWI_STORE_TYPE_BIT;
+ let mwi = msg.mwi;
+ if (!mwi) {
+ mwi = msg.mwi = {};
+ }
+
+ if (storeType == PDU_MWI_STORE_TYPE_STORE) {
+ // Store message because TP_UDH indicates so, note this may override
+ // the setting in DCS, but that is expected
+ mwi.discard = false;
+ } else if (mwi.discard === undefined) {
+ // storeType == PDU_MWI_STORE_TYPE_DISCARD
+ // only override mwi.discard here if it hasn't already been set
+ mwi.discard = true;
+ }
+
+ mwi.msgCount = msgCount & 0xFF;
+ mwi.active = mwi.msgCount > 0;
+
+ if (DEBUG) {
+ this.context.debug("MWI in TP_UDH received: " + JSON.stringify(mwi));
+ }
+
+ break;
+ default:
+ if (DEBUG) {
+ this.context.debug("readUserDataHeader: unsupported IEI(" + id +
+ "), " + length + " bytes.");
+ }
+
+ // Read out unsupported data
+ if (length) {
+ let octets;
+ if (DEBUG) octets = new Uint8Array(length);
+
+ for (let i = 0; i < length; i++) {
+ let octet = this.readHexOctet();
+ if (DEBUG) octets[i] = octet;
+ }
+ dataAvailable -= length;
+
+ if (DEBUG) {
+ this.context.debug("readUserDataHeader: " + Array.slice(octets));
+ }
+ }
+ break;
+ }
+ }
+
+ if (dataAvailable !== 0) {
+ throw new Error("Illegal user data header found!");
+ }
+
+ msg.header = header;
+ },
+
+ /**
+ * Write out user data header.
+ *
+ * @param options
+ * Options containing information for user data header write-out. The
+ * `userDataHeaderLength` property must be correctly pre-calculated.
+ */
+ writeUserDataHeader: function(options) {
+ this.writeHexOctet(options.userDataHeaderLength);
+
+ if (options.segmentMaxSeq > 1) {
+ if (options.segmentRef16Bit) {
+ this.writeHexOctet(PDU_IEI_CONCATENATED_SHORT_MESSAGES_16BIT);
+ this.writeHexOctet(4);
+ this.writeHexOctet((options.segmentRef >> 8) & 0xFF);
+ } else {
+ this.writeHexOctet(PDU_IEI_CONCATENATED_SHORT_MESSAGES_8BIT);
+ this.writeHexOctet(3);
+ }
+ this.writeHexOctet(options.segmentRef & 0xFF);
+ this.writeHexOctet(options.segmentMaxSeq & 0xFF);
+ this.writeHexOctet(options.segmentSeq & 0xFF);
+ }
+
+ if (options.dcs == PDU_DCS_MSG_CODING_7BITS_ALPHABET) {
+ if (options.langIndex != PDU_NL_IDENTIFIER_DEFAULT) {
+ this.writeHexOctet(PDU_IEI_NATIONAL_LANGUAGE_LOCKING_SHIFT);
+ this.writeHexOctet(1);
+ this.writeHexOctet(options.langIndex);
+ }
+
+ if (options.langShiftIndex != PDU_NL_IDENTIFIER_DEFAULT) {
+ this.writeHexOctet(PDU_IEI_NATIONAL_LANGUAGE_SINGLE_SHIFT);
+ this.writeHexOctet(1);
+ this.writeHexOctet(options.langShiftIndex);
+ }
+ }
+ },
+
+ /**
+ * Read SM-TL Address.
+ *
+ * @param len
+ * Length of useful semi-octets within the Address-Value field. For
+ * example, the lenth of "12345" should be 5, and 4 for "1234".
+ *
+ * @see 3GPP TS 23.040 9.1.2.5
+ */
+ readAddress: function(len) {
+ // Address Length
+ if (!len || (len < 0)) {
+ if (DEBUG) {
+ this.context.debug("PDU error: invalid sender address length: " + len);
+ }
+ return null;
+ }
+ if (len % 2 == 1) {
+ len += 1;
+ }
+ if (DEBUG) this.context.debug("PDU: Going to read address: " + len);
+
+ // Type-of-Address
+ let toa = this.readHexOctet();
+ let addr = "";
+
+ if ((toa & 0xF0) == PDU_TOA_ALPHANUMERIC) {
+ addr = this.readSeptetsToString(Math.floor(len * 4 / 7), 0,
+ PDU_NL_IDENTIFIER_DEFAULT , PDU_NL_IDENTIFIER_DEFAULT );
+ return addr;
+ }
+ addr = this.readSwappedNibbleExtendedBcdString(len / 2);
+ if (addr.length <= 0) {
+ if (DEBUG) this.context.debug("PDU error: no number provided");
+ return null;
+ }
+ if ((toa & 0xF0) == (PDU_TOA_INTERNATIONAL)) {
+ addr = '+' + addr;
+ }
+
+ return addr;
+ },
+
+ /**
+ * Read TP-Protocol-Indicator(TP-PID).
+ *
+ * @param msg
+ * message object for output.
+ *
+ * @see 3GPP TS 23.040 9.2.3.9
+ */
+ readProtocolIndicator: function(msg) {
+ // `The MS shall interpret reserved, obsolete, or unsupported values as the
+ // value 00000000 but shall store them exactly as received.`
+ msg.pid = this.readHexOctet();
+
+ msg.epid = msg.pid;
+ switch (msg.epid & 0xC0) {
+ case 0x40:
+ // Bit 7..0 = 01xxxxxx
+ switch (msg.epid) {
+ case PDU_PID_SHORT_MESSAGE_TYPE_0:
+ case PDU_PID_ANSI_136_R_DATA:
+ case PDU_PID_USIM_DATA_DOWNLOAD:
+ return;
+ }
+ break;
+ }
+
+ msg.epid = PDU_PID_DEFAULT;
+ },
+
+ /**
+ * Read TP-Data-Coding-Scheme(TP-DCS)
+ *
+ * @param msg
+ * message object for output.
+ *
+ * @see 3GPP TS 23.040 9.2.3.10, 3GPP TS 23.038 4.
+ */
+ readDataCodingScheme: function(msg) {
+ let dcs = this.readHexOctet();
+ if (DEBUG) this.context.debug("PDU: read SMS dcs: " + dcs);
+
+ // No message class by default.
+ let messageClass = PDU_DCS_MSG_CLASS_NORMAL;
+ // 7 bit is the default fallback encoding.
+ let encoding = PDU_DCS_MSG_CODING_7BITS_ALPHABET;
+ switch (dcs & PDU_DCS_CODING_GROUP_BITS) {
+ case 0x40: // bits 7..4 = 01xx
+ case 0x50:
+ case 0x60:
+ case 0x70:
+ // Bit 5..0 are coded exactly the same as Group 00xx
+ case 0x00: // bits 7..4 = 00xx
+ case 0x10:
+ case 0x20:
+ case 0x30:
+ if (dcs & 0x10) {
+ messageClass = dcs & PDU_DCS_MSG_CLASS_BITS;
+ }
+ switch (dcs & 0x0C) {
+ case 0x4:
+ encoding = PDU_DCS_MSG_CODING_8BITS_ALPHABET;
+ break;
+ case 0x8:
+ encoding = PDU_DCS_MSG_CODING_16BITS_ALPHABET;
+ break;
+ }
+ break;
+
+ case 0xE0: // bits 7..4 = 1110
+ encoding = PDU_DCS_MSG_CODING_16BITS_ALPHABET;
+ // Bit 3..0 are coded exactly the same as Message Waiting Indication
+ // Group 1101.
+ // Fall through.
+ case 0xC0: // bits 7..4 = 1100
+ case 0xD0: // bits 7..4 = 1101
+ // Indiciates voicemail indicator set or clear
+ let active = (dcs & PDU_DCS_MWI_ACTIVE_BITS) == PDU_DCS_MWI_ACTIVE_VALUE;
+
+ // If TP-UDH is present, these values will be overwritten
+ switch (dcs & PDU_DCS_MWI_TYPE_BITS) {
+ case PDU_DCS_MWI_TYPE_VOICEMAIL:
+ let mwi = msg.mwi;
+ if (!mwi) {
+ mwi = msg.mwi = {};
+ }
+
+ mwi.active = active;
+ mwi.discard = (dcs & PDU_DCS_CODING_GROUP_BITS) == 0xC0;
+ mwi.msgCount = active ? GECKO_VOICEMAIL_MESSAGE_COUNT_UNKNOWN : 0;
+
+ if (DEBUG) {
+ this.context.debug("MWI in DCS received for voicemail: " +
+ JSON.stringify(mwi));
+ }
+ break;
+ case PDU_DCS_MWI_TYPE_FAX:
+ if (DEBUG) this.context.debug("MWI in DCS received for fax");
+ break;
+ case PDU_DCS_MWI_TYPE_EMAIL:
+ if (DEBUG) this.context.debug("MWI in DCS received for email");
+ break;
+ default:
+ if (DEBUG) this.context.debug("MWI in DCS received for \"other\"");
+ break;
+ }
+ break;
+
+ case 0xF0: // bits 7..4 = 1111
+ if (dcs & 0x04) {
+ encoding = PDU_DCS_MSG_CODING_8BITS_ALPHABET;
+ }
+ messageClass = dcs & PDU_DCS_MSG_CLASS_BITS;
+ break;
+
+ default:
+ // Falling back to default encoding.
+ break;
+ }
+
+ msg.dcs = dcs;
+ msg.encoding = encoding;
+ msg.messageClass = GECKO_SMS_MESSAGE_CLASSES[messageClass];
+
+ if (DEBUG) this.context.debug("PDU: message encoding is " + encoding + " bit.");
+ },
+
+ /**
+ * Read GSM TP-Service-Centre-Time-Stamp(TP-SCTS).
+ *
+ * @see 3GPP TS 23.040 9.2.3.11
+ */
+ readTimestamp: function() {
+ let year = this.readSwappedNibbleBcdNum(1) + PDU_TIMESTAMP_YEAR_OFFSET;
+ let month = this.readSwappedNibbleBcdNum(1) - 1;
+ let day = this.readSwappedNibbleBcdNum(1);
+ let hour = this.readSwappedNibbleBcdNum(1);
+ let minute = this.readSwappedNibbleBcdNum(1);
+ let second = this.readSwappedNibbleBcdNum(1);
+ let timestamp = Date.UTC(year, month, day, hour, minute, second);
+
+ // If the most significant bit of the least significant nibble is 1,
+ // the timezone offset is negative (fourth bit from the right => 0x08):
+ // localtime = UTC + tzOffset
+ // therefore
+ // UTC = localtime - tzOffset
+ let tzOctet = this.readHexOctet();
+ let tzOffset = this.octetToBCD(tzOctet & ~0x08) * 15 * 60 * 1000;
+ tzOffset = (tzOctet & 0x08) ? -tzOffset : tzOffset;
+ timestamp -= tzOffset;
+
+ return timestamp;
+ },
+
+ /**
+ * Write GSM TP-Service-Centre-Time-Stamp(TP-SCTS).
+ *
+ * @see 3GPP TS 23.040 9.2.3.11
+ */
+ writeTimestamp: function(date) {
+ this.writeSwappedNibbleBCDNum(date.getFullYear() - PDU_TIMESTAMP_YEAR_OFFSET);
+
+ // The value returned by getMonth() is an integer between 0 and 11.
+ // 0 is corresponds to January, 1 to February, and so on.
+ this.writeSwappedNibbleBCDNum(date.getMonth() + 1);
+ this.writeSwappedNibbleBCDNum(date.getDate());
+ this.writeSwappedNibbleBCDNum(date.getHours());
+ this.writeSwappedNibbleBCDNum(date.getMinutes());
+ this.writeSwappedNibbleBCDNum(date.getSeconds());
+
+ // the value returned by getTimezoneOffset() is the difference,
+ // in minutes, between UTC and local time.
+ // For example, if your time zone is UTC+10 (Australian Eastern Standard Time),
+ // -600 will be returned.
+ // In TS 23.040 9.2.3.11, the Time Zone field of TP-SCTS indicates
+ // the different between the local time and GMT.
+ // And expressed in quarters of an hours. (so need to divid by 15)
+ let zone = date.getTimezoneOffset() / 15;
+ let octet = this.BCDToOctet(zone);
+
+ // the bit3 of the Time Zone field represents the algebraic sign.
+ // (0: positive, 1: negative).
+ // For example, if the time zone is -0800 GMT,
+ // 480 will be returned by getTimezoneOffset().
+ // In this case, need to mark sign bit as 1. => 0x08
+ if (zone > 0) {
+ octet = octet | 0x08;
+ }
+ this.writeHexOctet(octet);
+ },
+
+ /**
+ * User data can be 7 bit (default alphabet) data, 8 bit data, or 16 bit
+ * (UCS2) data.
+ *
+ * @param msg
+ * message object for output.
+ * @param length
+ * length of user data to read in octets.
+ */
+ readUserData: function(msg, length) {
+ if (DEBUG) {
+ this.context.debug("Reading " + length + " bytes of user data.");
+ }
+
+ let paddingBits = 0;
+ if (msg.udhi) {
+ this.readUserDataHeader(msg);
+
+ if (msg.encoding == PDU_DCS_MSG_CODING_7BITS_ALPHABET) {
+ let headerBits = (msg.header.length + 1) * 8;
+ let headerSeptets = Math.ceil(headerBits / 7);
+
+ length -= headerSeptets;
+ paddingBits = headerSeptets * 7 - headerBits;
+ } else {
+ length -= (msg.header.length + 1);
+ }
+ }
+
+ if (DEBUG) {
+ this.context.debug("After header, " + length + " septets left of user data");
+ }
+
+ msg.body = null;
+ msg.data = null;
+
+ if (length <= 0) {
+ // No data to read.
+ return;
+ }
+
+ switch (msg.encoding) {
+ case PDU_DCS_MSG_CODING_7BITS_ALPHABET:
+ // 7 bit encoding allows 140 octets, which means 160 characters
+ // ((140x8) / 7 = 160 chars)
+ if (length > PDU_MAX_USER_DATA_7BIT) {
+ if (DEBUG) {
+ this.context.debug("PDU error: user data is too long: " + length);
+ }
+ break;
+ }
+
+ let langIndex = msg.udhi ? msg.header.langIndex : PDU_NL_IDENTIFIER_DEFAULT;
+ let langShiftIndex = msg.udhi ? msg.header.langShiftIndex : PDU_NL_IDENTIFIER_DEFAULT;
+ msg.body = this.readSeptetsToString(length, paddingBits, langIndex,
+ langShiftIndex);
+ break;
+ case PDU_DCS_MSG_CODING_8BITS_ALPHABET:
+ msg.data = this.readHexOctetArray(length);
+ break;
+ case PDU_DCS_MSG_CODING_16BITS_ALPHABET:
+ msg.body = this.readUCS2String(length);
+ break;
+ }
+ },
+
+ /**
+ * Read extra parameters if TP-PI is set.
+ *
+ * @param msg
+ * message object for output.
+ */
+ readExtraParams: function(msg) {
+ // Because each PDU octet is converted to two UCS2 char2, we should always
+ // get even messageStringLength in this#_processReceivedSms(). So, we'll
+ // always need two delimitors at the end.
+ if (this.context.Buf.getReadAvailable() <= 4) {
+ return;
+ }
+
+ // TP-Parameter-Indicator
+ let pi;
+ do {
+ // `The most significant bit in octet 1 and any other TP-PI octets which
+ // may be added later is reserved as an extension bit which when set to a
+ // 1 shall indicate that another TP-PI octet follows immediately
+ // afterwards.` ~ 3GPP TS 23.040 9.2.3.27
+ pi = this.readHexOctet();
+ } while (pi & PDU_PI_EXTENSION);
+
+ // `If the TP-UDL bit is set to "1" but the TP-DCS bit is set to "0" then
+ // the receiving entity shall for TP-DCS assume a value of 0x00, i.e. the
+ // 7bit default alphabet.` ~ 3GPP 23.040 9.2.3.27
+ msg.dcs = 0;
+ msg.encoding = PDU_DCS_MSG_CODING_7BITS_ALPHABET;
+
+ // TP-Protocol-Identifier
+ if (pi & PDU_PI_PROTOCOL_IDENTIFIER) {
+ this.readProtocolIndicator(msg);
+ }
+ // TP-Data-Coding-Scheme
+ if (pi & PDU_PI_DATA_CODING_SCHEME) {
+ this.readDataCodingScheme(msg);
+ }
+ // TP-User-Data-Length
+ if (pi & PDU_PI_USER_DATA_LENGTH) {
+ let userDataLength = this.readHexOctet();
+ this.readUserData(msg, userDataLength);
+ }
+ },
+
+ /**
+ * Read and decode a PDU-encoded message from the stream.
+ *
+ * TODO: add some basic sanity checks like:
+ * - do we have the minimum number of chars available
+ */
+ readMessage: function() {
+ // An empty message object. This gets filled below and then returned.
+ let msg = {
+ // D:DELIVER, DR:DELIVER-REPORT, S:SUBMIT, SR:SUBMIT-REPORT,
+ // ST:STATUS-REPORT, C:COMMAND
+ // M:Mandatory, O:Optional, X:Unavailable
+ // D DR S SR ST C
+ SMSC: null, // M M M M M M
+ mti: null, // M M M M M M
+ udhi: null, // M M O M M M
+ sender: null, // M X X X X X
+ recipient: null, // X X M X M M
+ pid: null, // M O M O O M
+ epid: null, // M O M O O M
+ dcs: null, // M O M O O X
+ mwi: null, // O O O O O O
+ replace: false, // O O O O O O
+ header: null, // M M O M M M
+ body: null, // M O M O O O
+ data: null, // M O M O O O
+ sentTimestamp: null, // M X X X X X
+ status: null, // X X X X M X
+ scts: null, // X X X M M X
+ dt: null, // X X X X M X
+ };
+
+ // SMSC info
+ let smscLength = this.readHexOctet();
+ if (smscLength > 0) {
+ let smscTypeOfAddress = this.readHexOctet();
+ // Subtract the type-of-address octet we just read from the length.
+ msg.SMSC = this.readSwappedNibbleExtendedBcdString(smscLength - 1);
+ if ((smscTypeOfAddress >> 4) == (PDU_TOA_INTERNATIONAL >> 4)) {
+ msg.SMSC = '+' + msg.SMSC;
+ }
+ }
+
+ // First octet of this SMS-DELIVER or SMS-SUBMIT message
+ let firstOctet = this.readHexOctet();
+ // Message Type Indicator
+ msg.mti = firstOctet & 0x03;
+ // User data header indicator
+ msg.udhi = firstOctet & PDU_UDHI;
+
+ switch (msg.mti) {
+ case PDU_MTI_SMS_RESERVED:
+ // `If an MS receives a TPDU with a "Reserved" value in the TP-MTI it
+ // shall process the message as if it were an "SMS-DELIVER" but store
+ // the message exactly as received.` ~ 3GPP TS 23.040 9.2.3.1
+ case PDU_MTI_SMS_DELIVER:
+ return this.readDeliverMessage(msg);
+ case PDU_MTI_SMS_STATUS_REPORT:
+ return this.readStatusReportMessage(msg);
+ default:
+ return null;
+ }
+ },
+
+ /**
+ * Helper for processing received SMS parcel data.
+ *
+ * @param length
+ * Length of SMS string in the incoming parcel.
+ *
+ * @return Message parsed or null for invalid message.
+ */
+ processReceivedSms: function(length) {
+ if (!length) {
+ if (DEBUG) this.context.debug("Received empty SMS!");
+ return [null, PDU_FCS_UNSPECIFIED];
+ }
+
+ let Buf = this.context.Buf;
+
+ // An SMS is a string, but we won't read it as such, so let's read the
+ // string length and then defer to PDU parsing helper.
+ let messageStringLength = Buf.readInt32();
+ if (DEBUG) this.context.debug("Got new SMS, length " + messageStringLength);
+ let message = this.readMessage();
+ if (DEBUG) this.context.debug("Got new SMS: " + JSON.stringify(message));
+
+ // Read string delimiters. See Buf.readString().
+ Buf.readStringDelimiter(length);
+
+ // Determine result
+ if (!message) {
+ return [null, PDU_FCS_UNSPECIFIED];
+ }
+
+ if (message.epid == PDU_PID_SHORT_MESSAGE_TYPE_0) {
+ // `A short message type 0 indicates that the ME must acknowledge receipt
+ // of the short message but shall discard its contents.` ~ 3GPP TS 23.040
+ // 9.2.3.9
+ return [null, PDU_FCS_OK];
+ }
+
+ if (message.messageClass == GECKO_SMS_MESSAGE_CLASSES[PDU_DCS_MSG_CLASS_2]) {
+ let RIL = this.context.RIL;
+ switch (message.epid) {
+ case PDU_PID_ANSI_136_R_DATA:
+ case PDU_PID_USIM_DATA_DOWNLOAD:
+ let ICCUtilsHelper = this.context.ICCUtilsHelper;
+ if (ICCUtilsHelper.isICCServiceAvailable("DATA_DOWNLOAD_SMS_PP")) {
+ // `If the service "data download via SMS Point-to-Point" is
+ // allocated and activated in the (U)SIM Service Table, ... then the
+ // ME shall pass the message transparently to the UICC using the
+ // ENVELOPE (SMS-PP DOWNLOAD).` ~ 3GPP TS 31.111 7.1.1.1
+ RIL.dataDownloadViaSMSPP(message);
+
+ // `the ME shall not display the message, or alert the user of a
+ // short message waiting.` ~ 3GPP TS 31.111 7.1.1.1
+ return [null, PDU_FCS_RESERVED];
+ }
+
+ // If the service "data download via SMS-PP" is not available in the
+ // (U)SIM Service Table, ..., then the ME shall store the message in
+ // EFsms in accordance with TS 31.102` ~ 3GPP TS 31.111 7.1.1.1
+
+ // Fall through.
+ default:
+ RIL.writeSmsToSIM(message);
+ break;
+ }
+ }
+
+ // TODO: Bug 739143: B2G SMS: Support SMS Storage Full event
+ if ((message.messageClass != GECKO_SMS_MESSAGE_CLASSES[PDU_DCS_MSG_CLASS_0]) && !true) {
+ // `When a mobile terminated message is class 0..., the MS shall display
+ // the message immediately and send a ACK to the SC ..., irrespective of
+ // whether there is memory available in the (U)SIM or ME.` ~ 3GPP 23.038
+ // clause 4.
+
+ if (message.messageClass == GECKO_SMS_MESSAGE_CLASSES[PDU_DCS_MSG_CLASS_2]) {
+ // `If all the short message storage at the MS is already in use, the
+ // MS shall return "memory capacity exceeded".` ~ 3GPP 23.038 clause 4.
+ return [null, PDU_FCS_MEMORY_CAPACITY_EXCEEDED];
+ }
+
+ return [null, PDU_FCS_UNSPECIFIED];
+ }
+
+ return [message, PDU_FCS_OK];
+ },
+
+ /**
+ * Read and decode a SMS-DELIVER PDU.
+ *
+ * @param msg
+ * message object for output.
+ */
+ readDeliverMessage: function(msg) {
+ // - Sender Address info -
+ let senderAddressLength = this.readHexOctet();
+ msg.sender = this.readAddress(senderAddressLength);
+ // - TP-Protocolo-Identifier -
+ this.readProtocolIndicator(msg);
+ // - TP-Data-Coding-Scheme -
+ this.readDataCodingScheme(msg);
+ // - TP-Service-Center-Time-Stamp -
+ msg.sentTimestamp = this.readTimestamp();
+ // - TP-User-Data-Length -
+ let userDataLength = this.readHexOctet();
+
+ // - TP-User-Data -
+ if (userDataLength > 0) {
+ this.readUserData(msg, userDataLength);
+ }
+
+ return msg;
+ },
+
+ /**
+ * Read and decode a SMS-STATUS-REPORT PDU.
+ *
+ * @param msg
+ * message object for output.
+ */
+ readStatusReportMessage: function(msg) {
+ // TP-Message-Reference
+ msg.messageRef = this.readHexOctet();
+ // TP-Recipient-Address
+ let recipientAddressLength = this.readHexOctet();
+ msg.recipient = this.readAddress(recipientAddressLength);
+ // TP-Service-Centre-Time-Stamp
+ msg.scts = this.readTimestamp();
+ // TP-Discharge-Time
+ msg.dt = this.readTimestamp();
+ // TP-Status
+ msg.status = this.readHexOctet();
+
+ this.readExtraParams(msg);
+
+ return msg;
+ },
+
+ /**
+ * Serialize a SMS-SUBMIT PDU message and write it to the output stream.
+ *
+ * This method expects that a data coding scheme has been chosen already
+ * and that the length of the user data payload in that encoding is known,
+ * too. Both go hand in hand together anyway.
+ *
+ * @param address
+ * String containing the address (number) of the SMS receiver
+ * @param userData
+ * String containing the message to be sent as user data
+ * @param dcs
+ * Data coding scheme. One of the PDU_DCS_MSG_CODING_*BITS_ALPHABET
+ * constants.
+ * @param userDataHeaderLength
+ * Length of embedded user data header, in bytes. The whole header
+ * size will be userDataHeaderLength + 1; 0 for no header.
+ * @param encodedBodyLength
+ * Length of the user data when encoded with the given DCS. For UCS2,
+ * in bytes; for 7-bit, in septets.
+ * @param langIndex
+ * Table index used for normal 7-bit encoded character lookup.
+ * @param langShiftIndex
+ * Table index used for escaped 7-bit encoded character lookup.
+ * @param requestStatusReport
+ * Request status report.
+ */
+ writeMessage: function(options) {
+ if (DEBUG) {
+ this.context.debug("writeMessage: " + JSON.stringify(options));
+ }
+ let Buf = this.context.Buf;
+ let address = options.number;
+ let body = options.body;
+ let dcs = options.dcs;
+ let userDataHeaderLength = options.userDataHeaderLength;
+ let encodedBodyLength = options.encodedBodyLength;
+ let langIndex = options.langIndex;
+ let langShiftIndex = options.langShiftIndex;
+
+ // SMS-SUBMIT Format:
+ //
+ // PDU Type - 1 octet
+ // Message Reference - 1 octet
+ // DA - Destination Address - 2 to 12 octets
+ // PID - Protocol Identifier - 1 octet
+ // DCS - Data Coding Scheme - 1 octet
+ // VP - Validity Period - 0, 1 or 7 octets
+ // UDL - User Data Length - 1 octet
+ // UD - User Data - 140 octets
+
+ let addressFormat = PDU_TOA_ISDN; // 81
+ if (address[0] == '+') {
+ addressFormat = PDU_TOA_INTERNATIONAL | PDU_TOA_ISDN; // 91
+ address = address.substring(1);
+ }
+ //TODO validity is unsupported for now
+ let validity = 0;
+
+ let headerOctets = (userDataHeaderLength ? userDataHeaderLength + 1 : 0);
+ let paddingBits;
+ let userDataLengthInSeptets;
+ let userDataLengthInOctets;
+ if (dcs == PDU_DCS_MSG_CODING_7BITS_ALPHABET) {
+ let headerSeptets = Math.ceil(headerOctets * 8 / 7);
+ userDataLengthInSeptets = headerSeptets + encodedBodyLength;
+ userDataLengthInOctets = Math.ceil(userDataLengthInSeptets * 7 / 8);
+ paddingBits = headerSeptets * 7 - headerOctets * 8;
+ } else {
+ userDataLengthInOctets = headerOctets + encodedBodyLength;
+ paddingBits = 0;
+ }
+
+ let pduOctetLength = 4 + // PDU Type, Message Ref, address length + format
+ Math.ceil(address.length / 2) +
+ 3 + // PID, DCS, UDL
+ userDataLengthInOctets;
+ if (validity) {
+ //TODO: add more to pduOctetLength
+ }
+
+ // Start the string. Since octets are represented in hex, we will need
+ // twice as many characters as octets.
+ Buf.writeInt32(pduOctetLength * 2);
+
+ // - PDU-TYPE-
+
+ // +--------+----------+---------+---------+--------+---------+
+ // | RP (1) | UDHI (1) | SRR (1) | VPF (2) | RD (1) | MTI (2) |
+ // +--------+----------+---------+---------+--------+---------+
+ // RP: 0 Reply path parameter is not set
+ // 1 Reply path parameter is set
+ // UDHI: 0 The UD Field contains only the short message
+ // 1 The beginning of the UD field contains a header in addition
+ // of the short message
+ // SRR: 0 A status report is not requested
+ // 1 A status report is requested
+ // VPF: bit4 bit3
+ // 0 0 VP field is not present
+ // 0 1 Reserved
+ // 1 0 VP field present an integer represented (relative)
+ // 1 1 VP field present a semi-octet represented (absolute)
+ // RD: Instruct the SMSC to accept(0) or reject(1) an SMS-SUBMIT
+ // for a short message still held in the SMSC which has the same
+ // MR and DA as a previously submitted short message from the
+ // same OA
+ // MTI: bit1 bit0 Message Type
+ // 0 0 SMS-DELIVER (SMSC ==> MS)
+ // 0 1 SMS-SUBMIT (MS ==> SMSC)
+
+ // PDU type. MTI is set to SMS-SUBMIT
+ let firstOctet = PDU_MTI_SMS_SUBMIT;
+
+ // Status-Report-Request
+ if (options.requestStatusReport) {
+ firstOctet |= PDU_SRI_SRR;
+ }
+
+ // Validity period
+ if (validity) {
+ //TODO: not supported yet, OR with one of PDU_VPF_*
+ }
+ // User data header indicator
+ if (headerOctets) {
+ firstOctet |= PDU_UDHI;
+ }
+ this.writeHexOctet(firstOctet);
+
+ // Message reference 00
+ this.writeHexOctet(0x00);
+
+ // - Destination Address -
+ this.writeHexOctet(address.length);
+ this.writeHexOctet(addressFormat);
+ this.writeSwappedNibbleBCD(address);
+
+ // - Protocol Identifier -
+ this.writeHexOctet(0x00);
+
+ // - Data coding scheme -
+ // For now it assumes bits 7..4 = 1111 except for the 16 bits use case
+ this.writeHexOctet(dcs);
+
+ // - Validity Period -
+ if (validity) {
+ this.writeHexOctet(validity);
+ }
+
+ // - User Data -
+ if (dcs == PDU_DCS_MSG_CODING_7BITS_ALPHABET) {
+ this.writeHexOctet(userDataLengthInSeptets);
+ } else {
+ this.writeHexOctet(userDataLengthInOctets);
+ }
+
+ if (headerOctets) {
+ this.writeUserDataHeader(options);
+ }
+
+ switch (dcs) {
+ case PDU_DCS_MSG_CODING_7BITS_ALPHABET:
+ this.writeStringAsSeptets(body, paddingBits, langIndex, langShiftIndex);
+ break;
+ case PDU_DCS_MSG_CODING_8BITS_ALPHABET:
+ // Unsupported.
+ break;
+ case PDU_DCS_MSG_CODING_16BITS_ALPHABET:
+ this.writeUCS2String(body);
+ break;
+ }
+
+ // End of the string. The string length is always even by definition, so
+ // we write two \0 delimiters.
+ Buf.writeUint16(0);
+ Buf.writeUint16(0);
+ },
+
+ /**
+ * Read GSM CBS message serial number.
+ *
+ * @param msg
+ * message object for output.
+ *
+ * @see 3GPP TS 23.041 section 9.4.1.2.1
+ */
+ readCbSerialNumber: function(msg) {
+ let Buf = this.context.Buf;
+ msg.serial = Buf.readUint8() << 8 | Buf.readUint8();
+ msg.geographicalScope = (msg.serial >>> 14) & 0x03;
+ msg.messageCode = (msg.serial >>> 4) & 0x03FF;
+ msg.updateNumber = msg.serial & 0x0F;
+ },
+
+ /**
+ * Read GSM CBS message message identifier.
+ *
+ * @param msg
+ * message object for output.
+ *
+ * @see 3GPP TS 23.041 section 9.4.1.2.2
+ */
+ readCbMessageIdentifier: function(msg) {
+ let Buf = this.context.Buf;
+ msg.messageId = Buf.readUint8() << 8 | Buf.readUint8();
+ },
+
+ /**
+ * Read ETWS information from message identifier and serial Number
+ * @param msg
+ * message object for output.
+ *
+ * @see 3GPP TS 23.041 section 9.4.1.2.1 & 9.4.1.2.2
+ */
+ readCbEtwsInfo: function(msg) {
+ if ((msg.format != CB_FORMAT_ETWS)
+ && (msg.messageId >= CB_GSM_MESSAGEID_ETWS_BEGIN)
+ && (msg.messageId <= CB_GSM_MESSAGEID_ETWS_END)) {
+ // `In the case of transmitting CBS message for ETWS, a part of
+ // Message Code can be used to command mobile terminals to activate
+ // emergency user alert and message popup in order to alert the users.`
+ msg.etws = {
+ emergencyUserAlert: msg.messageCode & 0x0200 ? true : false,
+ popup: msg.messageCode & 0x0100 ? true : false
+ };
+
+ let warningType = msg.messageId - CB_GSM_MESSAGEID_ETWS_BEGIN;
+ if (warningType < CB_ETWS_WARNING_TYPE_NAMES.length) {
+ msg.etws.warningType = warningType;
+ }
+ }
+ },
+
+ /**
+ * Read CBS Data Coding Scheme.
+ *
+ * @param msg
+ * message object for output.
+ *
+ * @see 3GPP TS 23.038 section 5.
+ */
+ readCbDataCodingScheme: function(msg) {
+ let dcs = this.context.Buf.readUint8();
+ if (DEBUG) this.context.debug("PDU: read CBS dcs: " + dcs);
+
+ let language = null, hasLanguageIndicator = false;
+ // `Any reserved codings shall be assumed to be the GSM 7bit default
+ // alphabet.`
+ let encoding = PDU_DCS_MSG_CODING_7BITS_ALPHABET;
+ let messageClass = PDU_DCS_MSG_CLASS_NORMAL;
+
+ switch (dcs & PDU_DCS_CODING_GROUP_BITS) {
+ case 0x00: // 0000
+ language = CB_DCS_LANG_GROUP_1[dcs & 0x0F];
+ break;
+
+ case 0x10: // 0001
+ switch (dcs & 0x0F) {
+ case 0x00:
+ hasLanguageIndicator = true;
+ break;
+ case 0x01:
+ encoding = PDU_DCS_MSG_CODING_16BITS_ALPHABET;
+ hasLanguageIndicator = true;
+ break;
+ }
+ break;
+
+ case 0x20: // 0010
+ language = CB_DCS_LANG_GROUP_2[dcs & 0x0F];
+ break;
+
+ case 0x40: // 01xx
+ case 0x50:
+ //case 0x60: Text Compression, not supported
+ //case 0x70: Text Compression, not supported
+ case 0x90: // 1001
+ encoding = (dcs & 0x0C);
+ if (encoding == 0x0C) {
+ encoding = PDU_DCS_MSG_CODING_7BITS_ALPHABET;
+ }
+ messageClass = (dcs & PDU_DCS_MSG_CLASS_BITS);
+ break;
+
+ case 0xF0:
+ encoding = (dcs & 0x04) ? PDU_DCS_MSG_CODING_8BITS_ALPHABET
+ : PDU_DCS_MSG_CODING_7BITS_ALPHABET;
+ switch(dcs & 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;
+ }
+ break;
+
+ case 0x30: // 0011 (Reserved)
+ case 0x80: // 1000 (Reserved)
+ case 0xA0: // 1010..1100 (Reserved)
+ case 0xB0:
+ case 0xC0:
+ break;
+
+ default:
+ throw new Error("Unsupported CBS data coding scheme: " + dcs);
+ }
+
+ msg.dcs = dcs;
+ msg.encoding = encoding;
+ msg.language = language;
+ msg.messageClass = GECKO_SMS_MESSAGE_CLASSES[messageClass];
+ msg.hasLanguageIndicator = hasLanguageIndicator;
+ },
+
+ /**
+ * Read GSM CBS message page parameter.
+ *
+ * @param msg
+ * message object for output.
+ *
+ * @see 3GPP TS 23.041 section 9.4.1.2.4
+ */
+ readCbPageParameter: function(msg) {
+ let octet = this.context.Buf.readUint8();
+ msg.pageIndex = (octet >>> 4) & 0x0F;
+ msg.numPages = octet & 0x0F;
+ if (!msg.pageIndex || !msg.numPages) {
+ // `If a mobile receives the code 0000 in either the first field or the
+ // second field then it shall treat the CBS message exactly the same as a
+ // CBS message with page parameter 0001 0001 (i.e. a single page message).`
+ msg.pageIndex = msg.numPages = 1;
+ }
+ },
+
+ /**
+ * Read ETWS Primary Notification message warning type.
+ *
+ * @param msg
+ * message object for output.
+ *
+ * @see 3GPP TS 23.041 section 9.3.24
+ */
+ readCbWarningType: function(msg) {
+ let Buf = this.context.Buf;
+ let word = Buf.readUint8() << 8 | Buf.readUint8();
+ msg.etws = {
+ warningType: (word >>> 9) & 0x7F,
+ popup: word & 0x80 ? true : false,
+ emergencyUserAlert: word & 0x100 ? true : false
+ };
+ },
+
+ /**
+ * Read GSM CB Data
+ *
+ * This parameter is a copy of the 'CBS-Message-Information-Page' as sent
+ * from the CBC to the BSC.
+ *
+ * @see 3GPP TS 23.041 section 9.4.1.2.5
+ */
+ readGsmCbData: function(msg, length) {
+ let Buf = this.context.Buf;
+ let bufAdapter = {
+ context: this.context,
+ readHexOctet: function() {
+ return Buf.readUint8();
+ }
+ };
+
+ msg.body = null;
+ msg.data = null;
+ switch (msg.encoding) {
+ case PDU_DCS_MSG_CODING_7BITS_ALPHABET:
+ msg.body = this.readSeptetsToString.call(bufAdapter,
+ Math.floor(length * 8 / 7), 0,
+ PDU_NL_IDENTIFIER_DEFAULT,
+ PDU_NL_IDENTIFIER_DEFAULT);
+ if (msg.hasLanguageIndicator) {
+ msg.language = msg.body.substring(0, 2);
+ msg.body = msg.body.substring(3);
+ }
+ break;
+
+ case PDU_DCS_MSG_CODING_8BITS_ALPHABET:
+ msg.data = Buf.readUint8Array(length);
+ break;
+
+ case PDU_DCS_MSG_CODING_16BITS_ALPHABET:
+ if (msg.hasLanguageIndicator) {
+ msg.language = this.readSeptetsToString.call(bufAdapter, 2, 0,
+ PDU_NL_IDENTIFIER_DEFAULT,
+ PDU_NL_IDENTIFIER_DEFAULT);
+ length -= 2;
+ }
+ msg.body = this.readUCS2String.call(bufAdapter, length);
+ break;
+ }
+
+ if (msg.data || !msg.body) {
+ return;
+ }
+
+ // According to 9.3.19 CBS-Message-Information-Page in TS 23.041:
+ // "
+ // This parameter is of a fixed length of 82 octets and carries up to and
+ // including 82 octets of user information. Where the user information is
+ // less than 82 octets, the remaining octets must be filled with padding.
+ // "
+ // According to 6.2.1.1 GSM 7 bit Default Alphabet and 6.2.3 UCS2 in
+ // TS 23.038, the padding character is <CR>.
+ for (let i = msg.body.length - 1; i >= 0; i--) {
+ if (msg.body.charAt(i) !== '\r') {
+ msg.body = msg.body.substring(0, i + 1);
+ break;
+ }
+ }
+ },
+
+ /**
+ * Read UMTS CB Data
+ *
+ * Octet Number(s) Parameter
+ * 1 Number-of-Pages
+ * 2 - 83 CBS-Message-Information-Page 1
+ * 84 CBS-Message-Information-Length 1
+ * ...
+ * CBS-Message-Information-Page n
+ * CBS-Message-Information-Length n
+ *
+ * @see 3GPP TS 23.041 section 9.4.2.2.5
+ */
+ readUmtsCbData: function(msg) {
+ let Buf = this.context.Buf;
+ let numOfPages = Buf.readUint8();
+ if (numOfPages < 0 || numOfPages > 15) {
+ throw new Error("Invalid numOfPages: " + numOfPages);
+ }
+
+ let bufAdapter = {
+ context: this.context,
+ readHexOctet: function() {
+ return Buf.readUint8();
+ }
+ };
+
+ let removePaddingCharactors = function (text) {
+ for (let i = text.length - 1; i >= 0; i--) {
+ if (text.charAt(i) !== '\r') {
+ return text.substring(0, i + 1);
+ }
+ }
+ return text;
+ };
+
+ let totalLength = 0, length, pageLengths = [];
+ for (let i = 0; i < numOfPages; i++) {
+ Buf.seekIncoming(CB_MSG_PAGE_INFO_SIZE);
+ length = Buf.readUint8();
+ totalLength += length;
+ pageLengths.push(length);
+ }
+
+ // Seek back to beginning of CB Data.
+ Buf.seekIncoming(-numOfPages * (CB_MSG_PAGE_INFO_SIZE + 1));
+
+ switch (msg.encoding) {
+ case PDU_DCS_MSG_CODING_7BITS_ALPHABET: {
+ let body;
+ msg.body = "";
+ for (let i = 0; i < numOfPages; i++) {
+ body = this.readSeptetsToString.call(bufAdapter,
+ Math.floor(pageLengths[i] * 8 / 7),
+ 0,
+ PDU_NL_IDENTIFIER_DEFAULT,
+ PDU_NL_IDENTIFIER_DEFAULT);
+ if (msg.hasLanguageIndicator) {
+ if (!msg.language) {
+ msg.language = body.substring(0, 2);
+ }
+ body = body.substring(3);
+ }
+
+ msg.body += removePaddingCharactors(body);
+
+ // Skip padding octets
+ Buf.seekIncoming(CB_MSG_PAGE_INFO_SIZE - pageLengths[i]);
+ // Read the octet of CBS-Message-Information-Length
+ Buf.readUint8();
+ }
+
+ break;
+ }
+
+ case PDU_DCS_MSG_CODING_8BITS_ALPHABET: {
+ msg.data = new Uint8Array(totalLength);
+ for (let i = 0, j = 0; i < numOfPages; i++) {
+ for (let pageLength = pageLengths[i]; pageLength > 0; pageLength--) {
+ msg.data[j++] = Buf.readUint8();
+ }
+
+ // Skip padding octets
+ Buf.seekIncoming(CB_MSG_PAGE_INFO_SIZE - pageLengths[i]);
+ // Read the octet of CBS-Message-Information-Length
+ Buf.readUint8();
+ }
+
+ break;
+ }
+
+ case PDU_DCS_MSG_CODING_16BITS_ALPHABET: {
+ msg.body = "";
+ for (let i = 0; i < numOfPages; i++) {
+ let pageLength = pageLengths[i];
+ if (msg.hasLanguageIndicator) {
+ if (!msg.language) {
+ msg.language = this.readSeptetsToString.call(bufAdapter,
+ 2,
+ 0,
+ PDU_NL_IDENTIFIER_DEFAULT,
+ PDU_NL_IDENTIFIER_DEFAULT);
+ } else {
+ Buf.readUint16();
+ }
+
+ pageLength -= 2;
+ }
+
+ msg.body += removePaddingCharactors(
+ this.readUCS2String.call(bufAdapter, pageLength));
+
+ // Skip padding octets
+ Buf.seekIncoming(CB_MSG_PAGE_INFO_SIZE - pageLengths[i]);
+ // Read the octet of CBS-Message-Information-Length
+ Buf.readUint8();
+ }
+
+ break;
+ }
+ }
+ },
+
+ /**
+ * Read Cell GSM/ETWS/UMTS Broadcast Message.
+ *
+ * @param pduLength
+ * total length of the incoming PDU in octets.
+ */
+ readCbMessage: function(pduLength) {
+ // Validity GSM ETWS UMTS
+ let msg = {
+ // Internally used in ril_worker:
+ serial: null, // O O O
+ updateNumber: null, // O O O
+ format: null, // O O O
+ dcs: 0x0F, // O X O
+ encoding: PDU_DCS_MSG_CODING_7BITS_ALPHABET, // O X O
+ hasLanguageIndicator: false, // O X O
+ data: null, // O X O
+ body: null, // O X O
+ pageIndex: 1, // O X X
+ numPages: 1, // O X X
+
+ // DOM attributes:
+ geographicalScope: null, // O O O
+ messageCode: null, // O O O
+ messageId: null, // O O O
+ language: null, // O X O
+ fullBody: null, // O X O
+ fullData: null, // O X O
+ messageClass: GECKO_SMS_MESSAGE_CLASSES[PDU_DCS_MSG_CLASS_NORMAL], // O x O
+ etws: null // ? O ?
+ /*{
+ warningType: null, // X O X
+ popup: false, // X O X
+ emergencyUserAlert: false, // X O X
+ }*/
+ };
+
+ if (pduLength <= CB_MESSAGE_SIZE_ETWS) {
+ msg.format = CB_FORMAT_ETWS;
+ return this.readEtwsCbMessage(msg);
+ }
+
+ if (pduLength <= CB_MESSAGE_SIZE_GSM) {
+ msg.format = CB_FORMAT_GSM;
+ return this.readGsmCbMessage(msg, pduLength);
+ }
+
+ if (pduLength >= CB_MESSAGE_SIZE_UMTS_MIN &&
+ pduLength <= CB_MESSAGE_SIZE_UMTS_MAX) {
+ msg.format = CB_FORMAT_UMTS;
+ return this.readUmtsCbMessage(msg);
+ }
+
+ throw new Error("Invalid PDU Length: " + pduLength);
+ },
+
+ /**
+ * Read UMTS CBS Message.
+ *
+ * @param msg
+ * message object for output.
+ *
+ * @see 3GPP TS 23.041 section 9.4.2
+ * @see 3GPP TS 25.324 section 10.2
+ */
+ readUmtsCbMessage: function(msg) {
+ let Buf = this.context.Buf;
+ let type = Buf.readUint8();
+ if (type != CB_UMTS_MESSAGE_TYPE_CBS) {
+ throw new Error("Unsupported UMTS Cell Broadcast message type: " + type);
+ }
+
+ this.readCbMessageIdentifier(msg);
+ this.readCbSerialNumber(msg);
+ this.readCbEtwsInfo(msg);
+ this.readCbDataCodingScheme(msg);
+ this.readUmtsCbData(msg);
+
+ return msg;
+ },
+
+ /**
+ * Read GSM Cell Broadcast Message.
+ *
+ * @param msg
+ * message object for output.
+ * @param pduLength
+ * total length of the incomint PDU in octets.
+ *
+ * @see 3GPP TS 23.041 clause 9.4.1.2
+ */
+ readGsmCbMessage: function(msg, pduLength) {
+ this.readCbSerialNumber(msg);
+ this.readCbMessageIdentifier(msg);
+ this.readCbEtwsInfo(msg);
+ this.readCbDataCodingScheme(msg);
+ this.readCbPageParameter(msg);
+
+ // GSM CB message header takes 6 octets.
+ this.readGsmCbData(msg, pduLength - 6);
+
+ return msg;
+ },
+
+ /**
+ * Read ETWS Primary Notification Message.
+ *
+ * @param msg
+ * message object for output.
+ *
+ * @see 3GPP TS 23.041 clause 9.4.1.3
+ */
+ readEtwsCbMessage: function(msg) {
+ this.readCbSerialNumber(msg);
+ this.readCbMessageIdentifier(msg);
+ this.readCbWarningType(msg);
+
+ // Octet 7..56 is Warning Security Information. However, according to
+ // section 9.4.1.3.6, `The UE shall ignore this parameter.` So we just skip
+ // processing it here.
+
+ return msg;
+ },
+
+ /**
+ * Read network name.
+ *
+ * @param len Length of the information element.
+ * @return
+ * {
+ * networkName: network name.
+ * shouldIncludeCi: Should Country's initials included in text string.
+ * }
+ * @see TS 24.008 clause 10.5.3.5a.
+ */
+ readNetworkName: function(len) {
+ // According to TS 24.008 Sec. 10.5.3.5a, the first octet is:
+ // bit 8: must be 1.
+ // bit 5-7: Text encoding.
+ // 000 - GSM default alphabet.
+ // 001 - UCS2 (16 bit).
+ // else - reserved.
+ // bit 4: MS should add the letters for Country's Initials and a space
+ // to the text string if this bit is true.
+ // bit 1-3: number of spare bits in last octet.
+
+ let codingInfo = this.readHexOctet();
+ if (!(codingInfo & 0x80)) {
+ return null;
+ }
+
+ let textEncoding = (codingInfo & 0x70) >> 4;
+ let shouldIncludeCountryInitials = !!(codingInfo & 0x08);
+ let spareBits = codingInfo & 0x07;
+ let resultString;
+
+ switch (textEncoding) {
+ case 0:
+ // GSM Default alphabet.
+ resultString = this.readSeptetsToString(
+ Math.floor(((len - 1) * 8 - spareBits) / 7), 0,
+ PDU_NL_IDENTIFIER_DEFAULT,
+ PDU_NL_IDENTIFIER_DEFAULT);
+ break;
+ case 1:
+ // UCS2 encoded.
+ resultString = this.context.ICCPDUHelper.readAlphaIdentifier(len - 1);
+ break;
+ default:
+ // Not an available text coding.
+ return null;
+ }
+
+ // TODO - Bug 820286: According to shouldIncludeCountryInitials, add
+ // country initials to the resulting string.
+ return resultString;
+ }
+};
+
+/**
+ * Provide buffer with bitwise read/write function so make encoding/decoding easier.
+ */
+function BitBufferHelperObject(/* unused */aContext) {
+ this.readBuffer = [];
+ this.writeBuffer = [];
+}
+BitBufferHelperObject.prototype = {
+ readCache: 0,
+ readCacheSize: 0,
+ readBuffer: null,
+ readIndex: 0,
+ writeCache: 0,
+ writeCacheSize: 0,
+ writeBuffer: null,
+
+ // Max length is 32 because we use integer as read/write cache.
+ // All read/write functions are implemented based on bitwise operation.
+ readBits: function(length) {
+ if (length <= 0 || length > 32) {
+ return null;
+ }
+
+ if (length > this.readCacheSize) {
+ let bytesToRead = Math.ceil((length - this.readCacheSize) / 8);
+ for(let i = 0; i < bytesToRead; i++) {
+ this.readCache = (this.readCache << 8) | (this.readBuffer[this.readIndex++] & 0xFF);
+ this.readCacheSize += 8;
+ }
+ }
+
+ let bitOffset = (this.readCacheSize - length),
+ resultMask = (1 << length) - 1,
+ result = 0;
+
+ result = (this.readCache >> bitOffset) & resultMask;
+ this.readCacheSize -= length;
+
+ return result;
+ },
+
+ backwardReadPilot: function(length) {
+ if (length <= 0) {
+ return;
+ }
+
+ // Zero-based position.
+ let bitIndexToRead = this.readIndex * 8 - this.readCacheSize - length;
+
+ if (bitIndexToRead < 0) {
+ return;
+ }
+
+ // Update readIndex, readCache, readCacheSize accordingly.
+ let readBits = bitIndexToRead % 8;
+ this.readIndex = Math.floor(bitIndexToRead / 8) + ((readBits) ? 1 : 0);
+ this.readCache = (readBits) ? this.readBuffer[this.readIndex - 1] : 0;
+ this.readCacheSize = (readBits) ? (8 - readBits) : 0;
+ },
+
+ writeBits: function(value, length) {
+ if (length <= 0 || length > 32) {
+ return;
+ }
+
+ let totalLength = length + this.writeCacheSize;
+
+ // 8-byte cache not full
+ if (totalLength < 8) {
+ let valueMask = (1 << length) - 1;
+ this.writeCache = (this.writeCache << length) | (value & valueMask);
+ this.writeCacheSize += length;
+ return;
+ }
+
+ // Deal with unaligned part
+ if (this.writeCacheSize) {
+ let mergeLength = 8 - this.writeCacheSize,
+ valueMask = (1 << mergeLength) - 1;
+
+ this.writeCache = (this.writeCache << mergeLength) | ((value >> (length - mergeLength)) & valueMask);
+ this.writeBuffer.push(this.writeCache & 0xFF);
+ length -= mergeLength;
+ }
+
+ // Aligned part, just copy
+ while (length >= 8) {
+ length -= 8;
+ this.writeBuffer.push((value >> length) & 0xFF);
+ }
+
+ // Rest part is saved into cache
+ this.writeCacheSize = length;
+ this.writeCache = value & ((1 << length) - 1);
+
+ return;
+ },
+
+ // Drop what still in read cache and goto next 8-byte alignment.
+ // There might be a better naming.
+ nextOctetAlign: function() {
+ this.readCache = 0;
+ this.readCacheSize = 0;
+ },
+
+ // Flush current write cache to Buf with padding 0s.
+ // There might be a better naming.
+ flushWithPadding: function() {
+ if (this.writeCacheSize) {
+ this.writeBuffer.push(this.writeCache << (8 - this.writeCacheSize));
+ }
+ this.writeCache = 0;
+ this.writeCacheSize = 0;
+ },
+
+ startWrite: function(dataBuffer) {
+ this.writeBuffer = dataBuffer;
+ this.writeCache = 0;
+ this.writeCacheSize = 0;
+ },
+
+ startRead: function(dataBuffer) {
+ this.readBuffer = dataBuffer;
+ this.readCache = 0;
+ this.readCacheSize = 0;
+ this.readIndex = 0;
+ },
+
+ getWriteBufferSize: function() {
+ return this.writeBuffer.length;
+ },
+
+ overwriteWriteBuffer: function(position, data) {
+ let writeLength = data.length;
+ if (writeLength + position >= this.writeBuffer.length) {
+ writeLength = this.writeBuffer.length - position;
+ }
+ for (let i = 0; i < writeLength; i++) {
+ this.writeBuffer[i + position] = data[i];
+ }
+ }
+};
+
+/**
+ * Helper for CDMA PDU
+ *
+ * Currently, some function are shared with GsmPDUHelper, they should be
+ * moved from GsmPDUHelper to a common object shared among GsmPDUHelper and
+ * CdmaPDUHelper.
+ */
+function CdmaPDUHelperObject(aContext) {
+ this.context = aContext;
+}
+CdmaPDUHelperObject.prototype = {
+ context: null,
+
+ // 1..........C
+ // Only "1234567890*#" is defined in C.S0005-D v2.0
+ dtmfChars: ".1234567890*#...",
+
+ /**
+ * Entry point for SMS encoding, the options object is made compatible
+ * with existing writeMessage() of GsmPDUHelper, but less key is used.
+ *
+ * Current used key in options:
+ * @param number
+ * String containing the address (number) of the SMS receiver
+ * @param body
+ * String containing the message to be sent, segmented part
+ * @param dcs
+ * Data coding scheme. One of the PDU_DCS_MSG_CODING_*BITS_ALPHABET
+ * constants.
+ * @param encodedBodyLength
+ * Length of the user data when encoded with the given DCS. For UCS2,
+ * in bytes; for 7-bit, in septets.
+ * @param requestStatusReport
+ * Request status report.
+ * @param segmentRef
+ * Reference number of concatenated SMS message
+ * @param segmentMaxSeq
+ * Total number of concatenated SMS message
+ * @param segmentSeq
+ * Sequence number of concatenated SMS message
+ */
+ writeMessage: function(options) {
+ if (DEBUG) {
+ this.context.debug("cdma_writeMessage: " + JSON.stringify(options));
+ }
+
+ // Get encoding
+ options.encoding = this.gsmDcsToCdmaEncoding(options.dcs);
+
+ // Common Header
+ if (options.segmentMaxSeq > 1) {
+ this.writeInt(PDU_CDMA_MSG_TELESERIVCIE_ID_WEMT);
+ } else {
+ this.writeInt(PDU_CDMA_MSG_TELESERIVCIE_ID_SMS);
+ }
+
+ this.writeInt(0);
+ this.writeInt(PDU_CDMA_MSG_CATEGORY_UNSPEC);
+
+ // Just fill out address info in byte, rild will encap them for us
+ let addrInfo = this.encodeAddr(options.number);
+ this.writeByte(addrInfo.digitMode);
+ this.writeByte(addrInfo.numberMode);
+ this.writeByte(addrInfo.numberType);
+ this.writeByte(addrInfo.numberPlan);
+ this.writeByte(addrInfo.address.length);
+ for (let i = 0; i < addrInfo.address.length; i++) {
+ this.writeByte(addrInfo.address[i]);
+ }
+
+ // Subaddress, not supported
+ this.writeByte(0); // Subaddress : Type
+ this.writeByte(0); // Subaddress : Odd
+ this.writeByte(0); // Subaddress : length
+
+ // User Data
+ let encodeResult = this.encodeUserData(options);
+ this.writeByte(encodeResult.length);
+ for (let i = 0; i < encodeResult.length; i++) {
+ this.writeByte(encodeResult[i]);
+ }
+
+ encodeResult = null;
+ },
+
+ /**
+ * Data writters
+ */
+ writeInt: function(value) {
+ this.context.Buf.writeInt32(value);
+ },
+
+ writeByte: function(value) {
+ this.context.Buf.writeInt32(value & 0xFF);
+ },
+
+ /**
+ * Transform GSM DCS to CDMA encoding.
+ */
+ gsmDcsToCdmaEncoding: function(encoding) {
+ switch (encoding) {
+ case PDU_DCS_MSG_CODING_7BITS_ALPHABET:
+ return PDU_CDMA_MSG_CODING_7BITS_ASCII;
+ case PDU_DCS_MSG_CODING_8BITS_ALPHABET:
+ return PDU_CDMA_MSG_CODING_OCTET;
+ case PDU_DCS_MSG_CODING_16BITS_ALPHABET:
+ return PDU_CDMA_MSG_CODING_UNICODE;
+ }
+ throw new Error("gsmDcsToCdmaEncoding(): Invalid GSM SMS DCS value: " + encoding);
+ },
+
+ /**
+ * Encode address into CDMA address format, as a byte array.
+ *
+ * @see 3GGP2 C.S0015-B 2.0, 3.4.3.3 Address Parameters
+ *
+ * @param address
+ * String of address to be encoded
+ */
+ encodeAddr: function(address) {
+ let result = {};
+
+ result.numberType = PDU_CDMA_MSG_ADDR_NUMBER_TYPE_UNKNOWN;
+ result.numberPlan = PDU_CDMA_MSG_ADDR_NUMBER_TYPE_UNKNOWN;
+
+ if (address[0] === '+') {
+ address = address.substring(1);
+ }
+
+ // Try encode with DTMF first
+ result.digitMode = PDU_CDMA_MSG_ADDR_DIGIT_MODE_DTMF;
+ result.numberMode = PDU_CDMA_MSG_ADDR_NUMBER_MODE_ANSI;
+
+ result.address = [];
+ for (let i = 0; i < address.length; i++) {
+ let addrDigit = this.dtmfChars.indexOf(address.charAt(i));
+ if (addrDigit < 0) {
+ result.digitMode = PDU_CDMA_MSG_ADDR_DIGIT_MODE_ASCII;
+ result.numberMode = PDU_CDMA_MSG_ADDR_NUMBER_MODE_ASCII;
+ result.address = [];
+ break;
+ }
+ result.address.push(addrDigit);
+ }
+
+ // Address can't be encoded with DTMF, then use 7-bit ASCII
+ if (result.digitMode !== PDU_CDMA_MSG_ADDR_DIGIT_MODE_DTMF) {
+ if (address.indexOf("@") !== -1) {
+ result.numberType = PDU_CDMA_MSG_ADDR_NUMBER_TYPE_NATIONAL;
+ }
+
+ for (let i = 0; i < address.length; i++) {
+ result.address.push(address.charCodeAt(i) & 0x7F);
+ }
+ }
+
+ return result;
+ },
+
+ /**
+ * Encode SMS contents in options into CDMA userData field.
+ * Corresponding and required subparameters will be added automatically.
+ *
+ * @see 3GGP2 C.S0015-B 2.0, 3.4.3.7 Bearer Data
+ * 4.5 Bearer Data Parameters
+ *
+ * Current used key in options:
+ * @param body
+ * String containing the message to be sent, segmented part
+ * @param encoding
+ * Encoding method of CDMA, can be transformed from GSM DCS by function
+ * cdmaPduHelp.gsmDcsToCdmaEncoding()
+ * @param encodedBodyLength
+ * Length of the user data when encoded with the given DCS. For UCS2,
+ * in bytes; for 7-bit, in septets.
+ * @param requestStatusReport
+ * Request status report.
+ * @param segmentRef
+ * Reference number of concatenated SMS message
+ * @param segmentMaxSeq
+ * Total number of concatenated SMS message
+ * @param segmentSeq
+ * Sequence number of concatenated SMS message
+ */
+ encodeUserData: function(options) {
+ let userDataBuffer = [];
+ this.context.BitBufferHelper.startWrite(userDataBuffer);
+
+ // Message Identifier
+ this.encodeUserDataMsgId(options);
+
+ // User Data
+ this.encodeUserDataMsg(options);
+
+ // Reply Option
+ this.encodeUserDataReplyOption(options);
+
+ return userDataBuffer;
+ },
+
+ /**
+ * User data subparameter encoder : Message Identifier
+ *
+ * @see 3GGP2 C.S0015-B 2.0, 4.5.1 Message Identifier
+ */
+ encodeUserDataMsgId: function(options) {
+ let BitBufferHelper = this.context.BitBufferHelper;
+ BitBufferHelper.writeBits(PDU_CDMA_MSG_USERDATA_MSG_ID, 8);
+ BitBufferHelper.writeBits(3, 8);
+ BitBufferHelper.writeBits(PDU_CDMA_MSG_TYPE_SUBMIT, 4);
+ BitBufferHelper.writeBits(1, 16); // TODO: How to get message ID?
+ if (options.segmentMaxSeq > 1) {
+ BitBufferHelper.writeBits(1, 1);
+ } else {
+ BitBufferHelper.writeBits(0, 1);
+ }
+
+ BitBufferHelper.flushWithPadding();
+ },
+
+ /**
+ * User data subparameter encoder : User Data
+ *
+ * @see 3GGP2 C.S0015-B 2.0, 4.5.2 User Data
+ */
+ encodeUserDataMsg: function(options) {
+ let BitBufferHelper = this.context.BitBufferHelper;
+ BitBufferHelper.writeBits(PDU_CDMA_MSG_USERDATA_BODY, 8);
+ // Reserve space for length
+ BitBufferHelper.writeBits(0, 8);
+ let lengthPosition = BitBufferHelper.getWriteBufferSize();
+
+ BitBufferHelper.writeBits(options.encoding, 5);
+
+ // Add user data header for message segement
+ let msgBody = options.body,
+ msgBodySize = (options.encoding === PDU_CDMA_MSG_CODING_7BITS_ASCII ?
+ options.encodedBodyLength :
+ msgBody.length);
+ if (options.segmentMaxSeq > 1) {
+ if (options.encoding === PDU_CDMA_MSG_CODING_7BITS_ASCII) {
+ BitBufferHelper.writeBits(msgBodySize + 7, 8); // Required length for user data header, in septet(7-bit)
+
+ BitBufferHelper.writeBits(5, 8); // total header length 5 bytes
+ BitBufferHelper.writeBits(PDU_IEI_CONCATENATED_SHORT_MESSAGES_8BIT, 8); // header id 0
+ BitBufferHelper.writeBits(3, 8); // length of element for id 0 is 3
+ BitBufferHelper.writeBits(options.segmentRef & 0xFF, 8); // Segement reference
+ BitBufferHelper.writeBits(options.segmentMaxSeq & 0xFF, 8); // Max segment
+ BitBufferHelper.writeBits(options.segmentSeq & 0xFF, 8); // Current segment
+ BitBufferHelper.writeBits(0, 1); // Padding to make header data septet(7-bit) aligned
+ } else {
+ if (options.encoding === PDU_CDMA_MSG_CODING_UNICODE) {
+ BitBufferHelper.writeBits(msgBodySize + 3, 8); // Required length for user data header, in 16-bit
+ } else {
+ BitBufferHelper.writeBits(msgBodySize + 6, 8); // Required length for user data header, in octet(8-bit)
+ }
+
+ BitBufferHelper.writeBits(5, 8); // total header length 5 bytes
+ BitBufferHelper.writeBits(PDU_IEI_CONCATENATED_SHORT_MESSAGES_8BIT, 8); // header id 0
+ BitBufferHelper.writeBits(3, 8); // length of element for id 0 is 3
+ BitBufferHelper.writeBits(options.segmentRef & 0xFF, 8); // Segement reference
+ BitBufferHelper.writeBits(options.segmentMaxSeq & 0xFF, 8); // Max segment
+ BitBufferHelper.writeBits(options.segmentSeq & 0xFF, 8); // Current segment
+ }
+ } else {
+ BitBufferHelper.writeBits(msgBodySize, 8);
+ }
+
+ // Encode message based on encoding method
+ const langTable = PDU_NL_LOCKING_SHIFT_TABLES[PDU_NL_IDENTIFIER_DEFAULT];
+ const langShiftTable = PDU_NL_SINGLE_SHIFT_TABLES[PDU_NL_IDENTIFIER_DEFAULT];
+ for (let i = 0; i < msgBody.length; i++) {
+ switch (options.encoding) {
+ case PDU_CDMA_MSG_CODING_OCTET: {
+ let msgDigit = msgBody.charCodeAt(i);
+ BitBufferHelper.writeBits(msgDigit, 8);
+ break;
+ }
+ case PDU_CDMA_MSG_CODING_7BITS_ASCII: {
+ let msgDigit = msgBody.charCodeAt(i),
+ msgDigitChar = msgBody.charAt(i);
+
+ if (msgDigit >= 32) {
+ BitBufferHelper.writeBits(msgDigit, 7);
+ } else {
+ msgDigit = langTable.indexOf(msgDigitChar);
+
+ if (msgDigit === PDU_NL_EXTENDED_ESCAPE) {
+ break;
+ }
+ if (msgDigit >= 0) {
+ BitBufferHelper.writeBits(msgDigit, 7);
+ } else {
+ msgDigit = langShiftTable.indexOf(msgDigitChar);
+ if (msgDigit == -1) {
+ throw new Error("'" + msgDigitChar + "' is not in 7 bit alphabet "
+ + langIndex + ":" + langShiftIndex + "!");
+ }
+
+ if (msgDigit === PDU_NL_RESERVED_CONTROL) {
+ break;
+ }
+
+ BitBufferHelper.writeBits(PDU_NL_EXTENDED_ESCAPE, 7);
+ BitBufferHelper.writeBits(msgDigit, 7);
+ }
+ }
+ break;
+ }
+ case PDU_CDMA_MSG_CODING_UNICODE: {
+ let msgDigit = msgBody.charCodeAt(i);
+ BitBufferHelper.writeBits(msgDigit, 16);
+ break;
+ }
+ }
+ }
+ BitBufferHelper.flushWithPadding();
+
+ // Fill length
+ let currentPosition = BitBufferHelper.getWriteBufferSize();
+ BitBufferHelper.overwriteWriteBuffer(lengthPosition - 1, [currentPosition - lengthPosition]);
+ },
+
+ /**
+ * User data subparameter encoder : Reply Option
+ *
+ * @see 3GGP2 C.S0015-B 2.0, 4.5.11 Reply Option
+ */
+ encodeUserDataReplyOption: function(options) {
+ if (options.requestStatusReport) {
+ let BitBufferHelper = this.context.BitBufferHelper;
+ BitBufferHelper.writeBits(PDU_CDMA_MSG_USERDATA_REPLY_OPTION, 8);
+ BitBufferHelper.writeBits(1, 8);
+ BitBufferHelper.writeBits(0, 1); // USER_ACK_REQ
+ BitBufferHelper.writeBits(1, 1); // DAK_REQ
+ BitBufferHelper.flushWithPadding();
+ }
+ },
+
+ /**
+ * Entry point for SMS decoding, the returned object is made compatible
+ * with existing readMessage() of GsmPDUHelper
+ */
+ readMessage: function() {
+ let message = {};
+
+ // Teleservice Identifier
+ message.teleservice = this.readInt();
+
+ // Message Type
+ let isServicePresent = this.readByte();
+ if (isServicePresent) {
+ message.messageType = PDU_CDMA_MSG_TYPE_BROADCAST;
+ } else {
+ if (message.teleservice) {
+ message.messageType = PDU_CDMA_MSG_TYPE_P2P;
+ } else {
+ message.messageType = PDU_CDMA_MSG_TYPE_ACK;
+ }
+ }
+
+ // Service Category
+ message.service = this.readInt();
+
+ // Originated Address
+ let addrInfo = {};
+ addrInfo.digitMode = (this.readInt() & 0x01);
+ addrInfo.numberMode = (this.readInt() & 0x01);
+ addrInfo.numberType = (this.readInt() & 0x01);
+ addrInfo.numberPlan = (this.readInt() & 0x01);
+ addrInfo.addrLength = this.readByte();
+ addrInfo.address = [];
+ for (let i = 0; i < addrInfo.addrLength; i++) {
+ addrInfo.address.push(this.readByte());
+ }
+ message.sender = this.decodeAddr(addrInfo);
+
+ // Originated Subaddress
+ addrInfo.Type = (this.readInt() & 0x07);
+ addrInfo.Odd = (this.readByte() & 0x01);
+ addrInfo.addrLength = this.readByte();
+ for (let i = 0; i < addrInfo.addrLength; i++) {
+ let addrDigit = this.readByte();
+ message.sender += String.fromCharCode(addrDigit);
+ }
+
+ // Bearer Data
+ this.decodeUserData(message);
+
+ // Bearer Data Sub-Parameter: User Data
+ let userData = message[PDU_CDMA_MSG_USERDATA_BODY];
+ [message.header, message.body, message.encoding, message.data] =
+ (userData) ? [userData.header, userData.body, userData.encoding, userData.data]
+ : [null, null, null, null];
+
+ // Bearer Data Sub-Parameter: Message Status
+ // Success Delivery (0) if both Message Status and User Data are absent.
+ // Message Status absent (-1) if only User Data is available.
+ let msgStatus = message[PDU_CDMA_MSG_USER_DATA_MSG_STATUS];
+ [message.errorClass, message.msgStatus] =
+ (msgStatus) ? [msgStatus.errorClass, msgStatus.msgStatus]
+ : ((message.body) ? [-1, -1] : [0, 0]);
+
+ // Transform message to GSM msg
+ let msg = {
+ SMSC: "",
+ mti: 0,
+ udhi: 0,
+ sender: message.sender,
+ recipient: null,
+ pid: PDU_PID_DEFAULT,
+ epid: PDU_PID_DEFAULT,
+ dcs: 0,
+ mwi: null,
+ replace: false,
+ header: message.header,
+ body: message.body,
+ data: message.data,
+ sentTimestamp: message[PDU_CDMA_MSG_USERDATA_TIMESTAMP],
+ language: message[PDU_CDMA_LANGUAGE_INDICATOR],
+ status: null,
+ scts: null,
+ dt: null,
+ encoding: message.encoding,
+ messageClass: GECKO_SMS_MESSAGE_CLASSES[PDU_DCS_MSG_CLASS_NORMAL],
+ messageType: message.messageType,
+ serviceCategory: message.service,
+ subMsgType: message[PDU_CDMA_MSG_USERDATA_MSG_ID].msgType,
+ msgId: message[PDU_CDMA_MSG_USERDATA_MSG_ID].msgId,
+ errorClass: message.errorClass,
+ msgStatus: message.msgStatus,
+ teleservice: message.teleservice
+ };
+
+ return msg;
+ },
+
+ /**
+ * Helper for processing received SMS parcel data.
+ *
+ * @param length
+ * Length of SMS string in the incoming parcel.
+ *
+ * @return Message parsed or null for invalid message.
+ */
+ processReceivedSms: function(length) {
+ if (!length) {
+ if (DEBUG) this.context.debug("Received empty SMS!");
+ return [null, PDU_FCS_UNSPECIFIED];
+ }
+
+ let message = this.readMessage();
+ if (DEBUG) this.context.debug("Got new SMS: " + JSON.stringify(message));
+
+ // Determine result
+ if (!message) {
+ return [null, PDU_FCS_UNSPECIFIED];
+ }
+
+ return [message, PDU_FCS_OK];
+ },
+
+ /**
+ * Data readers
+ */
+ readInt: function() {
+ return this.context.Buf.readInt32();
+ },
+
+ readByte: function() {
+ return (this.context.Buf.readInt32() & 0xFF);
+ },
+
+ /**
+ * Decode CDMA address data into address string
+ *
+ * @see 3GGP2 C.S0015-B 2.0, 3.4.3.3 Address Parameters
+ *
+ * Required key in addrInfo
+ * @param addrLength
+ * Length of address
+ * @param digitMode
+ * Address encoding method
+ * @param address
+ * Array of encoded address data
+ */
+ decodeAddr: function(addrInfo) {
+ let result = "";
+ for (let i = 0; i < addrInfo.addrLength; i++) {
+ if (addrInfo.digitMode === PDU_CDMA_MSG_ADDR_DIGIT_MODE_DTMF) {
+ result += this.dtmfChars.charAt(addrInfo.address[i]);
+ } else {
+ result += String.fromCharCode(addrInfo.address[i]);
+ }
+ }
+ return result;
+ },
+
+ /**
+ * Read userData in parcel buffer and decode into message object.
+ * Each subparameter will be stored in corresponding key.
+ *
+ * @see 3GGP2 C.S0015-B 2.0, 3.4.3.7 Bearer Data
+ * 4.5 Bearer Data Parameters
+ */
+ decodeUserData: function(message) {
+ let userDataLength = this.readInt();
+
+ while (userDataLength > 0) {
+ let id = this.readByte(),
+ length = this.readByte(),
+ userDataBuffer = [];
+
+ for (let i = 0; i < length; i++) {
+ userDataBuffer.push(this.readByte());
+ }
+
+ this.context.BitBufferHelper.startRead(userDataBuffer);
+
+ switch (id) {
+ case PDU_CDMA_MSG_USERDATA_MSG_ID:
+ message[id] = this.decodeUserDataMsgId();
+ break;
+ case PDU_CDMA_MSG_USERDATA_BODY:
+ message[id] = this.decodeUserDataMsg(message[PDU_CDMA_MSG_USERDATA_MSG_ID].userHeader);
+ break;
+ case PDU_CDMA_MSG_USERDATA_TIMESTAMP:
+ message[id] = this.decodeUserDataTimestamp();
+ break;
+ case PDU_CDMA_MSG_USERDATA_REPLY_OPTION:
+ message[id] = this.decodeUserDataReplyOption();
+ break;
+ case PDU_CDMA_LANGUAGE_INDICATOR:
+ message[id] = this.decodeLanguageIndicator();
+ break;
+ case PDU_CDMA_MSG_USERDATA_CALLBACK_NUMBER:
+ message[id] = this.decodeUserDataCallbackNumber();
+ break;
+ case PDU_CDMA_MSG_USER_DATA_MSG_STATUS:
+ message[id] = this.decodeUserDataMsgStatus();
+ break;
+ }
+
+ userDataLength -= (length + 2);
+ userDataBuffer = [];
+ }
+ },
+
+ /**
+ * User data subparameter decoder: Message Identifier
+ *
+ * @see 3GGP2 C.S0015-B 2.0, 4.5.1 Message Identifier
+ */
+ decodeUserDataMsgId: function() {
+ let result = {};
+ let BitBufferHelper = this.context.BitBufferHelper;
+ result.msgType = BitBufferHelper.readBits(4);
+ result.msgId = BitBufferHelper.readBits(16);
+ result.userHeader = BitBufferHelper.readBits(1);
+
+ return result;
+ },
+
+ /**
+ * Decode user data header, we only care about segment information
+ * on CDMA.
+ *
+ * This function is mostly copied from gsmPduHelper.readUserDataHeader() but
+ * change the read function, because CDMA user header decoding is't byte-wise
+ * aligned.
+ */
+ decodeUserDataHeader: function(encoding) {
+ let BitBufferHelper = this.context.BitBufferHelper;
+ let header = {},
+ headerSize = BitBufferHelper.readBits(8),
+ userDataHeaderSize = headerSize + 1,
+ headerPaddingBits = 0;
+
+ // Calculate header size
+ if (encoding === PDU_DCS_MSG_CODING_7BITS_ALPHABET) {
+ // Length is in 7-bit
+ header.length = Math.ceil(userDataHeaderSize * 8 / 7);
+ // Calulate padding length
+ headerPaddingBits = (header.length * 7) - (userDataHeaderSize * 8);
+ } else if (encoding === PDU_DCS_MSG_CODING_8BITS_ALPHABET) {
+ header.length = userDataHeaderSize;
+ } else {
+ header.length = userDataHeaderSize / 2;
+ }
+
+ while (headerSize) {
+ let identifier = BitBufferHelper.readBits(8),
+ length = BitBufferHelper.readBits(8);
+
+ headerSize -= (2 + length);
+
+ switch (identifier) {
+ case PDU_IEI_CONCATENATED_SHORT_MESSAGES_8BIT: {
+ let ref = BitBufferHelper.readBits(8),
+ max = BitBufferHelper.readBits(8),
+ seq = BitBufferHelper.readBits(8);
+ if (max && seq && (seq <= max)) {
+ header.segmentRef = ref;
+ header.segmentMaxSeq = max;
+ header.segmentSeq = seq;
+ }
+ break;
+ }
+ case PDU_IEI_APPLICATION_PORT_ADDRESSING_SCHEME_8BIT: {
+ let dstp = BitBufferHelper.readBits(8),
+ orip = BitBufferHelper.readBits(8);
+ if ((dstp < PDU_APA_RESERVED_8BIT_PORTS)
+ || (orip < PDU_APA_RESERVED_8BIT_PORTS)) {
+ // 3GPP TS 23.040 clause 9.2.3.24.3: "A receiving entity shall
+ // ignore any information element where the value of the
+ // Information-Element-Data is Reserved or not supported"
+ break;
+ }
+ header.destinationPort = dstp;
+ header.originatorPort = orip;
+ break;
+ }
+ case PDU_IEI_APPLICATION_PORT_ADDRESSING_SCHEME_16BIT: {
+ let dstp = (BitBufferHelper.readBits(8) << 8) | BitBufferHelper.readBits(8),
+ orip = (BitBufferHelper.readBits(8) << 8) | BitBufferHelper.readBits(8);
+ // 3GPP TS 23.040 clause 9.2.3.24.4: "A receiving entity shall
+ // ignore any information element where the value of the
+ // Information-Element-Data is Reserved or not supported"
+ if ((dstp < PDU_APA_VALID_16BIT_PORTS)
+ && (orip < PDU_APA_VALID_16BIT_PORTS)) {
+ header.destinationPort = dstp;
+ header.originatorPort = orip;
+ }
+ break;
+ }
+ case PDU_IEI_CONCATENATED_SHORT_MESSAGES_16BIT: {
+ let ref = (BitBufferHelper.readBits(8) << 8) | BitBufferHelper.readBits(8),
+ max = BitBufferHelper.readBits(8),
+ seq = BitBufferHelper.readBits(8);
+ if (max && seq && (seq <= max)) {
+ header.segmentRef = ref;
+ header.segmentMaxSeq = max;
+ header.segmentSeq = seq;
+ }
+ break;
+ }
+ case PDU_IEI_NATIONAL_LANGUAGE_SINGLE_SHIFT: {
+ let langShiftIndex = BitBufferHelper.readBits(8);
+ if (langShiftIndex < PDU_NL_SINGLE_SHIFT_TABLES.length) {
+ header.langShiftIndex = langShiftIndex;
+ }
+ break;
+ }
+ case PDU_IEI_NATIONAL_LANGUAGE_LOCKING_SHIFT: {
+ let langIndex = BitBufferHelper.readBits(8);
+ if (langIndex < PDU_NL_LOCKING_SHIFT_TABLES.length) {
+ header.langIndex = langIndex;
+ }
+ break;
+ }
+ case PDU_IEI_SPECIAL_SMS_MESSAGE_INDICATION: {
+ let msgInd = BitBufferHelper.readBits(8) & 0xFF,
+ msgCount = BitBufferHelper.readBits(8);
+ /*
+ * TS 23.040 V6.8.1 Sec 9.2.3.24.2
+ * bits 1 0 : basic message indication type
+ * bits 4 3 2 : extended message indication type
+ * bits 6 5 : Profile id
+ * bit 7 : storage type
+ */
+ let storeType = msgInd & PDU_MWI_STORE_TYPE_BIT;
+ header.mwi = {};
+ mwi = header.mwi;
+
+ if (storeType == PDU_MWI_STORE_TYPE_STORE) {
+ // Store message because TP_UDH indicates so, note this may override
+ // the setting in DCS, but that is expected
+ mwi.discard = false;
+ } else if (mwi.discard === undefined) {
+ // storeType == PDU_MWI_STORE_TYPE_DISCARD
+ // only override mwi.discard here if it hasn't already been set
+ mwi.discard = true;
+ }
+
+ mwi.msgCount = msgCount & 0xFF;
+ mwi.active = mwi.msgCount > 0;
+
+ if (DEBUG) {
+ this.context.debug("MWI in TP_UDH received: " + JSON.stringify(mwi));
+ }
+ break;
+ }
+ default:
+ // Drop unsupported id
+ for (let i = 0; i < length; i++) {
+ BitBufferHelper.readBits(8);
+ }
+ }
+ }
+
+ // Consume padding bits
+ if (headerPaddingBits) {
+ BitBufferHelper.readBits(headerPaddingBits);
+ }
+
+ return header;
+ },
+
+ getCdmaMsgEncoding: function(encoding) {
+ // Determine encoding method
+ switch (encoding) {
+ case PDU_CDMA_MSG_CODING_7BITS_ASCII:
+ case PDU_CDMA_MSG_CODING_IA5:
+ case PDU_CDMA_MSG_CODING_7BITS_GSM:
+ return PDU_DCS_MSG_CODING_7BITS_ALPHABET;
+ case PDU_CDMA_MSG_CODING_OCTET:
+ case PDU_CDMA_MSG_CODING_IS_91:
+ case PDU_CDMA_MSG_CODING_LATIN_HEBREW:
+ case PDU_CDMA_MSG_CODING_LATIN:
+ return PDU_DCS_MSG_CODING_8BITS_ALPHABET;
+ case PDU_CDMA_MSG_CODING_UNICODE:
+ case PDU_CDMA_MSG_CODING_SHIFT_JIS:
+ case PDU_CDMA_MSG_CODING_KOREAN:
+ return PDU_DCS_MSG_CODING_16BITS_ALPHABET;
+ }
+ return null;
+ },
+
+ decodeCdmaPDUMsg: function(encoding, msgType, msgBodySize) {
+ const langTable = PDU_NL_LOCKING_SHIFT_TABLES[PDU_NL_IDENTIFIER_DEFAULT];
+ const langShiftTable = PDU_NL_SINGLE_SHIFT_TABLES[PDU_NL_IDENTIFIER_DEFAULT];
+ let BitBufferHelper = this.context.BitBufferHelper;
+ let result = "";
+ let msgDigit;
+ switch (encoding) {
+ case PDU_CDMA_MSG_CODING_OCTET: // TODO : Require Test
+ while(msgBodySize > 0) {
+ msgDigit = String.fromCharCode(BitBufferHelper.readBits(8));
+ result += msgDigit;
+ msgBodySize--;
+ }
+ break;
+ case PDU_CDMA_MSG_CODING_IS_91: // TODO : Require Test
+ // Referenced from android code
+ switch (msgType) {
+ case PDU_CDMA_MSG_CODING_IS_91_TYPE_SMS:
+ case PDU_CDMA_MSG_CODING_IS_91_TYPE_SMS_FULL:
+ case PDU_CDMA_MSG_CODING_IS_91_TYPE_VOICEMAIL_STATUS:
+ while(msgBodySize > 0) {
+ msgDigit = String.fromCharCode(BitBufferHelper.readBits(6) + 0x20);
+ result += msgDigit;
+ msgBodySize--;
+ }
+ break;
+ case PDU_CDMA_MSG_CODING_IS_91_TYPE_CLI:
+ let addrInfo = {};
+ addrInfo.digitMode = PDU_CDMA_MSG_ADDR_DIGIT_MODE_DTMF;
+ addrInfo.numberMode = PDU_CDMA_MSG_ADDR_NUMBER_MODE_ANSI;
+ addrInfo.numberType = PDU_CDMA_MSG_ADDR_NUMBER_TYPE_UNKNOWN;
+ addrInfo.numberPlan = PDU_CDMA_MSG_ADDR_NUMBER_PLAN_UNKNOWN;
+ addrInfo.addrLength = msgBodySize;
+ addrInfo.address = [];
+ for (let i = 0; i < addrInfo.addrLength; i++) {
+ addrInfo.address.push(BitBufferHelper.readBits(4));
+ }
+ result = this.decodeAddr(addrInfo);
+ break;
+ }
+ // Fall through.
+ case PDU_CDMA_MSG_CODING_7BITS_ASCII:
+ case PDU_CDMA_MSG_CODING_IA5: // TODO : Require Test
+ while(msgBodySize > 0) {
+ msgDigit = BitBufferHelper.readBits(7);
+ if (msgDigit >= 32) {
+ msgDigit = String.fromCharCode(msgDigit);
+ } else {
+ if (msgDigit !== PDU_NL_EXTENDED_ESCAPE) {
+ msgDigit = langTable[msgDigit];
+ } else {
+ msgDigit = BitBufferHelper.readBits(7);
+ msgBodySize--;
+ msgDigit = langShiftTable[msgDigit];
+ }
+ }
+ result += msgDigit;
+ msgBodySize--;
+ }
+ break;
+ case PDU_CDMA_MSG_CODING_UNICODE:
+ while(msgBodySize > 0) {
+ msgDigit = String.fromCharCode(BitBufferHelper.readBits(16));
+ result += msgDigit;
+ msgBodySize--;
+ }
+ break;
+ case PDU_CDMA_MSG_CODING_7BITS_GSM: // TODO : Require Test
+ while(msgBodySize > 0) {
+ msgDigit = BitBufferHelper.readBits(7);
+ if (msgDigit !== PDU_NL_EXTENDED_ESCAPE) {
+ msgDigit = langTable[msgDigit];
+ } else {
+ msgDigit = BitBufferHelper.readBits(7);
+ msgBodySize--;
+ msgDigit = langShiftTable[msgDigit];
+ }
+ result += msgDigit;
+ msgBodySize--;
+ }
+ break;
+ case PDU_CDMA_MSG_CODING_LATIN: // TODO : Require Test
+ // Reference : http://en.wikipedia.org/wiki/ISO/IEC_8859-1
+ while(msgBodySize > 0) {
+ msgDigit = String.fromCharCode(BitBufferHelper.readBits(8));
+ result += msgDigit;
+ msgBodySize--;
+ }
+ break;
+ case PDU_CDMA_MSG_CODING_LATIN_HEBREW: // TODO : Require Test
+ // Reference : http://en.wikipedia.org/wiki/ISO/IEC_8859-8
+ while(msgBodySize > 0) {
+ msgDigit = BitBufferHelper.readBits(8);
+ if (msgDigit === 0xDF) {
+ msgDigit = String.fromCharCode(0x2017);
+ } else if (msgDigit === 0xFD) {
+ msgDigit = String.fromCharCode(0x200E);
+ } else if (msgDigit === 0xFE) {
+ msgDigit = String.fromCharCode(0x200F);
+ } else if (msgDigit >= 0xE0 && msgDigit <= 0xFA) {
+ msgDigit = String.fromCharCode(0x4F0 + msgDigit);
+ } else {
+ msgDigit = String.fromCharCode(msgDigit);
+ }
+ result += msgDigit;
+ msgBodySize--;
+ }
+ break;
+ case PDU_CDMA_MSG_CODING_SHIFT_JIS:
+ // Reference : http://msdn.microsoft.com/en-US/goglobal/cc305152.aspx
+ // http://demo.icu-project.org/icu-bin/convexp?conv=Shift_JIS
+ let shift_jis_message = [];
+
+ while (msgBodySize > 0) {
+ shift_jis_message.push(BitBufferHelper.readBits(8));
+ msgBodySize--;
+ }
+
+ let decoder = new TextDecoder("shift_jis");
+ result = decoder.decode(new Uint8Array(shift_jis_message));
+ break;
+ case PDU_CDMA_MSG_CODING_KOREAN:
+ case PDU_CDMA_MSG_CODING_GSM_DCS:
+ // Fall through.
+ default:
+ break;
+ }
+ return result;
+ },
+
+ /**
+ * User data subparameter decoder : User Data
+ *
+ * @see 3GGP2 C.S0015-B 2.0, 4.5.2 User Data
+ */
+ decodeUserDataMsg: function(hasUserHeader) {
+ let BitBufferHelper = this.context.BitBufferHelper;
+ let result = {},
+ encoding = BitBufferHelper.readBits(5),
+ msgType;
+
+ if (encoding === PDU_CDMA_MSG_CODING_IS_91) {
+ msgType = BitBufferHelper.readBits(8);
+ }
+ result.encoding = this.getCdmaMsgEncoding(encoding);
+
+ let msgBodySize = BitBufferHelper.readBits(8);
+
+ // For segmented SMS, a user header is included before sms content
+ if (hasUserHeader) {
+ result.header = this.decodeUserDataHeader(result.encoding);
+ // header size is included in body size, they are decoded
+ msgBodySize -= result.header.length;
+ }
+
+ // Store original payload if enconding is OCTET for further handling of WAP Push, etc.
+ if (encoding === PDU_CDMA_MSG_CODING_OCTET && msgBodySize > 0) {
+ result.data = new Uint8Array(msgBodySize);
+ for (let i = 0; i < msgBodySize; i++) {
+ result.data[i] = BitBufferHelper.readBits(8);
+ }
+ BitBufferHelper.backwardReadPilot(8 * msgBodySize);
+ }
+
+ // Decode sms content
+ result.body = this.decodeCdmaPDUMsg(encoding, msgType, msgBodySize);
+
+ return result;
+ },
+
+ decodeBcd: function(value) {
+ return ((value >> 4) & 0xF) * 10 + (value & 0x0F);
+ },
+
+ /**
+ * User data subparameter decoder : Time Stamp
+ *
+ * @see 3GGP2 C.S0015-B 2.0, 4.5.4 Message Center Time Stamp
+ */
+ decodeUserDataTimestamp: function() {
+ let BitBufferHelper = this.context.BitBufferHelper;
+ let year = this.decodeBcd(BitBufferHelper.readBits(8)),
+ month = this.decodeBcd(BitBufferHelper.readBits(8)) - 1,
+ date = this.decodeBcd(BitBufferHelper.readBits(8)),
+ hour = this.decodeBcd(BitBufferHelper.readBits(8)),
+ min = this.decodeBcd(BitBufferHelper.readBits(8)),
+ sec = this.decodeBcd(BitBufferHelper.readBits(8));
+
+ if (year >= 96 && year <= 99) {
+ year += 1900;
+ } else {
+ year += 2000;
+ }
+
+ let result = (new Date(year, month, date, hour, min, sec, 0)).valueOf();
+
+ return result;
+ },
+
+ /**
+ * User data subparameter decoder : Reply Option
+ *
+ * @see 3GGP2 C.S0015-B 2.0, 4.5.11 Reply Option
+ */
+ decodeUserDataReplyOption: function() {
+ let replyAction = this.context.BitBufferHelper.readBits(4),
+ result = { userAck: (replyAction & 0x8) ? true : false,
+ deliverAck: (replyAction & 0x4) ? true : false,
+ readAck: (replyAction & 0x2) ? true : false,
+ report: (replyAction & 0x1) ? true : false
+ };
+
+ return result;
+ },
+
+ /**
+ * User data subparameter decoder : Language Indicator
+ *
+ * @see 3GGP2 C.S0015-B 2.0, 4.5.14 Language Indicator
+ */
+ decodeLanguageIndicator: function() {
+ let language = this.context.BitBufferHelper.readBits(8);
+ let result = CB_CDMA_LANG_GROUP[language];
+ return result;
+ },
+
+ /**
+ * User data subparameter decoder : Call-Back Number
+ *
+ * @see 3GGP2 C.S0015-B 2.0, 4.5.15 Call-Back Number
+ */
+ decodeUserDataCallbackNumber: function() {
+ let BitBufferHelper = this.context.BitBufferHelper;
+ let digitMode = BitBufferHelper.readBits(1);
+ if (digitMode) {
+ let numberType = BitBufferHelper.readBits(3),
+ numberPlan = BitBufferHelper.readBits(4);
+ }
+ let numberFields = BitBufferHelper.readBits(8),
+ result = "";
+ for (let i = 0; i < numberFields; i++) {
+ if (digitMode === PDU_CDMA_MSG_ADDR_DIGIT_MODE_DTMF) {
+ let addrDigit = BitBufferHelper.readBits(4);
+ result += this.dtmfChars.charAt(addrDigit);
+ } else {
+ let addrDigit = BitBufferHelper.readBits(8);
+ result += String.fromCharCode(addrDigit);
+ }
+ }
+
+ return result;
+ },
+
+ /**
+ * User data subparameter decoder : Message Status
+ *
+ * @see 3GGP2 C.S0015-B 2.0, 4.5.21 Message Status
+ */
+ decodeUserDataMsgStatus: function() {
+ let BitBufferHelper = this.context.BitBufferHelper;
+ let result = {
+ errorClass: BitBufferHelper.readBits(2),
+ msgStatus: BitBufferHelper.readBits(6)
+ };
+
+ return result;
+ },
+
+ /**
+ * Decode information record parcel.
+ */
+ decodeInformationRecord: function() {
+ let Buf = this.context.Buf;
+ let records = [];
+ let numOfRecords = Buf.readInt32();
+
+ let type;
+ let record;
+ for (let i = 0; i < numOfRecords; i++) {
+ record = {};
+ type = Buf.readInt32();
+
+ switch (type) {
+ /*
+ * Every type is encaped by ril, except extended display
+ */
+ case PDU_CDMA_INFO_REC_TYPE_DISPLAY:
+ case PDU_CDMA_INFO_REC_TYPE_EXTENDED_DISPLAY:
+ record.display = Buf.readString();
+ break;
+ case PDU_CDMA_INFO_REC_TYPE_CALLED_PARTY_NUMBER:
+ record.calledNumber = {};
+ record.calledNumber.number = Buf.readString();
+ record.calledNumber.type = Buf.readInt32();
+ record.calledNumber.plan = Buf.readInt32();
+ record.calledNumber.pi = Buf.readInt32();
+ record.calledNumber.si = Buf.readInt32();
+ break;
+ case PDU_CDMA_INFO_REC_TYPE_CALLING_PARTY_NUMBER:
+ record.callingNumber = {};
+ record.callingNumber.number = Buf.readString();
+ record.callingNumber.type = Buf.readInt32();
+ record.callingNumber.plan = Buf.readInt32();
+ record.callingNumber.pi = Buf.readInt32();
+ record.callingNumber.si = Buf.readInt32();
+ break;
+ case PDU_CDMA_INFO_REC_TYPE_CONNECTED_NUMBER:
+ record.connectedNumber = {};
+ record.connectedNumber.number = Buf.readString();
+ record.connectedNumber.type = Buf.readInt32();
+ record.connectedNumber.plan = Buf.readInt32();
+ record.connectedNumber.pi = Buf.readInt32();
+ record.connectedNumber.si = Buf.readInt32();
+ break;
+ case PDU_CDMA_INFO_REC_TYPE_SIGNAL:
+ record.signal = {};
+ if (!Buf.readInt32()) { // Non-zero if signal is present.
+ Buf.seekIncoming(3 * Buf.UINT32_SIZE);
+ continue;
+ }
+ record.signal.type = Buf.readInt32();
+ record.signal.alertPitch = Buf.readInt32();
+ record.signal.signal = Buf.readInt32();
+ break;
+ case PDU_CDMA_INFO_REC_TYPE_REDIRECTING_NUMBER:
+ record.redirect = {};
+ record.redirect.number = Buf.readString();
+ record.redirect.type = Buf.readInt32();
+ record.redirect.plan = Buf.readInt32();
+ record.redirect.pi = Buf.readInt32();
+ record.redirect.si = Buf.readInt32();
+ record.redirect.reason = Buf.readInt32();
+ break;
+ case PDU_CDMA_INFO_REC_TYPE_LINE_CONTROL:
+ record.lineControl = {};
+ record.lineControl.polarityIncluded = Buf.readInt32();
+ record.lineControl.toggle = Buf.readInt32();
+ record.lineControl.reverse = Buf.readInt32();
+ record.lineControl.powerDenial = Buf.readInt32();
+ break;
+ case PDU_CDMA_INFO_REC_TYPE_T53_CLIR:
+ record.clirCause = Buf.readInt32();
+ break;
+ case PDU_CDMA_INFO_REC_TYPE_T53_AUDIO_CONTROL:
+ record.audioControl = {};
+ record.audioControl.upLink = Buf.readInt32();
+ record.audioControl.downLink = Buf.readInt32();
+ break;
+ case PDU_CDMA_INFO_REC_TYPE_T53_RELEASE:
+ // Fall through
+ default:
+ throw new Error("UNSOLICITED_CDMA_INFO_REC(), Unsupported information record type " + type + "\n");
+ }
+
+ records.push(record);
+ }
+
+ return records;
+ }
+};
+
+/**
+ * Helper for processing ICC PDUs.
+ */
+function ICCPDUHelperObject(aContext) {
+ this.context = aContext;
+}
+ICCPDUHelperObject.prototype = {
+ context: null,
+
+ /**
+ * Read GSM 8-bit unpacked octets,
+ * which are default 7-bit alphabets with bit 8 set to 0.
+ *
+ * @param numOctets
+ * Number of octets to be read.
+ */
+ read8BitUnpackedToString: function(numOctets) {
+ let GsmPDUHelper = this.context.GsmPDUHelper;
+
+ let ret = "";
+ let escapeFound = false;
+ let i;
+ const langTable = PDU_NL_LOCKING_SHIFT_TABLES[PDU_NL_IDENTIFIER_DEFAULT];
+ const langShiftTable = PDU_NL_SINGLE_SHIFT_TABLES[PDU_NL_IDENTIFIER_DEFAULT];
+
+ for(i = 0; i < numOctets; i++) {
+ let octet = GsmPDUHelper.readHexOctet();
+ if (octet == 0xff) {
+ i++;
+ break;
+ }
+
+ if (escapeFound) {
+ escapeFound = false;
+ if (octet == PDU_NL_EXTENDED_ESCAPE) {
+ // According to 3GPP TS 23.038, section 6.2.1.1, NOTE 1, "On
+ // receipt of this code, a receiving entity shall display a space
+ // until another extensiion table is defined."
+ ret += " ";
+ } else if (octet == PDU_NL_RESERVED_CONTROL) {
+ // According to 3GPP TS 23.038 B.2, "This code represents a control
+ // character and therefore must not be used for language specific
+ // characters."
+ ret += " ";
+ } else {
+ ret += langShiftTable[octet];
+ }
+ } else if (octet == PDU_NL_EXTENDED_ESCAPE) {
+ escapeFound = true;
+ } else {
+ ret += langTable[octet];
+ }
+ }
+
+ let Buf = this.context.Buf;
+ Buf.seekIncoming((numOctets - i) * Buf.PDU_HEX_OCTET_SIZE);
+ return ret;
+ },
+
+ /**
+ * Write GSM 8-bit unpacked octets.
+ *
+ * @param numOctets Number of total octets to be writen, including trailing
+ * 0xff.
+ * @param str String to be written. Could be null.
+ *
+ * @return The string has been written into Buf. "" if str is null.
+ */
+ writeStringTo8BitUnpacked: function(numOctets, str) {
+ const langTable = PDU_NL_LOCKING_SHIFT_TABLES[PDU_NL_IDENTIFIER_DEFAULT];
+ const langShiftTable = PDU_NL_SINGLE_SHIFT_TABLES[PDU_NL_IDENTIFIER_DEFAULT];
+
+ let GsmPDUHelper = this.context.GsmPDUHelper;
+
+ // If the character is GSM extended alphabet, two octets will be written.
+ // So we need to keep track of number of octets to be written.
+ let i, j;
+ let len = str ? str.length : 0;
+ for (i = 0, j = 0; i < len && j < numOctets; i++) {
+ let c = str.charAt(i);
+ let octet = langTable.indexOf(c);
+
+ if (octet == -1) {
+ // Make sure we still have enough space to write two octets.
+ if (j + 2 > numOctets) {
+ break;
+ }
+
+ octet = langShiftTable.indexOf(c);
+ if (octet == -1) {
+ // Fallback to ASCII space.
+ octet = langTable.indexOf(' ');
+ } else {
+ GsmPDUHelper.writeHexOctet(PDU_NL_EXTENDED_ESCAPE);
+ j++;
+ }
+ }
+ GsmPDUHelper.writeHexOctet(octet);
+ j++;
+ }
+
+ // trailing 0xff
+ while (j++ < numOctets) {
+ GsmPDUHelper.writeHexOctet(0xff);
+ }
+
+ return (str) ? str.substring(0, i) : "";
+ },
+
+ /**
+ * Write UCS2 String on UICC.
+ * The default choose 0x81 or 0x82 encode, otherwise use 0x80 encode.
+ *
+ * @see TS 102.221, Annex A.
+ * @param numOctets
+ * Total number of octets to be written. This includes the length of
+ * alphaId and the length of trailing unused octets(0xff).
+ * @param str
+ * String to be written.
+ *
+ * @return The string has been written into Buf.
+ */
+ writeICCUCS2String: function(numOctets, str) {
+ let GsmPDUHelper = this.context.GsmPDUHelper;
+ let scheme = 0x80;
+ let basePointer;
+
+ if (str.length > 2) {
+ let min = 0xFFFF;
+ let max = 0;
+ for (let i = 0; i < str.length; i++) {
+ let code = str.charCodeAt(i);
+ // filter out GSM Default Alphabet character
+ if (code & 0xFF80) {
+ if (min > code) {
+ min = code;
+ }
+ if (max < code) {
+ max = code;
+ }
+ }
+ }
+
+ // 0x81 and 0x82 only support 'half-page', i.e., 128 characters.
+ if ((max - min) >= 0 && (max - min) < 128) {
+ // 0x81 base pointer is 0hhh hhhh h000 0000, and bit 16 is set to zero,
+ // therefore it can't compute 0x8000~0xFFFF.
+ // Since 0x81 only support 128 characters,
+ // either XX00~XX7f(bit 8 are 0) or XX80~XXff(bit 8 are 1)
+ if (((min & 0x7f80) == (max & 0x7f80)) &&
+ ((max & 0x8000) == 0)) {
+ scheme = 0x81;
+ basePointer = min & 0x7f80;
+ } else {
+ scheme = 0x82;
+ basePointer = min;
+ }
+ }
+ }
+
+ switch (scheme) {
+ /**
+ * +------+---------+---------+---------+---------+------+------+
+ * | 0x80 | Ch1_msb | Ch1_lsb | Ch2_msb | Ch2_lsb | 0xff | 0xff |
+ * +------+---------+---------+---------+---------+------+------+
+ */
+ case 0x80: {
+ // 0x80 support UCS2 0000~ffff
+ GsmPDUHelper.writeHexOctet(0x80);
+ numOctets--;
+ // Now the str is UCS2 string, each character will take 2 octets.
+ if (str.length * 2 > numOctets) {
+ str = str.substring(0, Math.floor(numOctets / 2));
+ }
+ GsmPDUHelper.writeUCS2String(str);
+
+ // trailing 0xff
+ for (let i = str.length * 2; i < numOctets; i++) {
+ GsmPDUHelper.writeHexOctet(0xff);
+ }
+ return str;
+ }
+ /**
+ * +------+-----+--------------+-----+-----+-----+--------+------+
+ * | 0x81 | len | base_pointer | Ch1 | Ch2 | ... | Ch_len | 0xff |
+ * +------+-----+--------------+-----+-----+-----+--------+------+
+ *
+ * len: The length of characters.
+ * base_pointer: 0hhh hhhh h000 0000
+ * Ch_n: bit 8 = 0
+ * GSM default alphabets
+ * bit 8 = 1
+ * UCS2 character whose char code is (Ch_n - base_pointer) | 0x80
+ *
+ */
+ case 0x81: {
+ GsmPDUHelper.writeHexOctet(0x81);
+
+ if (str.length > (numOctets - 3)) {
+ str = str.substring(0, numOctets - 3);
+ }
+
+ GsmPDUHelper.writeHexOctet(str.length);
+ GsmPDUHelper.writeHexOctet((basePointer >> 7) & 0xff);
+ numOctets -= 3;
+ break;
+ }
+ /* +------+-----+------------------+------------------+-----+-----+-----+--------+
+ * | 0x82 | len | base_pointer_msb | base_pointer_lsb | Ch1 | Ch2 | ... | Ch_len |
+ * +------+-----+------------------+------------------+-----+-----+-----+--------+
+ *
+ * len: The length of characters.
+ * base_pointer_msb, base_pointer_lsn: base_pointer
+ * Ch_n: bit 8 = 0
+ * GSM default alphabets
+ * bit 8 = 1
+ * UCS2 character whose char code is (Ch_n - base_pointer) | 0x80
+ */
+ case 0x82: {
+ GsmPDUHelper.writeHexOctet(0x82);
+
+ if (str.length > (numOctets - 4)) {
+ str = str.substring(0, numOctets - 4);
+ }
+
+ GsmPDUHelper.writeHexOctet(str.length);
+ GsmPDUHelper.writeHexOctet((basePointer >> 8) & 0xff);
+ GsmPDUHelper.writeHexOctet(basePointer & 0xff);
+ numOctets -= 4;
+ break;
+ }
+ }
+
+ if (scheme == 0x81 || scheme == 0x82) {
+ for (let i = 0; i < str.length; i++) {
+ let code = str.charCodeAt(i);
+
+ // bit 8 = 0,
+ // GSM default alphabets
+ if (code >> 8 == 0) {
+ GsmPDUHelper.writeHexOctet(code & 0x7F);
+ } else {
+ // bit 8 = 1,
+ // UCS2 character whose char code is (code - basePointer) | 0x80
+ GsmPDUHelper.writeHexOctet((code - basePointer) | 0x80);
+ }
+ }
+
+ // trailing 0xff
+ for (let i = 0; i < numOctets - str.length; i++) {
+ GsmPDUHelper.writeHexOctet(0xff);
+ }
+ }
+ return str;
+ },
+
+ /**
+ * Read UCS2 String on UICC.
+ *
+ * @see TS 101.221, Annex A.
+ * @param scheme
+ * Coding scheme for UCS2 on UICC. One of 0x80, 0x81 or 0x82.
+ * @param numOctets
+ * Number of octets to be read as UCS2 string.
+ */
+ readICCUCS2String: function(scheme, numOctets) {
+ let Buf = this.context.Buf;
+ let GsmPDUHelper = this.context.GsmPDUHelper;
+
+ let str = "";
+ switch (scheme) {
+ /**
+ * +------+---------+---------+---------+---------+------+------+
+ * | 0x80 | Ch1_msb | Ch1_lsb | Ch2_msb | Ch2_lsb | 0xff | 0xff |
+ * +------+---------+---------+---------+---------+------+------+
+ */
+ case 0x80:
+ let isOdd = numOctets % 2;
+ let i;
+ for (i = 0; i < numOctets - isOdd; i += 2) {
+ let code = (GsmPDUHelper.readHexOctet() << 8) | GsmPDUHelper.readHexOctet();
+ if (code == 0xffff) {
+ i += 2;
+ break;
+ }
+ str += String.fromCharCode(code);
+ }
+
+ // Skip trailing 0xff
+ Buf.seekIncoming((numOctets - i) * Buf.PDU_HEX_OCTET_SIZE);
+ break;
+ case 0x81: // Fall through
+ case 0x82:
+ /**
+ * +------+-----+--------+-----+-----+-----+--------+------+
+ * | 0x81 | len | offset | Ch1 | Ch2 | ... | Ch_len | 0xff |
+ * +------+-----+--------+-----+-----+-----+--------+------+
+ *
+ * len : The length of characters.
+ * offset : 0hhh hhhh h000 0000
+ * Ch_n: bit 8 = 0
+ * GSM default alphabets
+ * bit 8 = 1
+ * UCS2 character whose char code is (Ch_n & 0x7f) + offset
+ *
+ * +------+-----+------------+------------+-----+-----+-----+--------+
+ * | 0x82 | len | offset_msb | offset_lsb | Ch1 | Ch2 | ... | Ch_len |
+ * +------+-----+------------+------------+-----+-----+-----+--------+
+ *
+ * len : The length of characters.
+ * offset_msb, offset_lsn: offset
+ * Ch_n: bit 8 = 0
+ * GSM default alphabets
+ * bit 8 = 1
+ * UCS2 character whose char code is (Ch_n & 0x7f) + offset
+ */
+ let len = GsmPDUHelper.readHexOctet();
+ let offset, headerLen;
+ if (scheme == 0x81) {
+ offset = GsmPDUHelper.readHexOctet() << 7;
+ headerLen = 2;
+ } else {
+ offset = (GsmPDUHelper.readHexOctet() << 8) | GsmPDUHelper.readHexOctet();
+ headerLen = 3;
+ }
+
+ for (let i = 0; i < len; i++) {
+ let ch = GsmPDUHelper.readHexOctet();
+ if (ch & 0x80) {
+ // UCS2
+ str += String.fromCharCode((ch & 0x7f) + offset);
+ } else {
+ // GSM 8bit
+ let count = 0, gotUCS2 = 0;
+ while ((i + count + 1 < len)) {
+ count++;
+ if (GsmPDUHelper.readHexOctet() & 0x80) {
+ gotUCS2 = 1;
+ break;
+ }
+ }
+ // Unread.
+ // +1 for the GSM alphabet indexed at i,
+ Buf.seekIncoming(-1 * (count + 1) * Buf.PDU_HEX_OCTET_SIZE);
+ str += this.read8BitUnpackedToString(count + 1 - gotUCS2);
+ i += count - gotUCS2;
+ }
+ }
+
+ // Skipping trailing 0xff
+ Buf.seekIncoming((numOctets - len - headerLen) * Buf.PDU_HEX_OCTET_SIZE);
+ break;
+ }
+ return str;
+ },
+
+ /**
+ * Read Alpha Id and Dialling number from TS TS 151.011 clause 10.5.1
+ *
+ * @param recordSize The size of linear fixed record.
+ */
+ readAlphaIdDiallingNumber: function(recordSize) {
+ let Buf = this.context.Buf;
+ let length = Buf.readInt32();
+
+ let alphaLen = recordSize - ADN_FOOTER_SIZE_BYTES;
+ let alphaId = this.readAlphaIdentifier(alphaLen);
+
+ let number = this.readNumberWithLength();
+
+ // Skip unused octet, CCP
+ Buf.seekIncoming(Buf.PDU_HEX_OCTET_SIZE);
+
+ let extRecordNumber = this.context.GsmPDUHelper.readHexOctet();
+ Buf.readStringDelimiter(length);
+
+ let contact = null;
+ if (alphaId || number) {
+ contact = {alphaId: alphaId,
+ number: number,
+ extRecordNumber: extRecordNumber};
+ }
+
+ return contact;
+ },
+
+ /**
+ * Write Alpha Identifier and Dialling number from TS 151.011 clause 10.5.1
+ *
+ * @param recordSize The size of linear fixed record.
+ * @param alphaId Alpha Identifier to be written.
+ * @param number Dialling Number to be written.
+ * @param extRecordNumber The record identifier of the EXT.
+ *
+ * @return An object contains the alphaId and number
+ * that have been written into Buf.
+ */
+ writeAlphaIdDiallingNumber: function(recordSize, alphaId, number, extRecordNumber) {
+ let Buf = this.context.Buf;
+ let GsmPDUHelper = this.context.GsmPDUHelper;
+
+ // Write String length
+ let strLen = recordSize * 2;
+ Buf.writeInt32(strLen);
+
+ let alphaLen = recordSize - ADN_FOOTER_SIZE_BYTES;
+ let writtenAlphaId = this.writeAlphaIdentifier(alphaLen, alphaId);
+ let writtenNumber = this.writeNumberWithLength(number);
+
+ // Write unused CCP octet 0xff.
+ GsmPDUHelper.writeHexOctet(0xff);
+ GsmPDUHelper.writeHexOctet((extRecordNumber != null) ? extRecordNumber : 0xff);
+
+ Buf.writeStringDelimiter(strLen);
+
+ return {alphaId: writtenAlphaId,
+ number: writtenNumber};
+ },
+
+ /**
+ * Read Alpha Identifier.
+ *
+ * @see TS 131.102
+ *
+ * @param numOctets
+ * Number of octets to be read.
+ *
+ * It uses either
+ * 1. SMS default 7-bit alphabet with bit 8 set to 0.
+ * 2. UCS2 string.
+ *
+ * Unused bytes should be set to 0xff.
+ */
+ readAlphaIdentifier: function(numOctets) {
+ if (numOctets === 0) {
+ return "";
+ }
+
+ let temp;
+ // Read the 1st octet to determine the encoding.
+ if ((temp = this.context.GsmPDUHelper.readHexOctet()) == 0x80 ||
+ temp == 0x81 ||
+ temp == 0x82) {
+ numOctets--;
+ return this.readICCUCS2String(temp, numOctets);
+ } else {
+ let Buf = this.context.Buf;
+ Buf.seekIncoming(-1 * Buf.PDU_HEX_OCTET_SIZE);
+ return this.read8BitUnpackedToString(numOctets);
+ }
+ },
+
+ /**
+ * Write Alpha Identifier.
+ *
+ * @param numOctets
+ * Total number of octets to be written. This includes the length of
+ * alphaId and the length of trailing unused octets(0xff).
+ * @param alphaId
+ * Alpha Identifier to be written.
+ *
+ * @return The Alpha Identifier has been written into Buf.
+ *
+ * Unused octets will be written as 0xff.
+ */
+ writeAlphaIdentifier: function(numOctets, alphaId) {
+ if (numOctets === 0) {
+ return "";
+ }
+
+ // If alphaId is empty or it's of GSM 8 bit.
+ if (!alphaId || this.context.ICCUtilsHelper.isGsm8BitAlphabet(alphaId)) {
+ return this.writeStringTo8BitUnpacked(numOctets, alphaId);
+ } else {
+ return this.writeICCUCS2String(numOctets, alphaId);
+ }
+ },
+
+ /**
+ * Read Dialling number.
+ *
+ * @see TS 131.102
+ *
+ * @param len
+ * The Length of BCD number.
+ *
+ * From TS 131.102, in EF_ADN, EF_FDN, the field 'Length of BCD number'
+ * means the total bytes should be allocated to store the TON/NPI and
+ * the dialing number.
+ * For example, if the dialing number is 1234567890,
+ * and the TON/NPI is 0x81,
+ * The field 'Length of BCD number' should be 06, which is
+ * 1 byte to store the TON/NPI, 0x81
+ * 5 bytes to store the BCD number 2143658709.
+ *
+ * Here the definition of the length is different from SMS spec,
+ * TS 23.040 9.1.2.5, which the length means
+ * "number of useful semi-octets within the Address-Value field".
+ */
+ readDiallingNumber: function(len) {
+ if (DEBUG) this.context.debug("PDU: Going to read Dialling number: " + len);
+ if (len === 0) {
+ return "";
+ }
+
+ let GsmPDUHelper = this.context.GsmPDUHelper;
+
+ // TOA = TON + NPI
+ let toa = GsmPDUHelper.readHexOctet();
+
+ let number = GsmPDUHelper.readSwappedNibbleExtendedBcdString(len - 1);
+ if (number.length <= 0) {
+ if (DEBUG) this.context.debug("No number provided");
+ return "";
+ }
+ if ((toa >> 4) == (PDU_TOA_INTERNATIONAL >> 4)) {
+ number = '+' + number;
+ }
+ return number;
+ },
+
+ /**
+ * Write Dialling Number.
+ *
+ * @param number The Dialling number
+ */
+ writeDiallingNumber: function(number) {
+ let GsmPDUHelper = this.context.GsmPDUHelper;
+
+ let toa = PDU_TOA_ISDN; // 81
+ if (number[0] == '+') {
+ toa = PDU_TOA_INTERNATIONAL | PDU_TOA_ISDN; // 91
+ number = number.substring(1);
+ }
+ GsmPDUHelper.writeHexOctet(toa);
+ GsmPDUHelper.writeSwappedNibbleBCD(number);
+ },
+
+ readNumberWithLength: function() {
+ let Buf = this.context.Buf;
+ let number = "";
+ let numLen = this.context.GsmPDUHelper.readHexOctet();
+ if (numLen != 0xff) {
+ if (numLen > ADN_MAX_BCD_NUMBER_BYTES) {
+ if (DEBUG) {
+ this.context.debug(
+ "Error: invalid length of BCD number/SSC contents - " + numLen);
+ }
+ Buf.seekIncoming(ADN_MAX_BCD_NUMBER_BYTES * Buf.PDU_HEX_OCTET_SIZE);
+ return number;
+ }
+
+ number = this.readDiallingNumber(numLen);
+ Buf.seekIncoming((ADN_MAX_BCD_NUMBER_BYTES - numLen) * Buf.PDU_HEX_OCTET_SIZE);
+ } else {
+ Buf.seekIncoming(ADN_MAX_BCD_NUMBER_BYTES * Buf.PDU_HEX_OCTET_SIZE);
+ }
+
+ return number;
+ },
+
+ /**
+ * Write Number with Length
+ *
+ * @param number The value to be written.
+ *
+ * @return The number has been written into Buf.
+ */
+ writeNumberWithLength: function(number) {
+ let GsmPDUHelper = this.context.GsmPDUHelper;
+
+ if (number) {
+ let numStart = number[0] == "+" ? 1 : 0;
+ let writtenNumber = number.substring(0, numStart) +
+ number.substring(numStart)
+ .replace(/[^0-9*#,]/g, "");
+ let numDigits = writtenNumber.length - numStart;
+
+ if (numDigits > ADN_MAX_NUMBER_DIGITS) {
+ writtenNumber = writtenNumber.substring(0, ADN_MAX_NUMBER_DIGITS + numStart);
+ numDigits = writtenNumber.length - numStart;
+ }
+
+ // +1 for TON/NPI
+ let numLen = Math.ceil(numDigits / 2) + 1;
+ GsmPDUHelper.writeHexOctet(numLen);
+ this.writeDiallingNumber(writtenNumber.replace(/\*/g, "a")
+ .replace(/\#/g, "b")
+ .replace(/\,/g, "c"));
+ // Write trailing 0xff of Dialling Number.
+ for (let i = 0; i < ADN_MAX_BCD_NUMBER_BYTES - numLen; i++) {
+ GsmPDUHelper.writeHexOctet(0xff);
+ }
+ return writtenNumber;
+ } else {
+ // +1 for numLen
+ for (let i = 0; i < ADN_MAX_BCD_NUMBER_BYTES + 1; i++) {
+ GsmPDUHelper.writeHexOctet(0xff);
+ }
+ return "";
+ }
+ }
+};
+
+function StkCommandParamsFactoryObject(aContext) {
+ this.context = aContext;
+}
+StkCommandParamsFactoryObject.prototype = {
+ context: null,
+
+ createParam: function(cmdDetails, ctlvs, onComplete) {
+ let method = this[cmdDetails.typeOfCommand];
+ if (typeof method != "function") {
+ if (DEBUG) {
+ this.context.debug("Unknown proactive command " +
+ cmdDetails.typeOfCommand.toString(16));
+ }
+ return;
+ }
+ method.call(this, cmdDetails, ctlvs, onComplete);
+ },
+
+ loadIcons: function(iconIdCtlvs, callback) {
+ if (!iconIdCtlvs ||
+ !this.context.ICCUtilsHelper.isICCServiceAvailable("IMG")) {
+ callback(null);
+ return;
+ }
+
+ let onerror = (function() {
+ callback(null);
+ }).bind(this);
+
+ let onsuccess = (function(aIcons) {
+ callback(aIcons);
+ }).bind(this);
+
+ this.context.IconLoader.loadIcons(iconIdCtlvs.map(aCtlv => aCtlv.value.identifier),
+ onsuccess,
+ onerror);
+ },
+
+ appendIconIfNecessary: function(iconIdCtlvs, result, onComplete) {
+ this.loadIcons(iconIdCtlvs, (aIcons) => {
+ if (aIcons) {
+ result.icons = aIcons[0];
+ result.iconSelfExplanatory =
+ iconIdCtlvs[0].value.qualifier == 0 ? true : false;
+ }
+
+ onComplete(result);
+ });
+ },
+
+ /**
+ * Construct a param for Refresh.
+ *
+ * @param cmdDetails
+ * The value object of CommandDetails TLV.
+ * @param ctlvs
+ * The all TLVs in this proactive command.
+ * @param onComplete
+ * Callback to be called when complete.
+ */
+ processRefresh: function(cmdDetails, ctlvs, onComplete) {
+ let refreshType = cmdDetails.commandQualifier;
+ switch (refreshType) {
+ case STK_REFRESH_FILE_CHANGE:
+ case STK_REFRESH_NAA_INIT_AND_FILE_CHANGE:
+ let ctlv = this.context.StkProactiveCmdHelper.searchForTag(
+ COMPREHENSIONTLV_TAG_FILE_LIST, ctlvs);
+ if (ctlv) {
+ let list = ctlv.value.fileList;
+ if (DEBUG) {
+ this.context.debug("Refresh, list = " + list);
+ }
+ this.context.ICCRecordHelper.fetchICCRecords();
+ }
+ break;
+ }
+
+ onComplete(null);
+ },
+
+ /**
+ * Construct a param for Poll Interval.
+ *
+ * @param cmdDetails
+ * The value object of CommandDetails TLV.
+ * @param ctlvs
+ * The all TLVs in this proactive command.
+ * @param onComplete
+ * Callback to be called when complete.
+ */
+ processPollInterval: function(cmdDetails, ctlvs, onComplete) {
+ // Duration is mandatory.
+ let ctlv = this.context.StkProactiveCmdHelper.searchForTag(
+ COMPREHENSIONTLV_TAG_DURATION, ctlvs);
+ if (!ctlv) {
+ this.context.RIL.sendStkTerminalResponse({
+ command: cmdDetails,
+ resultCode: STK_RESULT_REQUIRED_VALUES_MISSING});
+ throw new Error("Stk Poll Interval: Required value missing : Duration");
+ }
+
+ onComplete(ctlv.value);
+ },
+
+ /**
+ * Construct a param for Poll Off.
+ *
+ * @param cmdDetails
+ * The value object of CommandDetails TLV.
+ * @param ctlvs
+ * The all TLVs in this proactive command.
+ * @param onComplete
+ * Callback to be called when complete.
+ */
+ processPollOff: function(cmdDetails, ctlvs, onComplete) {
+ onComplete(null);
+ },
+
+ /**
+ * Construct a param for Set Up Event list.
+ *
+ * @param cmdDetails
+ * The value object of CommandDetails TLV.
+ * @param ctlvs
+ * The all TLVs in this proactive command.
+ * @param onComplete
+ * Callback to be called when complete.
+ */
+ processSetUpEventList: function(cmdDetails, ctlvs, onComplete) {
+ // Event list is mandatory.
+ let ctlv = this.context.StkProactiveCmdHelper.searchForTag(
+ COMPREHENSIONTLV_TAG_EVENT_LIST, ctlvs);
+ if (!ctlv) {
+ this.context.RIL.sendStkTerminalResponse({
+ command: cmdDetails,
+ resultCode: STK_RESULT_REQUIRED_VALUES_MISSING});
+ throw new Error("Stk Event List: Required value missing : Event List");
+ }
+
+ onComplete(ctlv.value || { eventList: null });
+ },
+
+ /**
+ * Construct a param for Setup Menu.
+ *
+ * @param cmdDetails
+ * The value object of CommandDetails TLV.
+ * @param ctlvs
+ * The all TLVs in this proactive command.
+ * @param onComplete
+ * Callback to be called when complete.
+ */
+ processSetupMenu: function(cmdDetails, ctlvs, onComplete) {
+ let StkProactiveCmdHelper = this.context.StkProactiveCmdHelper;
+ let menu = {
+ // Help information available.
+ isHelpAvailable: !!(cmdDetails.commandQualifier & 0x80)
+ };
+
+ let selectedCtlvs = StkProactiveCmdHelper.searchForSelectedTags(ctlvs, [
+ COMPREHENSIONTLV_TAG_ALPHA_ID,
+ COMPREHENSIONTLV_TAG_ITEM,
+ COMPREHENSIONTLV_TAG_ITEM_ID,
+ COMPREHENSIONTLV_TAG_NEXT_ACTION_IND,
+ COMPREHENSIONTLV_TAG_ICON_ID,
+ COMPREHENSIONTLV_TAG_ICON_ID_LIST
+ ]);
+
+ // Alpha identifier is optional.
+ let ctlv = selectedCtlvs.retrieve(COMPREHENSIONTLV_TAG_ALPHA_ID);
+ if (ctlv) {
+ menu.title = ctlv.value.identifier;
+ }
+
+ // Item data object for item 1 is mandatory.
+ let menuCtlvs = selectedCtlvs[COMPREHENSIONTLV_TAG_ITEM];
+ if (!menuCtlvs) {
+ this.context.RIL.sendStkTerminalResponse({
+ command: cmdDetails,
+ resultCode: STK_RESULT_REQUIRED_VALUES_MISSING});
+ throw new Error("Stk Menu: Required value missing : items");
+ }
+ menu.items = menuCtlvs.map(aCtlv => aCtlv.value);
+
+ // Item identifier is optional.
+ ctlv = selectedCtlvs.retrieve(COMPREHENSIONTLV_TAG_ITEM_ID);
+ if (ctlv) {
+ menu.defaultItem = ctlv.value.identifier - 1;
+ }
+
+ // Items next action indicator is optional.
+ ctlv = selectedCtlvs.retrieve(COMPREHENSIONTLV_TAG_NEXT_ACTION_IND);
+ if (ctlv) {
+ menu.nextActionList = ctlv.value;
+ }
+
+ // Icon identifier is optional.
+ let iconIdCtlvs = null;
+ let menuIconCtlv = selectedCtlvs.retrieve(COMPREHENSIONTLV_TAG_ICON_ID);
+ if (menuIconCtlv) {
+ iconIdCtlvs = [menuIconCtlv];
+ }
+
+ // Item icon identifier list is optional.
+ ctlv = selectedCtlvs.retrieve(COMPREHENSIONTLV_TAG_ICON_ID_LIST);
+ if (ctlv) {
+ if (!iconIdCtlvs) {
+ iconIdCtlvs = [];
+ };
+ let iconIdList = ctlv.value;
+ iconIdCtlvs = iconIdCtlvs.concat(iconIdList.identifiers.map((aId) => {
+ return {
+ value: { qualifier: iconIdList.qualifier, identifier: aId }
+ };
+ }));
+ }
+
+ this.loadIcons(iconIdCtlvs, (aIcons) => {
+ if (aIcons) {
+ if (menuIconCtlv) {
+ menu.iconSelfExplanatory =
+ (iconIdCtlvs.shift().value.qualifier == 0) ? true: false;
+ menu.icons = aIcons.shift();
+ }
+
+ for (let i = 0; i < aIcons.length; i++) {
+ menu.items[i].icons = aIcons[i];
+ menu.items[i].iconSelfExplanatory =
+ (iconIdCtlvs[i].value.qualifier == 0) ? true: false;
+ }
+ }
+
+ onComplete(menu);
+ });
+ },
+
+ /**
+ * Construct a param for Select Item.
+ *
+ * @param cmdDetails
+ * The value object of CommandDetails TLV.
+ * @param ctlvs
+ * The all TLVs in this proactive command.
+ * @param onComplete
+ * Callback to be called when complete.
+ */
+ processSelectItem: function(cmdDetails, ctlvs, onComplete) {
+ this.processSetupMenu(cmdDetails, ctlvs, (menu) => {
+ // The 1st bit and 2nd bit determines the presentation type.
+ menu.presentationType = cmdDetails.commandQualifier & 0x03;
+ onComplete(menu);
+ });
+ },
+
+ /**
+ * Construct a param for Display Text.
+ *
+ * @param cmdDetails
+ * The value object of CommandDetails TLV.
+ * @param ctlvs
+ * The all TLVs in this proactive command.
+ * @param onComplete
+ * Callback to be called when complete.
+ */
+ processDisplayText: function(cmdDetails, ctlvs, onComplete) {
+ let StkProactiveCmdHelper = this.context.StkProactiveCmdHelper;
+ let textMsg = {
+ isHighPriority: !!(cmdDetails.commandQualifier & 0x01),
+ userClear: !!(cmdDetails.commandQualifier & 0x80)
+ };
+
+ let selectedCtlvs = StkProactiveCmdHelper.searchForSelectedTags(ctlvs, [
+ COMPREHENSIONTLV_TAG_TEXT_STRING,
+ COMPREHENSIONTLV_TAG_IMMEDIATE_RESPONSE,
+ COMPREHENSIONTLV_TAG_DURATION,
+ COMPREHENSIONTLV_TAG_ICON_ID
+ ]);
+
+ // Text string is mandatory.
+ let ctlv = selectedCtlvs.retrieve(COMPREHENSIONTLV_TAG_TEXT_STRING);
+ if (!ctlv) {
+ this.context.RIL.sendStkTerminalResponse({
+ command: cmdDetails,
+ resultCode: STK_RESULT_REQUIRED_VALUES_MISSING});
+ throw new Error("Stk Display Text: Required value missing : Text String");
+ }
+ textMsg.text = ctlv.value.textString;
+
+ // Immediate response is optional.
+ textMsg.responseNeeded =
+ !!(selectedCtlvs.retrieve(COMPREHENSIONTLV_TAG_IMMEDIATE_RESPONSE));
+
+ // Duration is optional.
+ ctlv = selectedCtlvs.retrieve(COMPREHENSIONTLV_TAG_DURATION);
+ if (ctlv) {
+ textMsg.duration = ctlv.value;
+ }
+
+ // Icon identifier is optional.
+ this.appendIconIfNecessary(selectedCtlvs[COMPREHENSIONTLV_TAG_ICON_ID] || null,
+ textMsg,
+ onComplete);
+ },
+
+ /**
+ * Construct a param for Setup Idle Mode Text.
+ *
+ * @param cmdDetails
+ * The value object of CommandDetails TLV.
+ * @param ctlvs
+ * The all TLVs in this proactive command.
+ * @param onComplete
+ * Callback to be called when complete.
+ */
+ processSetUpIdleModeText: function(cmdDetails, ctlvs, onComplete) {
+ let StkProactiveCmdHelper = this.context.StkProactiveCmdHelper;
+ let textMsg = {};
+
+ let selectedCtlvs = StkProactiveCmdHelper.searchForSelectedTags(ctlvs, [
+ COMPREHENSIONTLV_TAG_TEXT_STRING,
+ COMPREHENSIONTLV_TAG_ICON_ID
+ ]);
+
+ // Text string is mandatory.
+ let ctlv = selectedCtlvs.retrieve(COMPREHENSIONTLV_TAG_TEXT_STRING);
+ if (!ctlv) {
+ this.context.RIL.sendStkTerminalResponse({
+ command: cmdDetails,
+ resultCode: STK_RESULT_REQUIRED_VALUES_MISSING});
+ throw new Error("Stk Set Up Idle Text: Required value missing : Text String");
+ }
+ textMsg.text = ctlv.value.textString;
+
+ // Icon identifier is optional.
+ this.appendIconIfNecessary(selectedCtlvs[COMPREHENSIONTLV_TAG_ICON_ID] || null,
+ textMsg,
+ onComplete);
+ },
+
+ /**
+ * Construct a param for Get Inkey.
+ *
+ * @param cmdDetails
+ * The value object of CommandDetails TLV.
+ * @param ctlvs
+ * The all TLVs in this proactive command.
+ * @param onComplete
+ * Callback to be called when complete.
+ */
+ processGetInkey: function(cmdDetails, ctlvs, onComplete) {
+ let StkProactiveCmdHelper = this.context.StkProactiveCmdHelper;
+ let input = {
+ minLength: 1,
+ maxLength: 1,
+ isAlphabet: !!(cmdDetails.commandQualifier & 0x01),
+ isUCS2: !!(cmdDetails.commandQualifier & 0x02),
+ // Character sets defined in bit 1 and bit 2 are disable and
+ // the YES/NO reponse is required.
+ isYesNoRequested: !!(cmdDetails.commandQualifier & 0x04),
+ // Help information available.
+ isHelpAvailable: !!(cmdDetails.commandQualifier & 0x80)
+ };
+
+ let selectedCtlvs = StkProactiveCmdHelper.searchForSelectedTags(ctlvs, [
+ COMPREHENSIONTLV_TAG_TEXT_STRING,
+ COMPREHENSIONTLV_TAG_DURATION,
+ COMPREHENSIONTLV_TAG_ICON_ID
+ ]);
+
+ // Text string is mandatory.
+ let ctlv = selectedCtlvs.retrieve(COMPREHENSIONTLV_TAG_TEXT_STRING);
+ if (!ctlv) {
+ this.context.RIL.sendStkTerminalResponse({
+ command: cmdDetails,
+ resultCode: STK_RESULT_REQUIRED_VALUES_MISSING});
+ throw new Error("Stk Get InKey: Required value missing : Text String");
+ }
+ input.text = ctlv.value.textString;
+
+ // Duration is optional.
+ ctlv = selectedCtlvs.retrieve(COMPREHENSIONTLV_TAG_DURATION);
+ if (ctlv) {
+ input.duration = ctlv.value;
+ }
+
+ // Icon identifier is optional.
+ this.appendIconIfNecessary(selectedCtlvs[COMPREHENSIONTLV_TAG_ICON_ID] || null,
+ input,
+ onComplete);
+ },
+
+ /**
+ * Construct a param for Get Input.
+ *
+ * @param cmdDetails
+ * The value object of CommandDetails TLV.
+ * @param ctlvs
+ * The all TLVs in this proactive command.
+ * @param onComplete
+ * Callback to be called when complete.
+ */
+ processGetInput: function(cmdDetails, ctlvs, onComplete) {
+ let StkProactiveCmdHelper = this.context.StkProactiveCmdHelper;
+ let input = {
+ isAlphabet: !!(cmdDetails.commandQualifier & 0x01),
+ isUCS2: !!(cmdDetails.commandQualifier & 0x02),
+ // User input shall not be revealed
+ hideInput: !!(cmdDetails.commandQualifier & 0x04),
+ // User input in SMS packed format
+ isPacked: !!(cmdDetails.commandQualifier & 0x08),
+ // Help information available.
+ isHelpAvailable: !!(cmdDetails.commandQualifier & 0x80)
+ };
+
+ let selectedCtlvs = StkProactiveCmdHelper.searchForSelectedTags(ctlvs, [
+ COMPREHENSIONTLV_TAG_TEXT_STRING,
+ COMPREHENSIONTLV_TAG_RESPONSE_LENGTH,
+ COMPREHENSIONTLV_TAG_DEFAULT_TEXT,
+ COMPREHENSIONTLV_TAG_ICON_ID
+ ]);
+
+ // Text string is mandatory.
+ let ctlv = selectedCtlvs.retrieve(COMPREHENSIONTLV_TAG_TEXT_STRING);
+ if (!ctlv) {
+ this.context.RIL.sendStkTerminalResponse({
+ command: cmdDetails,
+ resultCode: STK_RESULT_REQUIRED_VALUES_MISSING});
+ throw new Error("Stk Get Input: Required value missing : Text String");
+ }
+ input.text = ctlv.value.textString;
+
+ // Response length is mandatory.
+ ctlv = selectedCtlvs.retrieve(COMPREHENSIONTLV_TAG_RESPONSE_LENGTH);
+ if (!ctlv) {
+ this.context.RIL.sendStkTerminalResponse({
+ command: cmdDetails,
+ resultCode: STK_RESULT_REQUIRED_VALUES_MISSING});
+ throw new Error("Stk Get Input: Required value missing : Response Length");
+ }
+ input.minLength = ctlv.value.minLength;
+ input.maxLength = ctlv.value.maxLength;
+
+ // Default text is optional.
+ ctlv = selectedCtlvs.retrieve(COMPREHENSIONTLV_TAG_DEFAULT_TEXT);
+ if (ctlv) {
+ input.defaultText = ctlv.value.textString;
+ }
+
+ // Icon identifier is optional.
+ this.appendIconIfNecessary(selectedCtlvs[COMPREHENSIONTLV_TAG_ICON_ID] || null,
+ input,
+ onComplete);
+ },
+
+ /**
+ * Construct a param for SendSS/SMS/USSD/DTMF.
+ *
+ * @param cmdDetails
+ * The value object of CommandDetails TLV.
+ * @param ctlvs
+ * The all TLVs in this proactive command.
+ * @param onComplete
+ * Callback to be called when complete.
+ */
+ processEventNotify: function(cmdDetails, ctlvs, onComplete) {
+ let StkProactiveCmdHelper = this.context.StkProactiveCmdHelper;
+ let textMsg = {};
+
+ let selectedCtlvs = StkProactiveCmdHelper.searchForSelectedTags(ctlvs, [
+ COMPREHENSIONTLV_TAG_ALPHA_ID,
+ COMPREHENSIONTLV_TAG_ICON_ID
+ ]);
+
+ // Alpha identifier is optional.
+ let ctlv = selectedCtlvs.retrieve(COMPREHENSIONTLV_TAG_ALPHA_ID);
+ if (ctlv) {
+ textMsg.text = ctlv.value.identifier;
+ }
+
+ // According to section 6.4.10 of |ETSI TS 102 223|:
+ //
+ // - if the alpha identifier is provided by the UICC and is a null data
+ // object (i.e. length = '00' and no value part), this is an indication
+ // that the terminal should not give any information to the user on the
+ // fact that the terminal is sending a short message;
+ //
+ // - if the alpha identifier is not provided by the UICC, the terminal may
+ // give information to the user concerning what is happening.
+ //
+ // ICCPDUHelper reads alpha id as an empty string if the length is zero,
+ // hence we'll notify the caller when it's not an empty string.
+ if (textMsg.text !== "") {
+ // Icon identifier is optional.
+ this.appendIconIfNecessary(selectedCtlvs[COMPREHENSIONTLV_TAG_ICON_ID] || null,
+ textMsg,
+ onComplete);
+ }
+ },
+
+ /**
+ * Construct a param for Setup Call.
+ *
+ * @param cmdDetails
+ * The value object of CommandDetails TLV.
+ * @param ctlvs
+ * The all TLVs in this proactive command.
+ * @param onComplete
+ * Callback to be called when complete.
+ */
+ processSetupCall: function(cmdDetails, ctlvs, onComplete) {
+ let StkProactiveCmdHelper = this.context.StkProactiveCmdHelper;
+ let call = {};
+ let confirmMessage = {};
+ let callMessage = {};
+
+ let selectedCtlvs = StkProactiveCmdHelper.searchForSelectedTags(ctlvs, [
+ COMPREHENSIONTLV_TAG_ADDRESS,
+ COMPREHENSIONTLV_TAG_ALPHA_ID,
+ COMPREHENSIONTLV_TAG_ICON_ID,
+ COMPREHENSIONTLV_TAG_DURATION
+ ]);
+
+ // Address is mandatory.
+ let ctlv = selectedCtlvs.retrieve(COMPREHENSIONTLV_TAG_ADDRESS);
+ if (!ctlv) {
+ this.context.RIL.sendStkTerminalResponse({
+ command: cmdDetails,
+ resultCode: STK_RESULT_REQUIRED_VALUES_MISSING});
+ throw new Error("Stk Set Up Call: Required value missing : Address");
+ }
+ call.address = ctlv.value.number;
+
+ // Alpha identifier (user confirmation phase) is optional.
+ ctlv = selectedCtlvs.retrieve(COMPREHENSIONTLV_TAG_ALPHA_ID);
+ if (ctlv) {
+ confirmMessage.text = ctlv.value.identifier;
+ call.confirmMessage = confirmMessage;
+ }
+
+ // Alpha identifier (call set up phase) is optional.
+ ctlv = selectedCtlvs.retrieve(COMPREHENSIONTLV_TAG_ALPHA_ID);
+ if (ctlv) {
+ callMessage.text = ctlv.value.identifier;
+ call.callMessage = callMessage;
+ }
+
+ // Duration is optional.
+ ctlv = selectedCtlvs.retrieve(COMPREHENSIONTLV_TAG_DURATION);
+ if (ctlv) {
+ call.duration = ctlv.value;
+ }
+
+ // Icon identifier is optional.
+ let iconIdCtlvs = selectedCtlvs[COMPREHENSIONTLV_TAG_ICON_ID] || null;
+ this.loadIcons(iconIdCtlvs, (aIcons) => {
+ if (aIcons) {
+ confirmMessage.icons = aIcons[0];
+ confirmMessage.iconSelfExplanatory =
+ (iconIdCtlvs[0].value.qualifier == 0) ? true: false;
+ call.confirmMessage = confirmMessage;
+
+ if (aIcons.length > 1) {
+ callMessage.icons = aIcons[1];
+ callMessage.iconSelfExplanatory =
+ (iconIdCtlvs[1].value.qualifier == 0) ? true: false;
+ call.callMessage = callMessage;
+ }
+ }
+
+ onComplete(call);
+ });
+ },
+
+ /**
+ * Construct a param for Launch Browser.
+ *
+ * @param cmdDetails
+ * The value object of CommandDetails TLV.
+ * @param ctlvs
+ * The all TLVs in this proactive command.
+ * @param onComplete
+ * Callback to be called when complete.
+ */
+ processLaunchBrowser: function(cmdDetails, ctlvs, onComplete) {
+ let StkProactiveCmdHelper = this.context.StkProactiveCmdHelper;
+ let browser = {
+ mode: cmdDetails.commandQualifier & 0x03
+ };
+ let confirmMessage = {};
+
+ let selectedCtlvs = StkProactiveCmdHelper.searchForSelectedTags(ctlvs, [
+ COMPREHENSIONTLV_TAG_URL,
+ COMPREHENSIONTLV_TAG_ALPHA_ID,
+ COMPREHENSIONTLV_TAG_ICON_ID
+ ]);
+
+ // URL is mandatory.
+ let ctlv = selectedCtlvs.retrieve(COMPREHENSIONTLV_TAG_URL);
+ if (!ctlv) {
+ this.context.RIL.sendStkTerminalResponse({
+ command: cmdDetails,
+ resultCode: STK_RESULT_REQUIRED_VALUES_MISSING});
+ throw new Error("Stk Launch Browser: Required value missing : URL");
+ }
+ browser.url = ctlv.value.url;
+
+ // Alpha identifier is optional.
+ ctlv = selectedCtlvs.retrieve(COMPREHENSIONTLV_TAG_ALPHA_ID);
+ if (ctlv) {
+ confirmMessage.text = ctlv.value.identifier;
+ browser.confirmMessage = confirmMessage;
+ }
+
+ // Icon identifier is optional.
+ let iconIdCtlvs = selectedCtlvs[COMPREHENSIONTLV_TAG_ICON_ID] || null;
+ this.loadIcons(iconIdCtlvs, (aIcons) => {
+ if (aIcons) {
+ confirmMessage.icons = aIcons[0];
+ confirmMessage.iconSelfExplanatory =
+ (iconIdCtlvs[0].value.qualifier == 0) ? true: false;
+ browser.confirmMessage = confirmMessage;
+ }
+
+ onComplete(browser);
+ });
+ },
+
+ /**
+ * Construct a param for Play Tone.
+ *
+ * @param cmdDetails
+ * The value object of CommandDetails TLV.
+ * @param ctlvs
+ * The all TLVs in this proactive command.
+ * @param onComplete
+ * Callback to be called when complete.
+ */
+ processPlayTone: function(cmdDetails, ctlvs, onComplete) {
+ let StkProactiveCmdHelper = this.context.StkProactiveCmdHelper;
+ let playTone = {
+ // The vibrate is only defined in TS 102.223.
+ isVibrate: !!(cmdDetails.commandQualifier & 0x01)
+ };
+
+ let selectedCtlvs = StkProactiveCmdHelper.searchForSelectedTags(ctlvs, [
+ COMPREHENSIONTLV_TAG_ALPHA_ID,
+ COMPREHENSIONTLV_TAG_TONE,
+ COMPREHENSIONTLV_TAG_DURATION,
+ COMPREHENSIONTLV_TAG_ICON_ID
+ ]);
+
+ // Alpha identifier is optional.
+ let ctlv = selectedCtlvs.retrieve(COMPREHENSIONTLV_TAG_ALPHA_ID);
+ if (ctlv) {
+ playTone.text = ctlv.value.identifier;
+ }
+
+ // Tone is optional.
+ ctlv = selectedCtlvs.retrieve(COMPREHENSIONTLV_TAG_TONE);
+ if (ctlv) {
+ playTone.tone = ctlv.value.tone;
+ }
+
+ // Duration is optional.
+ ctlv = selectedCtlvs.retrieve(COMPREHENSIONTLV_TAG_DURATION);
+ if (ctlv) {
+ playTone.duration = ctlv.value;
+ }
+
+ // Icon identifier is optional.
+ this.appendIconIfNecessary(selectedCtlvs[COMPREHENSIONTLV_TAG_ICON_ID] || null,
+ playTone,
+ onComplete);
+ },
+
+ /**
+ * Construct a param for Provide Local Information.
+ *
+ * @param cmdDetails
+ * The value object of CommandDetails TLV.
+ * @param ctlvs
+ * The all TLVs in this proactive command.
+ * @param onComplete
+ * Callback to be called when complete.
+ */
+ processProvideLocalInfo: function(cmdDetails, ctlvs, onComplete) {
+ let provideLocalInfo = {
+ localInfoType: cmdDetails.commandQualifier
+ };
+
+ onComplete(provideLocalInfo);
+ },
+
+ /**
+ * Construct a param for Timer Management.
+ *
+ * @param cmdDetails
+ * The value object of CommandDetails TLV.
+ * @param ctlvs
+ * The all TLVs in this proactive command.
+ * @param onComplete
+ * Callback to be called when complete.
+ */
+ processTimerManagement: function(cmdDetails, ctlvs, onComplete) {
+ let StkProactiveCmdHelper = this.context.StkProactiveCmdHelper;
+ let timer = {
+ timerAction: cmdDetails.commandQualifier
+ };
+
+ let selectedCtlvs = StkProactiveCmdHelper.searchForSelectedTags(ctlvs, [
+ COMPREHENSIONTLV_TAG_TIMER_IDENTIFIER,
+ COMPREHENSIONTLV_TAG_TIMER_VALUE
+ ]);
+
+ // Timer identifier is mandatory.
+ let ctlv = selectedCtlvs.retrieve(COMPREHENSIONTLV_TAG_TIMER_IDENTIFIER);
+ if (!ctlv) {
+ this.context.RIL.sendStkTerminalResponse({
+ command: cmdDetails,
+ resultCode: STK_RESULT_REQUIRED_VALUES_MISSING});
+ throw new Error("Stk Timer Management: Required value missing : Timer Identifier");
+ }
+ timer.timerId = ctlv.value.timerId;
+
+ // Timer value is conditional.
+ ctlv = selectedCtlvs.retrieve(COMPREHENSIONTLV_TAG_TIMER_VALUE);
+ if (ctlv) {
+ timer.timerValue = ctlv.value.timerValue;
+ }
+
+ onComplete(timer);
+ },
+
+ /**
+ * Construct a param for BIP commands.
+ *
+ * @param cmdDetails
+ * The value object of CommandDetails TLV.
+ * @param ctlvs
+ * The all TLVs in this proactive command.
+ * @param onComplete
+ * Callback to be called when complete.
+ */
+ processBipMessage: function(cmdDetails, ctlvs, onComplete) {
+ let StkProactiveCmdHelper = this.context.StkProactiveCmdHelper;
+ let bipMsg = {};
+
+ let selectedCtlvs = StkProactiveCmdHelper.searchForSelectedTags(ctlvs, [
+ COMPREHENSIONTLV_TAG_ALPHA_ID,
+ COMPREHENSIONTLV_TAG_ICON_ID
+ ]);
+
+ // Alpha identifier is optional.
+ let ctlv = selectedCtlvs.retrieve(COMPREHENSIONTLV_TAG_ALPHA_ID);
+ if (ctlv) {
+ bipMsg.text = ctlv.value.identifier;
+ }
+
+ // Icon identifier is optional.
+ this.appendIconIfNecessary(selectedCtlvs[COMPREHENSIONTLV_TAG_ICON_ID] || null,
+ bipMsg,
+ onComplete);
+ }
+};
+StkCommandParamsFactoryObject.prototype[STK_CMD_REFRESH] = function STK_CMD_REFRESH(cmdDetails, ctlvs, onComplete) {
+ return this.processRefresh(cmdDetails, ctlvs, onComplete);
+};
+StkCommandParamsFactoryObject.prototype[STK_CMD_POLL_INTERVAL] = function STK_CMD_POLL_INTERVAL(cmdDetails, ctlvs, onComplete) {
+ return this.processPollInterval(cmdDetails, ctlvs, onComplete);
+};
+StkCommandParamsFactoryObject.prototype[STK_CMD_POLL_OFF] = function STK_CMD_POLL_OFF(cmdDetails, ctlvs, onComplete) {
+ return this.processPollOff(cmdDetails, ctlvs, onComplete);
+};
+StkCommandParamsFactoryObject.prototype[STK_CMD_PROVIDE_LOCAL_INFO] = function STK_CMD_PROVIDE_LOCAL_INFO(cmdDetails, ctlvs, onComplete) {
+ return this.processProvideLocalInfo(cmdDetails, ctlvs, onComplete);
+};
+StkCommandParamsFactoryObject.prototype[STK_CMD_SET_UP_EVENT_LIST] = function STK_CMD_SET_UP_EVENT_LIST(cmdDetails, ctlvs, onComplete) {
+ return this.processSetUpEventList(cmdDetails, ctlvs, onComplete);
+};
+StkCommandParamsFactoryObject.prototype[STK_CMD_SET_UP_MENU] = function STK_CMD_SET_UP_MENU(cmdDetails, ctlvs, onComplete) {
+ return this.processSetupMenu(cmdDetails, ctlvs, onComplete);
+};
+StkCommandParamsFactoryObject.prototype[STK_CMD_SELECT_ITEM] = function STK_CMD_SELECT_ITEM(cmdDetails, ctlvs, onComplete) {
+ return this.processSelectItem(cmdDetails, ctlvs, onComplete);
+};
+StkCommandParamsFactoryObject.prototype[STK_CMD_DISPLAY_TEXT] = function STK_CMD_DISPLAY_TEXT(cmdDetails, ctlvs, onComplete) {
+ return this.processDisplayText(cmdDetails, ctlvs, onComplete);
+};
+StkCommandParamsFactoryObject.prototype[STK_CMD_SET_UP_IDLE_MODE_TEXT] = function STK_CMD_SET_UP_IDLE_MODE_TEXT(cmdDetails, ctlvs, onComplete) {
+ return this.processSetUpIdleModeText(cmdDetails, ctlvs, onComplete);
+};
+StkCommandParamsFactoryObject.prototype[STK_CMD_GET_INKEY] = function STK_CMD_GET_INKEY(cmdDetails, ctlvs, onComplete) {
+ return this.processGetInkey(cmdDetails, ctlvs, onComplete);
+};
+StkCommandParamsFactoryObject.prototype[STK_CMD_GET_INPUT] = function STK_CMD_GET_INPUT(cmdDetails, ctlvs, onComplete) {
+ return this.processGetInput(cmdDetails, ctlvs, onComplete);
+};
+StkCommandParamsFactoryObject.prototype[STK_CMD_SEND_SS] = function STK_CMD_SEND_SS(cmdDetails, ctlvs, onComplete) {
+ return this.processEventNotify(cmdDetails, ctlvs, onComplete);
+};
+StkCommandParamsFactoryObject.prototype[STK_CMD_SEND_USSD] = function STK_CMD_SEND_USSD(cmdDetails, ctlvs, onComplete) {
+ return this.processEventNotify(cmdDetails, ctlvs, onComplete);
+};
+StkCommandParamsFactoryObject.prototype[STK_CMD_SEND_SMS] = function STK_CMD_SEND_SMS(cmdDetails, ctlvs, onComplete) {
+ return this.processEventNotify(cmdDetails, ctlvs, onComplete);
+};
+StkCommandParamsFactoryObject.prototype[STK_CMD_SEND_DTMF] = function STK_CMD_SEND_DTMF(cmdDetails, ctlvs, onComplete) {
+ return this.processEventNotify(cmdDetails, ctlvs, onComplete);
+};
+StkCommandParamsFactoryObject.prototype[STK_CMD_SET_UP_CALL] = function STK_CMD_SET_UP_CALL(cmdDetails, ctlvs, onComplete) {
+ return this.processSetupCall(cmdDetails, ctlvs, onComplete);
+};
+StkCommandParamsFactoryObject.prototype[STK_CMD_LAUNCH_BROWSER] = function STK_CMD_LAUNCH_BROWSER(cmdDetails, ctlvs, onComplete) {
+ return this.processLaunchBrowser(cmdDetails, ctlvs, onComplete);
+};
+StkCommandParamsFactoryObject.prototype[STK_CMD_PLAY_TONE] = function STK_CMD_PLAY_TONE(cmdDetails, ctlvs, onComplete) {
+ return this.processPlayTone(cmdDetails, ctlvs, onComplete);
+};
+StkCommandParamsFactoryObject.prototype[STK_CMD_TIMER_MANAGEMENT] = function STK_CMD_TIMER_MANAGEMENT(cmdDetails, ctlvs, onComplete) {
+ return this.processTimerManagement(cmdDetails, ctlvs, onComplete);
+};
+StkCommandParamsFactoryObject.prototype[STK_CMD_OPEN_CHANNEL] = function STK_CMD_OPEN_CHANNEL(cmdDetails, ctlvs, onComplete) {
+ return this.processBipMessage(cmdDetails, ctlvs, onComplete);
+};
+StkCommandParamsFactoryObject.prototype[STK_CMD_CLOSE_CHANNEL] = function STK_CMD_CLOSE_CHANNEL(cmdDetails, ctlvs, onComplete) {
+ return this.processBipMessage(cmdDetails, ctlvs, onComplete);
+};
+StkCommandParamsFactoryObject.prototype[STK_CMD_RECEIVE_DATA] = function STK_CMD_RECEIVE_DATA(cmdDetails, ctlvs, onComplete) {
+ return this.processBipMessage(cmdDetails, ctlvs, onComplete);
+};
+StkCommandParamsFactoryObject.prototype[STK_CMD_SEND_DATA] = function STK_CMD_SEND_DATA(cmdDetails, ctlvs, onComplete) {
+ return this.processBipMessage(cmdDetails, ctlvs, onComplete);
+};
+
+function StkProactiveCmdHelperObject(aContext) {
+ this.context = aContext;
+}
+StkProactiveCmdHelperObject.prototype = {
+ context: null,
+
+ retrieve: function(tag, length) {
+ let method = this[tag];
+ if (typeof method != "function") {
+ if (DEBUG) {
+ this.context.debug("Unknown comprehension tag " + tag.toString(16));
+ }
+ let Buf = this.context.Buf;
+ Buf.seekIncoming(length * Buf.PDU_HEX_OCTET_SIZE);
+ return null;
+ }
+ return method.call(this, length);
+ },
+
+ /**
+ * Command Details.
+ *
+ * | Byte | Description | Length |
+ * | 1 | Command details Tag | 1 |
+ * | 2 | Length = 03 | 1 |
+ * | 3 | Command number | 1 |
+ * | 4 | Type of Command | 1 |
+ * | 5 | Command Qualifier | 1 |
+ */
+ retrieveCommandDetails: function(length) {
+ let GsmPDUHelper = this.context.GsmPDUHelper;
+ let cmdDetails = {
+ commandNumber: GsmPDUHelper.readHexOctet(),
+ typeOfCommand: GsmPDUHelper.readHexOctet(),
+ commandQualifier: GsmPDUHelper.readHexOctet()
+ };
+ return cmdDetails;
+ },
+
+ /**
+ * Device Identities.
+ *
+ * | Byte | Description | Length |
+ * | 1 | Device Identity Tag | 1 |
+ * | 2 | Length = 02 | 1 |
+ * | 3 | Source device Identity | 1 |
+ * | 4 | Destination device Id | 1 |
+ */
+ retrieveDeviceId: function(length) {
+ let GsmPDUHelper = this.context.GsmPDUHelper;
+ let deviceId = {
+ sourceId: GsmPDUHelper.readHexOctet(),
+ destinationId: GsmPDUHelper.readHexOctet()
+ };
+ return deviceId;
+ },
+
+ /**
+ * Alpha Identifier.
+ *
+ * | Byte | Description | Length |
+ * | 1 | Alpha Identifier Tag | 1 |
+ * | 2 ~ (Y-1)+2 | Length (X) | Y |
+ * | (Y-1)+3 ~ | Alpha identfier | X |
+ * | (Y-1)+X+2 | | |
+ */
+ retrieveAlphaId: function(length) {
+ let alphaId = {
+ identifier: this.context.ICCPDUHelper.readAlphaIdentifier(length)
+ };
+ return alphaId;
+ },
+
+ /**
+ * Duration.
+ *
+ * | Byte | Description | Length |
+ * | 1 | Response Length Tag | 1 |
+ * | 2 | Lenth = 02 | 1 |
+ * | 3 | Time unit | 1 |
+ * | 4 | Time interval | 1 |
+ */
+ retrieveDuration: function(length) {
+ let GsmPDUHelper = this.context.GsmPDUHelper;
+ let duration = {
+ timeUnit: GsmPDUHelper.readHexOctet(),
+ timeInterval: GsmPDUHelper.readHexOctet(),
+ };
+ return duration;
+ },
+
+ /**
+ * Address.
+ *
+ * | Byte | Description | Length |
+ * | 1 | Alpha Identifier Tag | 1 |
+ * | 2 ~ (Y-1)+2 | Length (X) | Y |
+ * | (Y-1)+3 | TON and NPI | 1 |
+ * | (Y-1)+4 ~ | Dialling number | X |
+ * | (Y-1)+X+2 | | |
+ */
+ retrieveAddress: function(length) {
+ let address = {
+ number : this.context.ICCPDUHelper.readDiallingNumber(length)
+ };
+ return address;
+ },
+
+ /**
+ * Text String.
+ *
+ * | Byte | Description | Length |
+ * | 1 | Text String Tag | 1 |
+ * | 2 ~ (Y-1)+2 | Length (X) | Y |
+ * | (Y-1)+3 | Data coding scheme | 1 |
+ * | (Y-1)+4~ | Text String | X |
+ * | (Y-1)+X+2 | | |
+ */
+ retrieveTextString: function(length) {
+ if (!length) {
+ // null string.
+ return {textString: null};
+ }
+
+ let GsmPDUHelper = this.context.GsmPDUHelper;
+ let text = {
+ codingScheme: GsmPDUHelper.readHexOctet()
+ };
+
+ length--; // -1 for the codingScheme.
+ switch (text.codingScheme & 0x0c) {
+ case STK_TEXT_CODING_GSM_7BIT_PACKED:
+ text.textString =
+ GsmPDUHelper.readSeptetsToString(Math.floor(length * 8 / 7), 0, 0, 0);
+ break;
+ case STK_TEXT_CODING_GSM_8BIT:
+ text.textString =
+ this.context.ICCPDUHelper.read8BitUnpackedToString(length);
+ break;
+ case STK_TEXT_CODING_UCS2:
+ text.textString = GsmPDUHelper.readUCS2String(length);
+ break;
+ }
+ return text;
+ },
+
+ /**
+ * Tone.
+ *
+ * | Byte | Description | Length |
+ * | 1 | Tone Tag | 1 |
+ * | 2 | Lenth = 01 | 1 |
+ * | 3 | Tone | 1 |
+ */
+ retrieveTone: function(length) {
+ let tone = {
+ tone: this.context.GsmPDUHelper.readHexOctet(),
+ };
+ return tone;
+ },
+
+ /**
+ * Item.
+ *
+ * | Byte | Description | Length |
+ * | 1 | Item Tag | 1 |
+ * | 2 ~ (Y-1)+2 | Length (X) | Y |
+ * | (Y-1)+3 | Identifier of item | 1 |
+ * | (Y-1)+4 ~ | Text string of item | X |
+ * | (Y-1)+X+2 | | |
+ */
+ retrieveItem: function(length) {
+ // TS 102.223 ,clause 6.6.7 SET-UP MENU
+ // If the "Item data object for item 1" is a null data object
+ // (i.e. length = '00' and no value part), this is an indication to the ME
+ // to remove the existing menu from the menu system in the ME.
+ if (!length) {
+ return null;
+ }
+ let item = {
+ identifier: this.context.GsmPDUHelper.readHexOctet(),
+ text: this.context.ICCPDUHelper.readAlphaIdentifier(length - 1)
+ };
+ return item;
+ },
+
+ /**
+ * Item Identifier.
+ *
+ * | Byte | Description | Length |
+ * | 1 | Item Identifier Tag | 1 |
+ * | 2 | Lenth = 01 | 1 |
+ * | 3 | Identifier of Item chosen | 1 |
+ */
+ retrieveItemId: function(length) {
+ let itemId = {
+ identifier: this.context.GsmPDUHelper.readHexOctet()
+ };
+ return itemId;
+ },
+
+ /**
+ * Response Length.
+ *
+ * | Byte | Description | Length |
+ * | 1 | Response Length Tag | 1 |
+ * | 2 | Lenth = 02 | 1 |
+ * | 3 | Minimum length of response | 1 |
+ * | 4 | Maximum length of response | 1 |
+ */
+ retrieveResponseLength: function(length) {
+ let GsmPDUHelper = this.context.GsmPDUHelper;
+ let rspLength = {
+ minLength : GsmPDUHelper.readHexOctet(),
+ maxLength : GsmPDUHelper.readHexOctet()
+ };
+ return rspLength;
+ },
+
+ /**
+ * File List.
+ *
+ * | Byte | Description | Length |
+ * | 1 | File List Tag | 1 |
+ * | 2 ~ (Y-1)+2 | Length (X) | Y |
+ * | (Y-1)+3 | Number of files | 1 |
+ * | (Y-1)+4 ~ | Files | X |
+ * | (Y-1)+X+2 | | |
+ */
+ retrieveFileList: function(length) {
+ let num = this.context.GsmPDUHelper.readHexOctet();
+ let fileList = "";
+ length--; // -1 for the num octet.
+ for (let i = 0; i < 2 * length; i++) {
+ // Didn't use readHexOctet here,
+ // otherwise 0x00 will be "0", not "00"
+ fileList += String.fromCharCode(this.context.Buf.readUint16());
+ }
+ return {
+ fileList: fileList
+ };
+ },
+
+ /**
+ * Default Text.
+ *
+ * Same as Text String.
+ */
+ retrieveDefaultText: function(length) {
+ return this.retrieveTextString(length);
+ },
+
+ /**
+ * Event List.
+ */
+ retrieveEventList: function(length) {
+ if (!length) {
+ // null means an indication to ME to remove the existing list of events
+ // in ME.
+ return null;
+ }
+
+ let GsmPDUHelper = this.context.GsmPDUHelper;
+ let eventList = [];
+ for (let i = 0; i < length; i++) {
+ eventList.push(GsmPDUHelper.readHexOctet());
+ }
+ return {
+ eventList: eventList
+ };
+ },
+
+ /**
+ * Icon Id.
+ *
+ * | Byte | Description | Length |
+ * | 1 | Icon Identifier Tag | 1 |
+ * | 2 | Length = 02 | 1 |
+ * | 3 | Icon qualifier | 1 |
+ * | 4 | Icon identifier | 1 |
+ */
+ retrieveIconId: function(length) {
+ if (!length) {
+ return null;
+ }
+
+ let iconId = {
+ qualifier: this.context.GsmPDUHelper.readHexOctet(),
+ identifier: this.context.GsmPDUHelper.readHexOctet()
+ };
+ return iconId;
+ },
+
+ /**
+ * Icon Id List.
+ *
+ * | Byte | Description | Length |
+ * | 1 | Icon Identifier Tag | 1 |
+ * | 2 | Length = X | 1 |
+ * | 3 | Icon qualifier | 1 |
+ * | 4~ | Icon identifier | X-1 |
+ * | 4+X-2 | | |
+ */
+ retrieveIconIdList: function(length) {
+ if (!length) {
+ return null;
+ }
+
+ let iconIdList = {
+ qualifier: this.context.GsmPDUHelper.readHexOctet(),
+ identifiers: []
+ };
+ for (let i = 0; i < length - 1; i++) {
+ iconIdList.identifiers.push(this.context.GsmPDUHelper.readHexOctet());
+ }
+ return iconIdList;
+ },
+
+ /**
+ * Timer Identifier.
+ *
+ * | Byte | Description | Length |
+ * | 1 | Timer Identifier Tag | 1 |
+ * | 2 | Length = 01 | 1 |
+ * | 3 | Timer Identifier | 1 |
+ */
+ retrieveTimerId: function(length) {
+ let id = {
+ timerId: this.context.GsmPDUHelper.readHexOctet()
+ };
+ return id;
+ },
+
+ /**
+ * Timer Value.
+ *
+ * | Byte | Description | Length |
+ * | 1 | Timer Value Tag | 1 |
+ * | 2 | Length = 03 | 1 |
+ * | 3 | Hour | 1 |
+ * | 4 | Minute | 1 |
+ * | 5 | Second | 1 |
+ */
+ retrieveTimerValue: function(length) {
+ let GsmPDUHelper = this.context.GsmPDUHelper;
+ let value = {
+ timerValue: (GsmPDUHelper.readSwappedNibbleBcdNum(1) * 60 * 60) +
+ (GsmPDUHelper.readSwappedNibbleBcdNum(1) * 60) +
+ (GsmPDUHelper.readSwappedNibbleBcdNum(1))
+ };
+ return value;
+ },
+
+ /**
+ * Immediate Response.
+ *
+ * | Byte | Description | Length |
+ * | 1 | Immediate Response Tag | 1 |
+ * | 2 | Length = 00 | 1 |
+ */
+ retrieveImmediaResponse: function(length) {
+ return {};
+ },
+
+ /**
+ * URL
+ *
+ * | Byte | Description | Length |
+ * | 1 | URL Tag | 1 |
+ * | 2 ~ (Y+1) | Length(X) | Y |
+ * | (Y+2) ~ | URL | X |
+ * | (Y+1+X) | | |
+ */
+ retrieveUrl: function(length) {
+ let GsmPDUHelper = this.context.GsmPDUHelper;
+ let s = "";
+ for (let i = 0; i < length; i++) {
+ s += String.fromCharCode(GsmPDUHelper.readHexOctet());
+ }
+ return {url: s};
+ },
+
+ /**
+ * Next Action Indicator List.
+ *
+ * | Byte | Description | Length |
+ * | 1 | Next Action tag | 1 |
+ * | 1 | Length(X) | 1 |
+ * | 3~ | Next Action List | X |
+ * | 3+X-1 | | |
+ */
+ retrieveNextActionList: function(length) {
+ let GsmPDUHelper = this.context.GsmPDUHelper;
+ let nextActionList = [];
+ for (let i = 0; i < length; i++) {
+ nextActionList.push(GsmPDUHelper.readHexOctet());
+ }
+ return nextActionList;
+ },
+
+ searchForTag: function(tag, ctlvs) {
+ for (let ctlv of ctlvs) {
+ if ((ctlv.tag & ~COMPREHENSIONTLV_FLAG_CR) == tag) {
+ return ctlv;
+ }
+ }
+ return null;
+ },
+
+ searchForSelectedTags: function(ctlvs, tags) {
+ let ret = {
+ // Handy utility to de-queue the 1st ctlv of the specified tag.
+ retrieve: function(aTag) {
+ return (this[aTag]) ? this[aTag].shift() : null;
+ }
+ };
+
+ ctlvs.forEach((aCtlv) => {
+ tags.forEach((aTag) => {
+ if ((aCtlv.tag & ~COMPREHENSIONTLV_FLAG_CR) == aTag) {
+ if (!ret[aTag]) {
+ ret[aTag] = [];
+ }
+ ret[aTag].push(aCtlv);
+ }
+ });
+ });
+
+ return ret;
+ },
+};
+StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_COMMAND_DETAILS] = function COMPREHENSIONTLV_TAG_COMMAND_DETAILS(length) {
+ return this.retrieveCommandDetails(length);
+};
+StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_DEVICE_ID] = function COMPREHENSIONTLV_TAG_DEVICE_ID(length) {
+ return this.retrieveDeviceId(length);
+};
+StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_ALPHA_ID] = function COMPREHENSIONTLV_TAG_ALPHA_ID(length) {
+ return this.retrieveAlphaId(length);
+};
+StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_DURATION] = function COMPREHENSIONTLV_TAG_DURATION(length) {
+ return this.retrieveDuration(length);
+};
+StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_ADDRESS] = function COMPREHENSIONTLV_TAG_ADDRESS(length) {
+ return this.retrieveAddress(length);
+};
+StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_TEXT_STRING] = function COMPREHENSIONTLV_TAG_TEXT_STRING(length) {
+ return this.retrieveTextString(length);
+};
+StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_TONE] = function COMPREHENSIONTLV_TAG_TONE(length) {
+ return this.retrieveTone(length);
+};
+StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_ITEM] = function COMPREHENSIONTLV_TAG_ITEM(length) {
+ return this.retrieveItem(length);
+};
+StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_ITEM_ID] = function COMPREHENSIONTLV_TAG_ITEM_ID(length) {
+ return this.retrieveItemId(length);
+};
+StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_RESPONSE_LENGTH] = function COMPREHENSIONTLV_TAG_RESPONSE_LENGTH(length) {
+ return this.retrieveResponseLength(length);
+};
+StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_FILE_LIST] = function COMPREHENSIONTLV_TAG_FILE_LIST(length) {
+ return this.retrieveFileList(length);
+};
+StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_DEFAULT_TEXT] = function COMPREHENSIONTLV_TAG_DEFAULT_TEXT(length) {
+ return this.retrieveDefaultText(length);
+};
+StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_EVENT_LIST] = function COMPREHENSIONTLV_TAG_EVENT_LIST(length) {
+ return this.retrieveEventList(length);
+};
+StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_ICON_ID] = function COMPREHENSIONTLV_TAG_ICON_ID(length) {
+ return this.retrieveIconId(length);
+};
+StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_ICON_ID_LIST] = function COMPREHENSIONTLV_TAG_ICON_ID_LIST(length) {
+ return this.retrieveIconIdList(length);
+};
+StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_TIMER_IDENTIFIER] = function COMPREHENSIONTLV_TAG_TIMER_IDENTIFIER(length) {
+ return this.retrieveTimerId(length);
+};
+StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_TIMER_VALUE] = function COMPREHENSIONTLV_TAG_TIMER_VALUE(length) {
+ return this.retrieveTimerValue(length);
+};
+StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_IMMEDIATE_RESPONSE] = function COMPREHENSIONTLV_TAG_IMMEDIATE_RESPONSE(length) {
+ return this.retrieveImmediaResponse(length);
+};
+StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_URL] = function COMPREHENSIONTLV_TAG_URL(length) {
+ return this.retrieveUrl(length);
+};
+StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_NEXT_ACTION_IND] = function COMPREHENSIONTLV_TAG_NEXT_ACTION_IND(length) {
+ return this.retrieveNextActionList(length);
+};
+
+function ComprehensionTlvHelperObject(aContext) {
+ this.context = aContext;
+}
+ComprehensionTlvHelperObject.prototype = {
+ context: null,
+
+ /**
+ * Decode raw data to a Comprehension-TLV.
+ */
+ decode: function() {
+ let GsmPDUHelper = this.context.GsmPDUHelper;
+
+ let hlen = 0; // For header(tag field + length field) length.
+ let temp = GsmPDUHelper.readHexOctet();
+ hlen++;
+
+ // TS 101.220, clause 7.1.1
+ let tag, cr;
+ switch (temp) {
+ // TS 101.220, clause 7.1.1
+ case 0x0: // Not used.
+ case 0xff: // Not used.
+ case 0x80: // Reserved for future use.
+ throw new Error("Invalid octet when parsing Comprehension TLV :" + temp);
+ case 0x7f: // Tag is three byte format.
+ // TS 101.220 clause 7.1.1.2.
+ // | Byte 1 | Byte 2 | Byte 3 |
+ // | | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | |
+ // | 0x7f |CR | Tag Value |
+ tag = (GsmPDUHelper.readHexOctet() << 8) | GsmPDUHelper.readHexOctet();
+ hlen += 2;
+ cr = (tag & 0x8000) !== 0;
+ tag &= ~0x8000;
+ break;
+ default: // Tag is single byte format.
+ tag = temp;
+ // TS 101.220 clause 7.1.1.1.
+ // | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 |
+ // |CR | Tag Value |
+ cr = (tag & 0x80) !== 0;
+ tag &= ~0x80;
+ }
+
+ // TS 101.220 clause 7.1.2, Length Encoding.
+ // Length | Byte 1 | Byte 2 | Byte 3 | Byte 4 |
+ // 0 - 127 | 00 - 7f | N/A | N/A | N/A |
+ // 128-255 | 81 | 80 - ff| N/A | N/A |
+ // 256-65535| 82 | 0100 - ffff | N/A |
+ // 65536- | 83 | 010000 - ffffff |
+ // 16777215
+ //
+ // Length errors: TS 11.14, clause 6.10.6
+
+ let length; // Data length.
+ temp = GsmPDUHelper.readHexOctet();
+ hlen++;
+ if (temp < 0x80) {
+ length = temp;
+ } else if (temp == 0x81) {
+ length = GsmPDUHelper.readHexOctet();
+ hlen++;
+ if (length < 0x80) {
+ throw new Error("Invalid length in Comprehension TLV :" + length);
+ }
+ } else if (temp == 0x82) {
+ length = (GsmPDUHelper.readHexOctet() << 8) | GsmPDUHelper.readHexOctet();
+ hlen += 2;
+ if (lenth < 0x0100) {
+ throw new Error("Invalid length in 3-byte Comprehension TLV :" + length);
+ }
+ } else if (temp == 0x83) {
+ length = (GsmPDUHelper.readHexOctet() << 16) |
+ (GsmPDUHelper.readHexOctet() << 8) |
+ GsmPDUHelper.readHexOctet();
+ hlen += 3;
+ if (length < 0x010000) {
+ throw new Error("Invalid length in 4-byte Comprehension TLV :" + length);
+ }
+ } else {
+ throw new Error("Invalid octet in Comprehension TLV :" + temp);
+ }
+
+ let ctlv = {
+ tag: tag,
+ length: length,
+ value: this.context.StkProactiveCmdHelper.retrieve(tag, length),
+ cr: cr,
+ hlen: hlen
+ };
+ return ctlv;
+ },
+
+ decodeChunks: function(length) {
+ let chunks = [];
+ let index = 0;
+ while (index < length) {
+ let tlv = this.decode();
+ chunks.push(tlv);
+ index += tlv.length;
+ index += tlv.hlen;
+ }
+ return chunks;
+ },
+
+ /**
+ * Write Location Info Comprehension TLV.
+ *
+ * @param loc location Information.
+ */
+ writeLocationInfoTlv: function(loc) {
+ let GsmPDUHelper = this.context.GsmPDUHelper;
+
+ GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_LOCATION_INFO |
+ COMPREHENSIONTLV_FLAG_CR);
+ GsmPDUHelper.writeHexOctet(loc.gsmCellId > 0xffff ? 9 : 7);
+ // From TS 11.14, clause 12.19
+ // "The mobile country code (MCC), the mobile network code (MNC),
+ // the location area code (LAC) and the cell ID are
+ // coded as in TS 04.08."
+ // And from TS 04.08 and TS 24.008,
+ // the format is as follows:
+ //
+ // MCC = MCC_digit_1 + MCC_digit_2 + MCC_digit_3
+ //
+ // 8 7 6 5 4 3 2 1
+ // +-------------+-------------+
+ // | MCC digit 2 | MCC digit 1 | octet 2
+ // | MNC digit 3 | MCC digit 3 | octet 3
+ // | MNC digit 2 | MNC digit 1 | octet 4
+ // +-------------+-------------+
+ //
+ // Also in TS 24.008
+ // "However a network operator may decide to
+ // use only two digits in the MNC in the LAI over the
+ // radio interface. In this case, bits 5 to 8 of octet 3
+ // shall be coded as '1111'".
+
+ // MCC & MNC, 3 octets
+ let mcc = loc.mcc, mnc;
+ if (loc.mnc.length == 2) {
+ mnc = "F" + loc.mnc;
+ } else {
+ mnc = loc.mnc[2] + loc.mnc[0] + loc.mnc[1];
+ }
+ GsmPDUHelper.writeSwappedNibbleBCD(mcc + mnc);
+
+ // LAC, 2 octets
+ GsmPDUHelper.writeHexOctet((loc.gsmLocationAreaCode >> 8) & 0xff);
+ GsmPDUHelper.writeHexOctet(loc.gsmLocationAreaCode & 0xff);
+
+ // Cell Id
+ if (loc.gsmCellId > 0xffff) {
+ // UMTS/WCDMA, gsmCellId is 28 bits.
+ GsmPDUHelper.writeHexOctet((loc.gsmCellId >> 24) & 0xff);
+ GsmPDUHelper.writeHexOctet((loc.gsmCellId >> 16) & 0xff);
+ GsmPDUHelper.writeHexOctet((loc.gsmCellId >> 8) & 0xff);
+ GsmPDUHelper.writeHexOctet(loc.gsmCellId & 0xff);
+ } else {
+ // GSM, gsmCellId is 16 bits.
+ GsmPDUHelper.writeHexOctet((loc.gsmCellId >> 8) & 0xff);
+ GsmPDUHelper.writeHexOctet(loc.gsmCellId & 0xff);
+ }
+ },
+
+ /**
+ * Given a geckoError string, this function translates it into cause value
+ * and write the value into buffer.
+ *
+ * @param geckoError Error string that is passed to gecko.
+ */
+ writeCauseTlv: function(geckoError) {
+ let GsmPDUHelper = this.context.GsmPDUHelper;
+
+ let cause = -1;
+ for (let errorNo in RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR) {
+ if (geckoError == RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[errorNo]) {
+ cause = errorNo;
+ break;
+ }
+ }
+
+ // Causes specified in 10.5.4.11 of TS 04.08 are less than 128.
+ // we can ignore causes > 127 since Cause TLV is optional in
+ // STK_EVENT_TYPE_CALL_DISCONNECTED.
+ if (cause > 127) {
+ return;
+ }
+
+ cause = (cause == -1) ? ERROR_SUCCESS : cause;
+
+ GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_CAUSE |
+ COMPREHENSIONTLV_FLAG_CR);
+ GsmPDUHelper.writeHexOctet(2); // For single cause value.
+
+ // TS 04.08, clause 10.5.4.11:
+ // Code Standard : Standard defined for GSM PLMNS
+ // Location: User
+ GsmPDUHelper.writeHexOctet(0x60);
+
+ // TS 04.08, clause 10.5.4.11: ext bit = 1 + 7 bits for cause.
+ // +-----------------+----------------------------------+
+ // | Ext = 1 (1 bit) | Cause (7 bits) |
+ // +-----------------+----------------------------------+
+ GsmPDUHelper.writeHexOctet(0x80 | cause);
+ },
+
+ writeDateTimeZoneTlv: function(date) {
+ let GsmPDUHelper = this.context.GsmPDUHelper;
+
+ GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_DATE_TIME_ZONE);
+ GsmPDUHelper.writeHexOctet(7);
+ GsmPDUHelper.writeTimestamp(date);
+ },
+
+ writeLanguageTlv: function(language) {
+ let GsmPDUHelper = this.context.GsmPDUHelper;
+
+ GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_LANGUAGE);
+ GsmPDUHelper.writeHexOctet(2);
+
+ // ISO 639-1, Alpha-2 code
+ // TS 123.038, clause 6.2.1, GSM 7 bit Default Alphabet
+ GsmPDUHelper.writeHexOctet(
+ PDU_NL_LOCKING_SHIFT_TABLES[PDU_NL_IDENTIFIER_DEFAULT].indexOf(language[0]));
+ GsmPDUHelper.writeHexOctet(
+ PDU_NL_LOCKING_SHIFT_TABLES[PDU_NL_IDENTIFIER_DEFAULT].indexOf(language[1]));
+ },
+
+ /**
+ * Write Timer Value Comprehension TLV.
+ *
+ * @param seconds length of time during of the timer.
+ * @param cr Comprehension Required or not
+ */
+ writeTimerValueTlv: function(seconds, cr) {
+ let GsmPDUHelper = this.context.GsmPDUHelper;
+
+ GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_TIMER_VALUE |
+ (cr ? COMPREHENSIONTLV_FLAG_CR : 0));
+ GsmPDUHelper.writeHexOctet(3);
+
+ // TS 102.223, clause 8.38
+ // +----------------+------------------+-------------------+
+ // | hours (1 byte) | minutes (1 btye) | seconds (1 byte) |
+ // +----------------+------------------+-------------------+
+ GsmPDUHelper.writeSwappedNibbleBCDNum(Math.floor(seconds / 60 / 60));
+ GsmPDUHelper.writeSwappedNibbleBCDNum(Math.floor(seconds / 60) % 60);
+ GsmPDUHelper.writeSwappedNibbleBCDNum(Math.floor(seconds) % 60);
+ },
+
+ writeTextStringTlv: function(text, coding) {
+ let GsmPDUHelper = this.context.GsmPDUHelper;
+ let buf = GsmPDUHelper.writeWithBuffer(() => {
+ // Write Coding.
+ GsmPDUHelper.writeHexOctet(coding);
+
+ // Write Text String.
+ switch (coding) {
+ case STK_TEXT_CODING_UCS2:
+ GsmPDUHelper.writeUCS2String(text);
+ break;
+ case STK_TEXT_CODING_GSM_7BIT_PACKED:
+ GsmPDUHelper.writeStringAsSeptets(text, 0, 0, 0);
+ break;
+ case STK_TEXT_CODING_GSM_8BIT:
+ GsmPDUHelper.writeStringAs8BitUnpacked(text);
+ break;
+ }
+ });
+
+ let length = buf.length;
+ if (length) {
+ // Write Tag.
+ GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_TEXT_STRING |
+ COMPREHENSIONTLV_FLAG_CR);
+ // Write Length.
+ this.writeLength(length);
+ // Write Value.
+ for (let i = 0; i < length; i++) {
+ GsmPDUHelper.writeHexOctet(buf[i]);
+ }
+ }
+ },
+
+ getSizeOfLengthOctets: function(length) {
+ if (length >= 0x10000) {
+ return 4; // 0x83, len_1, len_2, len_3
+ } else if (length >= 0x100) {
+ return 3; // 0x82, len_1, len_2
+ } else if (length >= 0x80) {
+ return 2; // 0x81, len
+ } else {
+ return 1; // len
+ }
+ },
+
+ writeLength: function(length) {
+ let GsmPDUHelper = this.context.GsmPDUHelper;
+
+ // TS 101.220 clause 7.1.2, Length Encoding.
+ // Length | Byte 1 | Byte 2 | Byte 3 | Byte 4 |
+ // 0 - 127 | 00 - 7f | N/A | N/A | N/A |
+ // 128-255 | 81 | 80 - ff| N/A | N/A |
+ // 256-65535| 82 | 0100 - ffff | N/A |
+ // 65536- | 83 | 010000 - ffffff |
+ // 16777215
+ if (length < 0x80) {
+ GsmPDUHelper.writeHexOctet(length);
+ } else if (0x80 <= length && length < 0x100) {
+ GsmPDUHelper.writeHexOctet(0x81);
+ GsmPDUHelper.writeHexOctet(length);
+ } else if (0x100 <= length && length < 0x10000) {
+ GsmPDUHelper.writeHexOctet(0x82);
+ GsmPDUHelper.writeHexOctet((length >> 8) & 0xff);
+ GsmPDUHelper.writeHexOctet(length & 0xff);
+ } else if (0x10000 <= length && length < 0x1000000) {
+ GsmPDUHelper.writeHexOctet(0x83);
+ GsmPDUHelper.writeHexOctet((length >> 16) & 0xff);
+ GsmPDUHelper.writeHexOctet((length >> 8) & 0xff);
+ GsmPDUHelper.writeHexOctet(length & 0xff);
+ } else {
+ throw new Error("Invalid length value :" + length);
+ }
+ },
+};
+
+function BerTlvHelperObject(aContext) {
+ this.context = aContext;
+}
+BerTlvHelperObject.prototype = {
+ context: null,
+
+ /**
+ * Decode Ber TLV.
+ *
+ * @param dataLen
+ * The length of data in bytes.
+ */
+ decode: function(dataLen) {
+ let GsmPDUHelper = this.context.GsmPDUHelper;
+
+ let hlen = 0;
+ let tag = GsmPDUHelper.readHexOctet();
+ hlen++;
+
+ // The length is coded onto 1 or 2 bytes.
+ // Length | Byte 1 | Byte 2
+ // 0 - 127 | length ('00' to '7f') | N/A
+ // 128 - 255 | '81' | length ('80' to 'ff')
+ let length;
+ let temp = GsmPDUHelper.readHexOctet();
+ hlen++;
+ if (temp < 0x80) {
+ length = temp;
+ } else if (temp === 0x81) {
+ length = GsmPDUHelper.readHexOctet();
+ hlen++;
+ if (length < 0x80) {
+ throw new Error("Invalid length " + length);
+ }
+ } else {
+ throw new Error("Invalid length octet " + temp);
+ }
+
+ // Header + body length check.
+ if (dataLen - hlen !== length) {
+ throw new Error("Unexpected BerTlvHelper value length!!");
+ }
+
+ let method = this[tag];
+ if (typeof method != "function") {
+ throw new Error("Unknown Ber tag 0x" + tag.toString(16));
+ }
+
+ let value = method.call(this, length);
+
+ return {
+ tag: tag,
+ length: length,
+ value: value
+ };
+ },
+
+ /**
+ * Process the value part for FCP template TLV.
+ *
+ * @param length
+ * The length of data in bytes.
+ */
+ processFcpTemplate: function(length) {
+ let tlvs = this.decodeChunks(length);
+ return tlvs;
+ },
+
+ /**
+ * Process the value part for proactive command TLV.
+ *
+ * @param length
+ * The length of data in bytes.
+ */
+ processProactiveCommand: function(length) {
+ let ctlvs = this.context.ComprehensionTlvHelper.decodeChunks(length);
+ return ctlvs;
+ },
+
+ /**
+ * Decode raw data to a Ber-TLV.
+ */
+ decodeInnerTlv: function() {
+ let GsmPDUHelper = this.context.GsmPDUHelper;
+ let tag = GsmPDUHelper.readHexOctet();
+ let length = GsmPDUHelper.readHexOctet();
+ return {
+ tag: tag,
+ length: length,
+ value: this.retrieve(tag, length)
+ };
+ },
+
+ decodeChunks: function(length) {
+ let chunks = [];
+ let index = 0;
+ while (index < length) {
+ let tlv = this.decodeInnerTlv();
+ if (tlv.value) {
+ chunks.push(tlv);
+ }
+ index += tlv.length;
+ // tag + length fields consume 2 bytes.
+ index += 2;
+ }
+ return chunks;
+ },
+
+ retrieve: function(tag, length) {
+ let method = this[tag];
+ if (typeof method != "function") {
+ if (DEBUG) {
+ this.context.debug("Unknown Ber tag : 0x" + tag.toString(16));
+ }
+ let Buf = this.context.Buf;
+ Buf.seekIncoming(length * Buf.PDU_HEX_OCTET_SIZE);
+ return null;
+ }
+ return method.call(this, length);
+ },
+
+ /**
+ * File Size Data.
+ *
+ * | Byte | Description | Length |
+ * | 1 | Tag | 1 |
+ * | 2 | Length | 1 |
+ * | 3 to X+24 | Number of allocated data bytes in the file | X |
+ * | | , excluding structural information | |
+ */
+ retrieveFileSizeData: function(length) {
+ let GsmPDUHelper = this.context.GsmPDUHelper;
+ let fileSizeData = 0;
+ for (let i = 0; i < length; i++) {
+ fileSizeData = fileSizeData << 8;
+ fileSizeData += GsmPDUHelper.readHexOctet();
+ }
+
+ return {fileSizeData: fileSizeData};
+ },
+
+ /**
+ * File Descriptor.
+ *
+ * | Byte | Description | Length |
+ * | 1 | Tag | 1 |
+ * | 2 | Length | 1 |
+ * | 3 | File descriptor byte | 1 |
+ * | 4 | Data coding byte | 1 |
+ * | 5 ~ 6 | Record length | 2 |
+ * | 7 | Number of records | 1 |
+ */
+ retrieveFileDescriptor: function(length) {
+ let GsmPDUHelper = this.context.GsmPDUHelper;
+ let fileDescriptorByte = GsmPDUHelper.readHexOctet();
+ let dataCodingByte = GsmPDUHelper.readHexOctet();
+ // See TS 102 221 Table 11.5, we only care the least 3 bits for the
+ // structure of file.
+ let fileStructure = fileDescriptorByte & 0x07;
+
+ let fileDescriptor = {
+ fileStructure: fileStructure
+ };
+ // byte 5 ~ 7 are mandatory for linear fixed and cyclic files, otherwise
+ // they are not applicable.
+ if (fileStructure === UICC_EF_STRUCTURE[EF_STRUCTURE_LINEAR_FIXED] ||
+ fileStructure === UICC_EF_STRUCTURE[EF_STRUCTURE_CYCLIC]) {
+ fileDescriptor.recordLength = (GsmPDUHelper.readHexOctet() << 8) +
+ GsmPDUHelper.readHexOctet();
+ fileDescriptor.numOfRecords = GsmPDUHelper.readHexOctet();
+ }
+
+ return fileDescriptor;
+ },
+
+ /**
+ * File identifier.
+ *
+ * | Byte | Description | Length |
+ * | 1 | Tag | 1 |
+ * | 2 | Length | 1 |
+ * | 3 ~ 4 | File identifier | 2 |
+ */
+ retrieveFileIdentifier: function(length) {
+ let GsmPDUHelper = this.context.GsmPDUHelper;
+ return {fileId : (GsmPDUHelper.readHexOctet() << 8) +
+ GsmPDUHelper.readHexOctet()};
+ },
+
+ searchForNextTag: function(tag, iter) {
+ for (let tlv of iter) {
+ if (tlv.tag === tag) {
+ return tlv;
+ }
+ }
+ return null;
+ }
+};
+BerTlvHelperObject.prototype[BER_FCP_TEMPLATE_TAG] = function BER_FCP_TEMPLATE_TAG(length) {
+ return this.processFcpTemplate(length);
+};
+BerTlvHelperObject.prototype[BER_PROACTIVE_COMMAND_TAG] = function BER_PROACTIVE_COMMAND_TAG(length) {
+ return this.processProactiveCommand(length);
+};
+BerTlvHelperObject.prototype[BER_FCP_FILE_SIZE_DATA_TAG] = function BER_FCP_FILE_SIZE_DATA_TAG(length) {
+ return this.retrieveFileSizeData(length);
+};
+BerTlvHelperObject.prototype[BER_FCP_FILE_DESCRIPTOR_TAG] = function BER_FCP_FILE_DESCRIPTOR_TAG(length) {
+ return this.retrieveFileDescriptor(length);
+};
+BerTlvHelperObject.prototype[BER_FCP_FILE_IDENTIFIER_TAG] = function BER_FCP_FILE_IDENTIFIER_TAG(length) {
+ return this.retrieveFileIdentifier(length);
+};
+
+/**
+ * ICC Helper for getting EF path.
+ */
+function ICCFileHelperObject(aContext) {
+ this.context = aContext;
+}
+ICCFileHelperObject.prototype = {
+ context: null,
+
+ /**
+ * This function handles only EFs that are common to RUIM, SIM, USIM
+ * and other types of ICC cards.
+ */
+ getCommonEFPath: function(fileId) {
+ switch (fileId) {
+ case ICC_EF_ICCID:
+ return EF_PATH_MF_SIM;
+ case ICC_EF_ADN:
+ case ICC_EF_SDN: // Fall through.
+ return EF_PATH_MF_SIM + EF_PATH_DF_TELECOM;
+ case ICC_EF_PBR:
+ return EF_PATH_MF_SIM + EF_PATH_DF_TELECOM + EF_PATH_DF_PHONEBOOK;
+ case ICC_EF_IMG:
+ return EF_PATH_MF_SIM + EF_PATH_DF_TELECOM + EF_PATH_GRAPHICS;
+ }
+ return null;
+ },
+
+ /**
+ * This function handles EFs for SIM.
+ */
+ getSimEFPath: function(fileId) {
+ switch (fileId) {
+ case ICC_EF_FDN:
+ case ICC_EF_MSISDN:
+ case ICC_EF_SMS:
+ case ICC_EF_EXT1:
+ case ICC_EF_EXT2:
+ case ICC_EF_EXT3:
+ return EF_PATH_MF_SIM + EF_PATH_DF_TELECOM;
+ case ICC_EF_AD:
+ case ICC_EF_MBDN:
+ case ICC_EF_MWIS:
+ case ICC_EF_PLMNsel:
+ case ICC_EF_SPN:
+ case ICC_EF_SPDI:
+ case ICC_EF_SST:
+ case ICC_EF_PHASE:
+ case ICC_EF_CBMI:
+ case ICC_EF_CBMID:
+ case ICC_EF_CBMIR:
+ case ICC_EF_OPL:
+ case ICC_EF_PNN:
+ case ICC_EF_GID1:
+ case ICC_EF_CPHS_INFO:
+ case ICC_EF_CPHS_MBN:
+ return EF_PATH_MF_SIM + EF_PATH_DF_GSM;
+ default:
+ return null;
+ }
+ },
+
+ /**
+ * This function handles EFs for USIM.
+ */
+ getUSimEFPath: function(fileId) {
+ switch (fileId) {
+ case ICC_EF_AD:
+ case ICC_EF_FDN:
+ case ICC_EF_MBDN:
+ case ICC_EF_MWIS:
+ case ICC_EF_UST:
+ case ICC_EF_MSISDN:
+ case ICC_EF_SPN:
+ case ICC_EF_SPDI:
+ case ICC_EF_CBMI:
+ case ICC_EF_CBMID:
+ case ICC_EF_CBMIR:
+ case ICC_EF_OPL:
+ case ICC_EF_PNN:
+ case ICC_EF_SMS:
+ case ICC_EF_GID1:
+ // CPHS spec was provided in 1997 based on SIM requirement, there is no
+ // detailed info about how these ICC_EF_CPHS_XXX are allocated in USIM.
+ // What we can do now is to follow what has been done in AOSP to have file
+ // path equal to MF_SIM/DF_GSM.
+ case ICC_EF_CPHS_INFO:
+ case ICC_EF_CPHS_MBN:
+ return EF_PATH_MF_SIM + EF_PATH_ADF_USIM;
+ default:
+ // The file ids in USIM phone book entries are decided by the
+ // card manufacturer. So if we don't match any of the cases
+ // above and if its a USIM return the phone book path.
+ return EF_PATH_MF_SIM + EF_PATH_DF_TELECOM + EF_PATH_DF_PHONEBOOK;
+ }
+ },
+
+ /**
+ * This function handles EFs for RUIM
+ */
+ getRuimEFPath: function(fileId) {
+ switch(fileId) {
+ case ICC_EF_CSIM_IMSI_M:
+ case ICC_EF_CSIM_CDMAHOME:
+ case ICC_EF_CSIM_CST:
+ case ICC_EF_CSIM_SPN:
+ return EF_PATH_MF_SIM + EF_PATH_DF_CDMA;
+ case ICC_EF_FDN:
+ case ICC_EF_EXT1:
+ case ICC_EF_EXT2:
+ case ICC_EF_EXT3:
+ return EF_PATH_MF_SIM + EF_PATH_DF_TELECOM;
+ default:
+ return null;
+ }
+ },
+
+ /**
+ * Helper function for getting the pathId for the specific ICC record
+ * depeding on which type of ICC card we are using.
+ *
+ * @param fileId
+ * File id.
+ * @return The pathId or null in case of an error or invalid input.
+ */
+ getEFPath: function(fileId) {
+ let path = this.getCommonEFPath(fileId);
+ if (path) {
+ return path;
+ }
+
+ switch (this.context.RIL.appType) {
+ case CARD_APPTYPE_SIM:
+ return this.getSimEFPath(fileId);
+ case CARD_APPTYPE_USIM:
+ return this.getUSimEFPath(fileId);
+ case CARD_APPTYPE_RUIM:
+ return this.getRuimEFPath(fileId);
+ default:
+ return null;
+ }
+ }
+};
+
+/**
+ * Helper for ICC IO functionalities.
+ */
+function ICCIOHelperObject(aContext) {
+ this.context = aContext;
+}
+ICCIOHelperObject.prototype = {
+ context: null,
+
+ /**
+ * Load EF with type 'Linear Fixed'.
+ *
+ * @param fileId
+ * The file to operate on, one of the ICC_EF_* constants.
+ * @param recordNumber [optional]
+ * The number of the record shall be loaded.
+ * @param recordSize [optional]
+ * The size of the record.
+ * @param callback [optional]
+ * The callback function shall be called when the record(s) is read.
+ * @param onerror [optional]
+ * The callback function shall be called when failure.
+ */
+ loadLinearFixedEF: function(options) {
+ let cb;
+ let readRecord = (function(options) {
+ options.command = ICC_COMMAND_READ_RECORD;
+ options.p1 = options.recordNumber || 1; // Record number
+ options.p2 = READ_RECORD_ABSOLUTE_MODE;
+ options.p3 = options.recordSize;
+ options.callback = cb || options.callback;
+ this.context.RIL.iccIO(options);
+ }).bind(this);
+
+ options.structure = EF_STRUCTURE_LINEAR_FIXED;
+ options.pathId = this.context.ICCFileHelper.getEFPath(options.fileId);
+ if (options.recordSize) {
+ readRecord(options);
+ return;
+ }
+
+ cb = options.callback;
+ options.callback = readRecord;
+ this.getResponse(options);
+ },
+
+ /**
+ * Load next record from current record Id.
+ */
+ loadNextRecord: function(options) {
+ options.p1++;
+ this.context.RIL.iccIO(options);
+ },
+
+ /**
+ * Update EF with type 'Linear Fixed'.
+ *
+ * @param fileId
+ * The file to operate on, one of the ICC_EF_* constants.
+ * @param recordNumber
+ * The number of the record shall be updated.
+ * @param dataWriter [optional]
+ * The function for writing string parameter for the ICC_COMMAND_UPDATE_RECORD.
+ * @param pin2 [optional]
+ * PIN2 is required when updating ICC_EF_FDN.
+ * @param callback [optional]
+ * The callback function shall be called when the record is updated.
+ * @param onerror [optional]
+ * The callback function shall be called when failure.
+ */
+ updateLinearFixedEF: function(options) {
+ if (!options.fileId || !options.recordNumber) {
+ throw new Error("Unexpected fileId " + options.fileId +
+ " or recordNumber " + options.recordNumber);
+ }
+
+ options.structure = EF_STRUCTURE_LINEAR_FIXED;
+ options.pathId = this.context.ICCFileHelper.getEFPath(options.fileId);
+ let cb = options.callback;
+ options.callback = function callback(options) {
+ options.callback = cb;
+ options.command = ICC_COMMAND_UPDATE_RECORD;
+ options.p1 = options.recordNumber;
+ options.p2 = READ_RECORD_ABSOLUTE_MODE;
+ options.p3 = options.recordSize;
+ this.context.RIL.iccIO(options);
+ }.bind(this);
+ this.getResponse(options);
+ },
+
+ /**
+ * Load EF with type 'Transparent'.
+ *
+ * @param fileId
+ * The file to operate on, one of the ICC_EF_* constants.
+ * @param callback [optional]
+ * The callback function shall be called when the record(s) is read.
+ * @param onerror [optional]
+ * The callback function shall be called when failure.
+ */
+ loadTransparentEF: function(options) {
+ options.structure = EF_STRUCTURE_TRANSPARENT;
+ let cb = options.callback;
+ options.callback = function callback(options) {
+ options.callback = cb;
+ options.command = ICC_COMMAND_READ_BINARY;
+ options.p2 = 0x00;
+ options.p3 = options.fileSize;
+ this.context.RIL.iccIO(options);
+ }.bind(this);
+ this.getResponse(options);
+ },
+
+ /**
+ * Use ICC_COMMAND_GET_RESPONSE to query the EF.
+ *
+ * @param fileId
+ * The file to operate on, one of the ICC_EF_* constants.
+ */
+ getResponse: function(options) {
+ options.command = ICC_COMMAND_GET_RESPONSE;
+ options.pathId = options.pathId ||
+ this.context.ICCFileHelper.getEFPath(options.fileId);
+ if (!options.pathId) {
+ throw new Error("Unknown pathId for " + options.fileId.toString(16));
+ }
+ options.p1 = 0; // For GET_RESPONSE, p1 = 0
+ switch (this.context.RIL.appType) {
+ case CARD_APPTYPE_USIM:
+ options.p2 = GET_RESPONSE_FCP_TEMPLATE;
+ options.p3 = 0x00;
+ break;
+ // For RUIM, CSIM and ISIM, cf bug 955946: keep the old behavior
+ case CARD_APPTYPE_RUIM:
+ case CARD_APPTYPE_CSIM:
+ case CARD_APPTYPE_ISIM:
+ // For SIM, this is what we want
+ case CARD_APPTYPE_SIM:
+ default:
+ options.p2 = 0x00;
+ options.p3 = GET_RESPONSE_EF_SIZE_BYTES;
+ break;
+ }
+ this.context.RIL.iccIO(options);
+ },
+
+ /**
+ * Process ICC I/O response.
+ */
+ processICCIO: function(options) {
+ let func = this[options.command];
+ func.call(this, options);
+ },
+
+ /**
+ * Process a ICC_COMMAND_GET_RESPONSE type command for REQUEST_SIM_IO.
+ */
+ processICCIOGetResponse: function(options) {
+ let Buf = this.context.Buf;
+ let strLen = Buf.readInt32();
+
+ let peek = this.context.GsmPDUHelper.readHexOctet();
+ Buf.seekIncoming(-1 * Buf.PDU_HEX_OCTET_SIZE);
+ if (peek === BER_FCP_TEMPLATE_TAG) {
+ this.processUSimGetResponse(options, strLen / 2);
+ } else {
+ this.processSimGetResponse(options);
+ }
+ Buf.readStringDelimiter(strLen);
+
+ if (options.callback) {
+ options.callback(options);
+ }
+ },
+
+ /**
+ * Helper function for processing USIM get response.
+ */
+ processUSimGetResponse: function(options, octetLen) {
+ let BerTlvHelper = this.context.BerTlvHelper;
+
+ let berTlv = BerTlvHelper.decode(octetLen);
+ // See TS 102 221 Table 11.4 for the content order of getResponse.
+ let iter = berTlv.value.values();
+ let tlv = BerTlvHelper.searchForNextTag(BER_FCP_FILE_DESCRIPTOR_TAG,
+ iter);
+ if (!tlv ||
+ (tlv.value.fileStructure !== UICC_EF_STRUCTURE[options.structure])) {
+ throw new Error("Expected EF structure " +
+ UICC_EF_STRUCTURE[options.structure] +
+ " but read " + tlv.value.fileStructure);
+ }
+
+ if (tlv.value.fileStructure === UICC_EF_STRUCTURE[EF_STRUCTURE_LINEAR_FIXED] ||
+ tlv.value.fileStructure === UICC_EF_STRUCTURE[EF_STRUCTURE_CYCLIC]) {
+ options.recordSize = tlv.value.recordLength;
+ options.totalRecords = tlv.value.numOfRecords;
+ }
+
+ tlv = BerTlvHelper.searchForNextTag(BER_FCP_FILE_IDENTIFIER_TAG, iter);
+ if (!tlv || (tlv.value.fileId !== options.fileId)) {
+ throw new Error("Expected file ID " + options.fileId.toString(16) +
+ " but read " + fileId.toString(16));
+ }
+
+ tlv = BerTlvHelper.searchForNextTag(BER_FCP_FILE_SIZE_DATA_TAG, iter);
+ if (!tlv) {
+ throw new Error("Unexpected file size data");
+ }
+ options.fileSize = tlv.value.fileSizeData;
+ },
+
+ /**
+ * Helper function for processing SIM get response.
+ */
+ processSimGetResponse: function(options) {
+ let Buf = this.context.Buf;
+ let GsmPDUHelper = this.context.GsmPDUHelper;
+
+ // The format is from TS 51.011, clause 9.2.1
+
+ // Skip RFU, data[0] data[1].
+ Buf.seekIncoming(2 * Buf.PDU_HEX_OCTET_SIZE);
+
+ // File size, data[2], data[3]
+ options.fileSize = (GsmPDUHelper.readHexOctet() << 8) |
+ GsmPDUHelper.readHexOctet();
+
+ // 2 bytes File id. data[4], data[5]
+ let fileId = (GsmPDUHelper.readHexOctet() << 8) |
+ GsmPDUHelper.readHexOctet();
+ if (fileId != options.fileId) {
+ throw new Error("Expected file ID " + options.fileId.toString(16) +
+ " but read " + fileId.toString(16));
+ }
+
+ // Type of file, data[6]
+ let fileType = GsmPDUHelper.readHexOctet();
+ if (fileType != TYPE_EF) {
+ throw new Error("Unexpected file type " + fileType);
+ }
+
+ // Skip 1 byte RFU, data[7],
+ // 3 bytes Access conditions, data[8] data[9] data[10],
+ // 1 byte File status, data[11],
+ // 1 byte Length of the following data, data[12].
+ Buf.seekIncoming(((RESPONSE_DATA_STRUCTURE - RESPONSE_DATA_FILE_TYPE - 1) *
+ Buf.PDU_HEX_OCTET_SIZE));
+
+ // Read Structure of EF, data[13]
+ let efStructure = GsmPDUHelper.readHexOctet();
+ if (efStructure != options.structure) {
+ throw new Error("Expected EF structure " + options.structure +
+ " but read " + efStructure);
+ }
+
+ // Length of a record, data[14].
+ // Only available for LINEAR_FIXED and CYCLIC.
+ if (efStructure == EF_STRUCTURE_LINEAR_FIXED ||
+ efStructure == EF_STRUCTURE_CYCLIC) {
+ options.recordSize = GsmPDUHelper.readHexOctet();
+ options.totalRecords = options.fileSize / options.recordSize;
+ } else {
+ Buf.seekIncoming(1 * Buf.PDU_HEX_OCTET_SIZE);
+ }
+ },
+
+ /**
+ * Process a ICC_COMMAND_READ_RECORD type command for REQUEST_SIM_IO.
+ */
+ processICCIOReadRecord: function(options) {
+ if (options.callback) {
+ options.callback(options);
+ }
+ },
+
+ /**
+ * Process a ICC_COMMAND_READ_BINARY type command for REQUEST_SIM_IO.
+ */
+ processICCIOReadBinary: function(options) {
+ if (options.callback) {
+ options.callback(options);
+ }
+ },
+
+ /**
+ * Process a ICC_COMMAND_UPDATE_RECORD type command for REQUEST_SIM_IO.
+ */
+ processICCIOUpdateRecord: function(options) {
+ if (options.callback) {
+ options.callback(options);
+ }
+ },
+};
+ICCIOHelperObject.prototype[ICC_COMMAND_SEEK] = null;
+ICCIOHelperObject.prototype[ICC_COMMAND_READ_BINARY] = function ICC_COMMAND_READ_BINARY(options) {
+ this.processICCIOReadBinary(options);
+};
+ICCIOHelperObject.prototype[ICC_COMMAND_READ_RECORD] = function ICC_COMMAND_READ_RECORD(options) {
+ this.processICCIOReadRecord(options);
+};
+ICCIOHelperObject.prototype[ICC_COMMAND_GET_RESPONSE] = function ICC_COMMAND_GET_RESPONSE(options) {
+ this.processICCIOGetResponse(options);
+};
+ICCIOHelperObject.prototype[ICC_COMMAND_UPDATE_BINARY] = null;
+ICCIOHelperObject.prototype[ICC_COMMAND_UPDATE_RECORD] = function ICC_COMMAND_UPDATE_RECORD(options) {
+ this.processICCIOUpdateRecord(options);
+};
+
+/**
+ * Helper for ICC records.
+ */
+function ICCRecordHelperObject(aContext) {
+ this.context = aContext;
+ // Cache the possible free record id for all files, use fileId as key.
+ this._freeRecordIds = {};
+}
+ICCRecordHelperObject.prototype = {
+ context: null,
+
+ /**
+ * Fetch ICC records.
+ */
+ fetchICCRecords: function() {
+ switch (this.context.RIL.appType) {
+ case CARD_APPTYPE_SIM:
+ case CARD_APPTYPE_USIM:
+ this.context.SimRecordHelper.fetchSimRecords();
+ break;
+ case CARD_APPTYPE_RUIM:
+ this.context.RuimRecordHelper.fetchRuimRecords();
+ break;
+ }
+ },
+
+ /**
+ * Read the ICCID.
+ */
+ readICCID: function() {
+ function callback() {
+ let Buf = this.context.Buf;
+ let RIL = this.context.RIL;
+ let GsmPDUHelper = this.context.GsmPDUHelper;
+
+ let strLen = Buf.readInt32();
+ let octetLen = strLen / 2;
+ RIL.iccInfo.iccid =
+ GsmPDUHelper.readSwappedNibbleBcdString(octetLen, true);
+ // Consumes the remaining buffer if any.
+ let unReadBuffer = this.context.Buf.getReadAvailable() -
+ this.context.Buf.PDU_HEX_OCTET_SIZE;
+ if (unReadBuffer > 0) {
+ this.context.Buf.seekIncoming(unReadBuffer);
+ }
+ Buf.readStringDelimiter(strLen);
+
+ if (DEBUG) this.context.debug("ICCID: " + RIL.iccInfo.iccid);
+ if (RIL.iccInfo.iccid) {
+ this.context.ICCUtilsHelper.handleICCInfoChange();
+ RIL.reportStkServiceIsRunning();
+ }
+ }
+
+ this.context.ICCIOHelper.loadTransparentEF({
+ fileId: ICC_EF_ICCID,
+ callback: callback.bind(this)
+ });
+ },
+
+ /**
+ * Read ICC ADN like EF, i.e. EF_ADN, EF_FDN.
+ *
+ * @param fileId EF id of the ADN, FDN or SDN.
+ * @param extFileId EF id of the EXT.
+ * @param onsuccess Callback to be called when success.
+ * @param onerror Callback to be called when error.
+ */
+ readADNLike: function(fileId, extFileId, onsuccess, onerror) {
+ let ICCIOHelper = this.context.ICCIOHelper;
+
+ function callback(options) {
+ let loadNextContactRecord = () => {
+ if (options.p1 < options.totalRecords) {
+ ICCIOHelper.loadNextRecord(options);
+ return;
+ }
+ if (DEBUG) {
+ for (let i = 0; i < contacts.length; i++) {
+ this.context.debug("contact [" + i + "] " +
+ JSON.stringify(contacts[i]));
+ }
+ }
+ if (onsuccess) {
+ onsuccess(contacts);
+ }
+ };
+
+ let contact =
+ this.context.ICCPDUHelper.readAlphaIdDiallingNumber(options.recordSize);
+ if (contact) {
+ let record = {
+ recordId: options.p1,
+ alphaId: contact.alphaId,
+ number: contact.number
+ };
+ contacts.push(record);
+
+ if (extFileId && contact.extRecordNumber != 0xff) {
+ this.readExtension(extFileId, contact.extRecordNumber, (number) => {
+ if (number) {
+ record.number += number;
+ }
+ loadNextContactRecord();
+ }, () => loadNextContactRecord());
+ return;
+ }
+ }
+ loadNextContactRecord();
+ }
+
+ let contacts = [];
+ ICCIOHelper.loadLinearFixedEF({fileId: fileId,
+ callback: callback.bind(this),
+ onerror: onerror});
+ },
+
+ /**
+ * Update ICC ADN like EFs, like EF_ADN, EF_FDN.
+ *
+ * @param fileId EF id of the ADN or FDN.
+ * @param extRecordNumber The record identifier of the EXT.
+ * @param contact The contact will be updated. (Shall have recordId property)
+ * @param pin2 PIN2 is required when updating ICC_EF_FDN.
+ * @param onsuccess Callback to be called when success.
+ * @param onerror Callback to be called when error.
+ */
+ updateADNLike: function(fileId, extRecordNumber, contact, pin2, onsuccess, onerror) {
+ let updatedContact;
+ function dataWriter(recordSize) {
+ updatedContact = this.context.ICCPDUHelper.writeAlphaIdDiallingNumber(recordSize,
+ contact.alphaId,
+ contact.number,
+ extRecordNumber);
+ }
+
+ function callback(options) {
+ if (onsuccess) {
+ onsuccess(updatedContact);
+ }
+ }
+
+ if (!contact || !contact.recordId) {
+ if (onerror) onerror(GECKO_ERROR_INVALID_PARAMETER);
+ return;
+ }
+
+ this.context.ICCIOHelper.updateLinearFixedEF({
+ fileId: fileId,
+ recordNumber: contact.recordId,
+ dataWriter: dataWriter.bind(this),
+ pin2: pin2,
+ callback: callback.bind(this),
+ onerror: onerror
+ });
+ },
+
+ /**
+ * Read USIM/RUIM Phonebook.
+ *
+ * @param onsuccess Callback to be called when success.
+ * @param onerror Callback to be called when error.
+ */
+ readPBR: function(onsuccess, onerror) {
+ let Buf = this.context.Buf;
+ let GsmPDUHelper = this.context.GsmPDUHelper;
+ let ICCIOHelper = this.context.ICCIOHelper;
+ let ICCUtilsHelper = this.context.ICCUtilsHelper;
+ let RIL = this.context.RIL;
+
+ function callback(options) {
+ let strLen = Buf.readInt32();
+ let octetLen = strLen / 2, readLen = 0;
+
+ let pbrTlvs = [];
+ while (readLen < octetLen) {
+ let tag = GsmPDUHelper.readHexOctet();
+ if (tag == 0xff) {
+ readLen++;
+ Buf.seekIncoming((octetLen - readLen) * Buf.PDU_HEX_OCTET_SIZE);
+ break;
+ }
+
+ let tlvLen = GsmPDUHelper.readHexOctet();
+ let tlvs = ICCUtilsHelper.decodeSimTlvs(tlvLen);
+ pbrTlvs.push({tag: tag,
+ length: tlvLen,
+ value: tlvs});
+
+ readLen += tlvLen + 2; // +2 for tag and tlvLen
+ }
+ Buf.readStringDelimiter(strLen);
+
+ if (pbrTlvs.length > 0) {
+ let pbr = ICCUtilsHelper.parsePbrTlvs(pbrTlvs);
+ // EF_ADN is mandatory if and only if DF_PHONEBOOK is present.
+ if (!pbr.adn) {
+ if (onerror) onerror("Cannot access ADN.");
+ return;
+ }
+ pbrs.push(pbr);
+ }
+
+ if (options.p1 < options.totalRecords) {
+ ICCIOHelper.loadNextRecord(options);
+ } else {
+ if (onsuccess) {
+ RIL.iccInfoPrivate.pbrs = pbrs;
+ onsuccess(pbrs);
+ }
+ }
+ }
+
+ if (RIL.iccInfoPrivate.pbrs) {
+ onsuccess(RIL.iccInfoPrivate.pbrs);
+ return;
+ }
+
+ let pbrs = [];
+ ICCIOHelper.loadLinearFixedEF({fileId : ICC_EF_PBR,
+ callback: callback.bind(this),
+ onerror: onerror});
+ },
+
+ /**
+ * Cache EF_IAP record size.
+ */
+ _iapRecordSize: null,
+
+ /**
+ * Read ICC EF_IAP. (Index Administration Phonebook)
+ *
+ * @see TS 131.102, clause 4.4.2.2
+ *
+ * @param fileId EF id of the IAP.
+ * @param recordNumber The number of the record shall be loaded.
+ * @param onsuccess Callback to be called when success.
+ * @param onerror Callback to be called when error.
+ */
+ readIAP: function(fileId, recordNumber, onsuccess, onerror) {
+ function callback(options) {
+ let Buf = this.context.Buf;
+ let strLen = Buf.readInt32();
+ let octetLen = strLen / 2;
+ this._iapRecordSize = options.recordSize;
+
+ let iap = this.context.GsmPDUHelper.readHexOctetArray(octetLen);
+ Buf.readStringDelimiter(strLen);
+
+ if (onsuccess) {
+ onsuccess(iap);
+ }
+ }
+
+ this.context.ICCIOHelper.loadLinearFixedEF({
+ fileId: fileId,
+ recordNumber: recordNumber,
+ recordSize: this._iapRecordSize,
+ callback: callback.bind(this),
+ onerror: onerror
+ });
+ },
+
+ /**
+ * Update USIM/RUIM Phonebook EF_IAP.
+ *
+ * @see TS 131.102, clause 4.4.2.13
+ *
+ * @param fileId EF id of the IAP.
+ * @param recordNumber The identifier of the record shall be updated.
+ * @param iap The IAP value to be written.
+ * @param onsuccess Callback to be called when success.
+ * @param onerror Callback to be called when error.
+ */
+ updateIAP: function(fileId, recordNumber, iap, onsuccess, onerror) {
+ let dataWriter = function dataWriter(recordSize) {
+ let Buf = this.context.Buf;
+ let GsmPDUHelper = this.context.GsmPDUHelper;
+
+ // Write String length
+ let strLen = recordSize * 2;
+ Buf.writeInt32(strLen);
+
+ for (let i = 0; i < iap.length; i++) {
+ GsmPDUHelper.writeHexOctet(iap[i]);
+ }
+
+ Buf.writeStringDelimiter(strLen);
+ }.bind(this);
+
+ this.context.ICCIOHelper.updateLinearFixedEF({
+ fileId: fileId,
+ recordNumber: recordNumber,
+ dataWriter: dataWriter,
+ callback: onsuccess,
+ onerror: onerror
+ });
+ },
+
+ /**
+ * Cache EF_Email record size.
+ */
+ _emailRecordSize: null,
+
+ /**
+ * Read USIM/RUIM Phonebook EF_EMAIL.
+ *
+ * @see TS 131.102, clause 4.4.2.13
+ *
+ * @param fileId EF id of the EMAIL.
+ * @param fileType The type of the EMAIL, one of the ICC_USIM_TYPE* constants.
+ * @param recordNumber The number of the record shall be loaded.
+ * @param onsuccess Callback to be called when success.
+ * @param onerror Callback to be called when error.
+ */
+ readEmail: function(fileId, fileType, recordNumber, onsuccess, onerror) {
+ function callback(options) {
+ let Buf = this.context.Buf;
+ let ICCPDUHelper = this.context.ICCPDUHelper;
+
+ let strLen = Buf.readInt32();
+ let octetLen = strLen / 2;
+ let email = null;
+ this._emailRecordSize = options.recordSize;
+
+ // Read contact's email
+ //
+ // | Byte | Description | Length | M/O
+ // | 1 ~ X | E-mail Address | X | M
+ // | X+1 | ADN file SFI | 1 | C
+ // | X+2 | ADN file Record Identifier | 1 | C
+ // Note: The fields marked as C above are mandatort if the file
+ // is not type 1 (as specified in EF_PBR)
+ if (fileType == ICC_USIM_TYPE1_TAG) {
+ email = ICCPDUHelper.read8BitUnpackedToString(octetLen);
+ } else {
+ email = ICCPDUHelper.read8BitUnpackedToString(octetLen - 2);
+
+ // Consumes the remaining buffer
+ Buf.seekIncoming(2 * Buf.PDU_HEX_OCTET_SIZE); // For ADN SFI and Record Identifier
+ }
+
+ Buf.readStringDelimiter(strLen);
+
+ if (onsuccess) {
+ onsuccess(email);
+ }
+ }
+
+ this.context.ICCIOHelper.loadLinearFixedEF({
+ fileId: fileId,
+ recordNumber: recordNumber,
+ recordSize: this._emailRecordSize,
+ callback: callback.bind(this),
+ onerror: onerror
+ });
+ },
+
+ /**
+ * Update USIM/RUIM Phonebook EF_EMAIL.
+ *
+ * @see TS 131.102, clause 4.4.2.13
+ *
+ * @param pbr Phonebook Reference File.
+ * @param recordNumber The identifier of the record shall be updated.
+ * @param email The value to be written.
+ * @param adnRecordId The record Id of ADN, only needed if the fileType of Email is TYPE2.
+ * @param onsuccess Callback to be called when success.
+ * @param onerror Callback to be called when error.
+ */
+ updateEmail: function(pbr, recordNumber, email, adnRecordId, onsuccess, onerror) {
+ let fileId = pbr[USIM_PBR_EMAIL].fileId;
+ let fileType = pbr[USIM_PBR_EMAIL].fileType;
+ let writtenEmail;
+ let dataWriter = function dataWriter(recordSize) {
+ let Buf = this.context.Buf;
+ let GsmPDUHelper = this.context.GsmPDUHelper;
+ let ICCPDUHelper = this.context.ICCPDUHelper;
+
+ // Write String length
+ let strLen = recordSize * 2;
+ Buf.writeInt32(strLen);
+
+ if (fileType == ICC_USIM_TYPE1_TAG) {
+ writtenEmail = ICCPDUHelper.writeStringTo8BitUnpacked(recordSize, email);
+ } else {
+ writtenEmail = ICCPDUHelper.writeStringTo8BitUnpacked(recordSize - 2, email);
+ GsmPDUHelper.writeHexOctet(pbr.adn.sfi || 0xff);
+ GsmPDUHelper.writeHexOctet(adnRecordId);
+ }
+
+ Buf.writeStringDelimiter(strLen);
+ }.bind(this);
+
+ let callback = (options) => {
+ if (onsuccess) {
+ onsuccess(writtenEmail);
+ }
+ }
+
+ this.context.ICCIOHelper.updateLinearFixedEF({
+ fileId: fileId,
+ recordNumber: recordNumber,
+ dataWriter: dataWriter,
+ callback: callback,
+ onerror: onerror
+ });
+ },
+
+ /**
+ * Cache EF_ANR record size.
+ */
+ _anrRecordSize: null,
+
+ /**
+ * Read USIM/RUIM Phonebook EF_ANR.
+ *
+ * @see TS 131.102, clause 4.4.2.9
+ *
+ * @param fileId EF id of the ANR.
+ * @param fileType One of the ICC_USIM_TYPE* constants.
+ * @param recordNumber The number of the record shall be loaded.
+ * @param onsuccess Callback to be called when success.
+ * @param onerror Callback to be called when error.
+ */
+ readANR: function(fileId, fileType, recordNumber, onsuccess, onerror) {
+ function callback(options) {
+ let Buf = this.context.Buf;
+ let strLen = Buf.readInt32();
+ let number = null;
+ this._anrRecordSize = options.recordSize;
+
+ // Skip EF_AAS Record ID.
+ Buf.seekIncoming(1 * Buf.PDU_HEX_OCTET_SIZE);
+
+ number = this.context.ICCPDUHelper.readNumberWithLength();
+
+ // Skip 2 unused octets, CCP and EXT1.
+ Buf.seekIncoming(2 * Buf.PDU_HEX_OCTET_SIZE);
+
+ // For Type 2 there are two extra octets.
+ if (fileType == ICC_USIM_TYPE2_TAG) {
+ // Skip 2 unused octets, ADN SFI and Record Identifier.
+ Buf.seekIncoming(2 * Buf.PDU_HEX_OCTET_SIZE);
+ }
+
+ Buf.readStringDelimiter(strLen);
+
+ if (onsuccess) {
+ onsuccess(number);
+ }
+ }
+
+ this.context.ICCIOHelper.loadLinearFixedEF({
+ fileId: fileId,
+ recordNumber: recordNumber,
+ recordSize: this._anrRecordSize,
+ callback: callback.bind(this),
+ onerror: onerror
+ });
+ },
+
+ /**
+ * Update USIM/RUIM Phonebook EF_ANR.
+ *
+ * @see TS 131.102, clause 4.4.2.9
+ *
+ * @param pbr Phonebook Reference File.
+ * @param recordNumber The identifier of the record shall be updated.
+ * @param number The value to be written.
+ * @param adnRecordId The record Id of ADN, only needed if the fileType of Email is TYPE2.
+ * @param onsuccess Callback to be called when success.
+ * @param onerror Callback to be called when error.
+ */
+ updateANR: function(pbr, recordNumber, number, adnRecordId, onsuccess, onerror) {
+ let fileId = pbr[USIM_PBR_ANR0].fileId;
+ let fileType = pbr[USIM_PBR_ANR0].fileType;
+ let writtenNumber;
+ let dataWriter = function dataWriter(recordSize) {
+ let Buf = this.context.Buf;
+ let GsmPDUHelper = this.context.GsmPDUHelper;
+
+ // Write String length
+ let strLen = recordSize * 2;
+ Buf.writeInt32(strLen);
+
+ // EF_AAS record Id. Unused for now.
+ GsmPDUHelper.writeHexOctet(0xff);
+
+ writtenNumber = this.context.ICCPDUHelper.writeNumberWithLength(number);
+
+ // Write unused octets 0xff, CCP and EXT1.
+ GsmPDUHelper.writeHexOctet(0xff);
+ GsmPDUHelper.writeHexOctet(0xff);
+
+ // For Type 2 there are two extra octets.
+ if (fileType == ICC_USIM_TYPE2_TAG) {
+ GsmPDUHelper.writeHexOctet(pbr.adn.sfi || 0xff);
+ GsmPDUHelper.writeHexOctet(adnRecordId);
+ }
+
+ Buf.writeStringDelimiter(strLen);
+ }.bind(this);
+
+ let callback = (options) => {
+ if (onsuccess) {
+ onsuccess(writtenNumber);
+ }
+ }
+
+ this.context.ICCIOHelper.updateLinearFixedEF({
+ fileId: fileId,
+ recordNumber: recordNumber,
+ dataWriter: dataWriter,
+ callback: callback,
+ onerror: onerror
+ });
+ },
+
+ /**
+ * Cache the possible free record id for all files.
+ */
+ _freeRecordIds: null,
+
+ /**
+ * Find free record id.
+ *
+ * @param fileId EF id.
+ * @param onsuccess Callback to be called when success.
+ * @param onerror Callback to be called when error.
+ */
+ findFreeRecordId: function(fileId, onsuccess, onerror) {
+ let ICCIOHelper = this.context.ICCIOHelper;
+
+ function callback(options) {
+ let Buf = this.context.Buf;
+ let GsmPDUHelper = this.context.GsmPDUHelper;
+
+ let strLen = Buf.readInt32();
+ let octetLen = strLen / 2;
+ let readLen = 0;
+
+ while (readLen < octetLen) {
+ let octet = GsmPDUHelper.readHexOctet();
+ readLen++;
+ if (octet != 0xff) {
+ break;
+ }
+ }
+
+ let nextRecord = (options.p1 % options.totalRecords) + 1;
+
+ if (readLen == octetLen) {
+ // Find free record, assume next record is probably free.
+ this._freeRecordIds[fileId] = nextRecord;
+ if (onsuccess) {
+ onsuccess(options.p1);
+ }
+ return;
+ } else {
+ Buf.seekIncoming((octetLen - readLen) * Buf.PDU_HEX_OCTET_SIZE);
+ }
+
+ Buf.readStringDelimiter(strLen);
+
+ if (nextRecord !== recordNumber) {
+ options.p1 = nextRecord;
+ this.context.RIL.iccIO(options);
+ } else {
+ // No free record found.
+ delete this._freeRecordIds[fileId];
+ if (DEBUG) {
+ this.context.debug(CONTACT_ERR_NO_FREE_RECORD_FOUND);
+ }
+ onerror(CONTACT_ERR_NO_FREE_RECORD_FOUND);
+ }
+ }
+
+ // Start searching free records from the possible one.
+ let recordNumber = this._freeRecordIds[fileId] || 1;
+ ICCIOHelper.loadLinearFixedEF({fileId: fileId,
+ recordNumber: recordNumber,
+ callback: callback.bind(this),
+ onerror: onerror});
+ },
+
+ /**
+ * Read Extension Number from TS 151.011 clause 10.5.10, TS 31.102, clause 4.4.2.4
+ *
+ * @param fileId EF Extension id
+ * @param recordNumber The number of the record shall be loaded.
+ * @param onsuccess Callback to be called when success.
+ * @param onerror Callback to be called when error.
+ */
+ readExtension: function(fileId, recordNumber, onsuccess, onerror) {
+ let callback = (options) => {
+ let Buf = this.context.Buf;
+ let length = Buf.readInt32();
+ let recordType = this.context.GsmPDUHelper.readHexOctet();
+ let number = "";
+
+ // TS 31.102, clause 4.4.2.4 EFEXT1
+ // Case 1, Extension1 record is additional data
+ if (recordType & 0x02) {
+ let numLen = this.context.GsmPDUHelper.readHexOctet();
+ if (numLen != 0xff) {
+ if (numLen > EXT_MAX_BCD_NUMBER_BYTES) {
+ if (DEBUG) {
+ this.context.debug(
+ "Error: invalid length of BCD number/SSC contents - " + numLen);
+ }
+ // +1 to skip Identifier
+ Buf.seekIncoming((EXT_MAX_BCD_NUMBER_BYTES + 1) * Buf.PDU_HEX_OCTET_SIZE);
+ Buf.readStringDelimiter(length);
+ onerror();
+ return;
+ }
+
+ number = this.context.GsmPDUHelper.readSwappedNibbleExtendedBcdString(numLen);
+ if (DEBUG) this.context.debug("Contact Extension Number: "+ number);
+ Buf.seekIncoming((EXT_MAX_BCD_NUMBER_BYTES - numLen) * Buf.PDU_HEX_OCTET_SIZE);
+ } else {
+ Buf.seekIncoming(EXT_MAX_BCD_NUMBER_BYTES * Buf.PDU_HEX_OCTET_SIZE);
+ }
+ } else {
+ // Don't support Case 2, Extension1 record is Called Party Subaddress.
+ // +1 skip numLen
+ Buf.seekIncoming((EXT_MAX_BCD_NUMBER_BYTES + 1) * Buf.PDU_HEX_OCTET_SIZE);
+ }
+
+ // Skip Identifier
+ Buf.seekIncoming(Buf.PDU_HEX_OCTET_SIZE);
+ Buf.readStringDelimiter(length);
+ onsuccess(number);
+ }
+
+ this.context.ICCIOHelper.loadLinearFixedEF({
+ fileId: fileId,
+ recordNumber: recordNumber,
+ callback: callback,
+ onerror: onerror
+ });
+ },
+
+ /**
+ * Update Extension.
+ *
+ * @param fileId EF id of the EXT.
+ * @param recordNumber The number of the record shall be updated.
+ * @param number Dialling Number to be written.
+ * @param onsuccess Callback to be called when success.
+ * @param onerror Callback to be called when error.
+ */
+ updateExtension: function(fileId, recordNumber, number, onsuccess, onerror) {
+ let dataWriter = (recordSize) => {
+ let GsmPDUHelper = this.context.GsmPDUHelper;
+ // Write String length
+ let strLen = recordSize * 2;
+ let Buf = this.context.Buf;
+ Buf.writeInt32(strLen);
+
+ // We don't support extension chain.
+ if (number.length > EXT_MAX_NUMBER_DIGITS) {
+ number = number.substring(0, EXT_MAX_NUMBER_DIGITS);
+ }
+
+ let numLen = Math.ceil(number.length / 2);
+ // Write Extension record
+ GsmPDUHelper.writeHexOctet(0x02);
+ GsmPDUHelper.writeHexOctet(numLen);
+ GsmPDUHelper.writeSwappedNibbleBCD(number);
+ // Write trailing 0xff of Extension data.
+ for (let i = 0; i < EXT_MAX_BCD_NUMBER_BYTES - numLen; i++) {
+ GsmPDUHelper.writeHexOctet(0xff);
+ }
+ // Write trailing 0xff for Identifier.
+ GsmPDUHelper.writeHexOctet(0xff);
+ Buf.writeStringDelimiter(strLen);
+ };
+
+ this.context.ICCIOHelper.updateLinearFixedEF({
+ fileId: fileId,
+ recordNumber: recordNumber,
+ dataWriter: dataWriter,
+ callback: onsuccess,
+ onerror: onerror
+ });
+ },
+
+ /**
+ * Clean an EF record.
+ *
+ * @param fileId EF id.
+ * @param recordNumber The number of the record shall be updated.
+ * @param onsuccess Callback to be called when success.
+ * @param onerror Callback to be called when error.
+ */
+ cleanEFRecord: function(fileId, recordNumber, onsuccess, onerror) {
+ let dataWriter = (recordSize) => {
+ let GsmPDUHelper = this.context.GsmPDUHelper;
+ let Buf = this.context.Buf;
+ // Write String length
+ let strLen = recordSize * 2;
+
+ Buf.writeInt32(strLen);
+ // Write record to 0xff
+ for (let i = 0; i < recordSize; i++) {
+ GsmPDUHelper.writeHexOctet(0xff);
+ }
+ Buf.writeStringDelimiter(strLen);
+ }
+
+ this.context.ICCIOHelper.updateLinearFixedEF({
+ fileId: fileId,
+ recordNumber: recordNumber,
+ dataWriter: dataWriter,
+ callback: onsuccess,
+ onerror: onerror
+ });
+ },
+
+ /**
+ * Get ADNLike extension record number.
+ *
+ * @param fileId EF id of the ADN or FDN.
+ * @param recordNumber EF record id of the ADN or FDN.
+ * @param onsuccess Callback to be called when success.
+ * @param onerror Callback to be called when error.
+ */
+ getADNLikeExtensionRecordNumber: function(fileId, recordNumber, onsuccess, onerror) {
+ let callback = (options) => {
+ let Buf = this.context.Buf;
+ let length = Buf.readInt32();
+
+ // Skip alphaLen, numLen, BCD Number, CCP octets.
+ Buf.seekIncoming((options.recordSize -1) * Buf.PDU_HEX_OCTET_SIZE);
+
+ let extRecordNumber = this.context.GsmPDUHelper.readHexOctet();
+ Buf.readStringDelimiter(length);
+
+ onsuccess(extRecordNumber);
+ }
+
+ this.context.ICCIOHelper.loadLinearFixedEF({
+ fileId: fileId,
+ recordNumber: recordNumber,
+ callback: callback,
+ onerror: onerror
+ });
+ },
+};
+
+/**
+ * Helper for (U)SIM Records.
+ */
+function SimRecordHelperObject(aContext) {
+ this.context = aContext;
+}
+SimRecordHelperObject.prototype = {
+ context: null,
+
+ /**
+ * Fetch (U)SIM records.
+ */
+ fetchSimRecords: function() {
+ this.context.RIL.getIMSI();
+ this.readAD();
+ // CPHS was widely introduced in Europe during GSM(2G) era to provide easier
+ // access to carrier's core service like voicemail, call forwarding, manual
+ // PLMN selection, and etc.
+ // Addition EF like EF_CPHS_MBN, EF_CPHS_CPHS_CFF, EF_CPHS_VWI, etc are
+ // introduced to support these feature.
+ // In USIM, the replancement of these EFs are provided. (EF_MBDN, EF_MWIS, ...)
+ // However, some carriers in Europe still rely on these EFs.
+ this.readCphsInfo(() => this.readSST(),
+ (aErrorMsg) => {
+ this.context.debug("Failed to read CPHS_INFO: " + aErrorMsg);
+ this.readSST();
+ });
+ },
+
+ /**
+ * Read EF_phase.
+ * This EF is only available in SIM.
+ */
+ readSimPhase: function() {
+ function callback() {
+ let Buf = this.context.Buf;
+ let strLen = Buf.readInt32();
+
+ let GsmPDUHelper = this.context.GsmPDUHelper;
+ let phase = GsmPDUHelper.readHexOctet();
+ // If EF_phase is coded '03' or greater, an ME supporting STK shall
+ // perform the PROFILE DOWNLOAD procedure.
+ if (RILQUIRKS_SEND_STK_PROFILE_DOWNLOAD &&
+ phase >= ICC_PHASE_2_PROFILE_DOWNLOAD_REQUIRED) {
+ this.context.RIL.sendStkTerminalProfile(STK_SUPPORTED_TERMINAL_PROFILE);
+ }
+
+ Buf.readStringDelimiter(strLen);
+ }
+
+ this.context.ICCIOHelper.loadTransparentEF({
+ fileId: ICC_EF_PHASE,
+ callback: callback.bind(this)
+ });
+ },
+
+ /**
+ * Read the MSISDN from the (U)SIM.
+ */
+ readMSISDN: function() {
+ function callback(options) {
+ let RIL = this.context.RIL;
+
+ let contact =
+ this.context.ICCPDUHelper.readAlphaIdDiallingNumber(options.recordSize);
+ if (!contact ||
+ (RIL.iccInfo.msisdn !== undefined &&
+ RIL.iccInfo.msisdn === contact.number)) {
+ return;
+ }
+ RIL.iccInfo.msisdn = contact.number;
+ if (DEBUG) this.context.debug("MSISDN: " + RIL.iccInfo.msisdn);
+ this.context.ICCUtilsHelper.handleICCInfoChange();
+ }
+
+ this.context.ICCIOHelper.loadLinearFixedEF({
+ fileId: ICC_EF_MSISDN,
+ callback: callback.bind(this)
+ });
+ },
+
+ /**
+ * Read the AD (Administrative Data) from the (U)SIM.
+ */
+ readAD: function() {
+ function callback() {
+ let Buf = this.context.Buf;
+ let strLen = Buf.readInt32();
+ // Each octet is encoded into two chars.
+ let octetLen = strLen / 2;
+ let ad = this.context.GsmPDUHelper.readHexOctetArray(octetLen);
+ Buf.readStringDelimiter(strLen);
+
+ if (DEBUG) {
+ let str = "";
+ for (let i = 0; i < ad.length; i++) {
+ str += ad[i] + ", ";
+ }
+ this.context.debug("AD: " + str);
+ }
+
+ let ICCUtilsHelper = this.context.ICCUtilsHelper;
+ let RIL = this.context.RIL;
+ // TS 31.102, clause 4.2.18 EFAD
+ let mncLength = 0;
+ if (ad && ad[3]) {
+ mncLength = ad[3] & 0x0f;
+ if (mncLength != 0x02 && mncLength != 0x03) {
+ mncLength = 0;
+ }
+ }
+ // The 4th byte of the response is the length of MNC.
+ let mccMnc = ICCUtilsHelper.parseMccMncFromImsi(RIL.iccInfoPrivate.imsi,
+ mncLength);
+ if (mccMnc) {
+ RIL.iccInfo.mcc = mccMnc.mcc;
+ RIL.iccInfo.mnc = mccMnc.mnc;
+ ICCUtilsHelper.handleICCInfoChange();
+ }
+ }
+
+ this.context.ICCIOHelper.loadTransparentEF({
+ fileId: ICC_EF_AD,
+ callback: callback.bind(this)
+ });
+ },
+
+ /**
+ * Read the SPN (Service Provider Name) from the (U)SIM.
+ */
+ readSPN: function() {
+ function callback() {
+ let Buf = this.context.Buf;
+ let strLen = Buf.readInt32();
+ // Each octet is encoded into two chars.
+ let octetLen = strLen / 2;
+ let spnDisplayCondition = this.context.GsmPDUHelper.readHexOctet();
+ // Minus 1 because the first octet is used to store display condition.
+ let spn = this.context.ICCPDUHelper.readAlphaIdentifier(octetLen - 1);
+ Buf.readStringDelimiter(strLen);
+
+ if (DEBUG) {
+ this.context.debug("SPN: spn = " + spn +
+ ", spnDisplayCondition = " + spnDisplayCondition);
+ }
+
+ let RIL = this.context.RIL;
+ RIL.iccInfoPrivate.spnDisplayCondition = spnDisplayCondition;
+ RIL.iccInfo.spn = spn;
+ let ICCUtilsHelper = this.context.ICCUtilsHelper;
+ ICCUtilsHelper.updateDisplayCondition();
+ ICCUtilsHelper.handleICCInfoChange();
+ }
+
+ this.context.ICCIOHelper.loadTransparentEF({
+ fileId: ICC_EF_SPN,
+ callback: callback.bind(this)
+ });
+ },
+
+ readIMG: function(recordNumber, onsuccess, onerror) {
+ function callback(options) {
+ let RIL = this.context.RIL;
+ let Buf = this.context.Buf;
+ let GsmPDUHelper = this.context.GsmPDUHelper;
+ let strLen = Buf.readInt32();
+ // Each octet is encoded into two chars.
+ let octetLen = strLen / 2;
+
+ let numInstances = GsmPDUHelper.readHexOctet();
+
+ // Data length is defined as 9n+1 or 9n+2. See TS 31.102, sub-clause
+ // 4.6.1.1. However, it's likely to have padding appended so we have a
+ // rather loose check.
+ if (octetLen < (9 * numInstances + 1)) {
+ Buf.seekIncoming((octetLen - 1) * Buf.PDU_HEX_OCTET_SIZE);
+ Buf.readStringDelimiter(strLen);
+ if (onerror) {
+ onerror();
+ }
+ return;
+ }
+
+ let imgDescriptors = [];
+ for (let i = 0; i < numInstances; i++) {
+ imgDescriptors[i] = {
+ width: GsmPDUHelper.readHexOctet(),
+ height: GsmPDUHelper.readHexOctet(),
+ codingScheme: GsmPDUHelper.readHexOctet(),
+ fileId: (GsmPDUHelper.readHexOctet() << 8) |
+ GsmPDUHelper.readHexOctet(),
+ offset: (GsmPDUHelper.readHexOctet() << 8) |
+ GsmPDUHelper.readHexOctet(),
+ dataLen: (GsmPDUHelper.readHexOctet() << 8) |
+ GsmPDUHelper.readHexOctet()
+ };
+ }
+ Buf.seekIncoming((octetLen - 9 * numInstances - 1) * Buf.PDU_HEX_OCTET_SIZE);
+ Buf.readStringDelimiter(strLen);
+
+ let instances = [];
+ let currentInstance = 0;
+ let readNextInstance = (function(img) {
+ instances[currentInstance] = img;
+ currentInstance++;
+
+ if (currentInstance < numInstances) {
+ let imgDescriptor = imgDescriptors[currentInstance];
+ this.readIIDF(imgDescriptor.fileId,
+ imgDescriptor.offset,
+ imgDescriptor.dataLen,
+ imgDescriptor.codingScheme,
+ readNextInstance,
+ onerror);
+ } else {
+ if (onsuccess) {
+ onsuccess(instances);
+ }
+ }
+ }).bind(this);
+
+ this.readIIDF(imgDescriptors[0].fileId,
+ imgDescriptors[0].offset,
+ imgDescriptors[0].dataLen,
+ imgDescriptors[0].codingScheme,
+ readNextInstance,
+ onerror);
+ }
+
+ this.context.ICCIOHelper.loadLinearFixedEF({
+ fileId: ICC_EF_IMG,
+ recordNumber: recordNumber,
+ callback: callback.bind(this),
+ onerror: onerror
+ });
+ },
+
+ readIIDF: function(fileId, offset, dataLen, codingScheme, onsuccess, onerror) {
+ // Valid fileId is '4FXX', see TS 31.102, clause 4.6.1.2.
+ if ((fileId >> 8) != 0x4F) {
+ if (onerror) {
+ onerror();
+ }
+ return;
+ }
+
+ function callback() {
+ let Buf = this.context.Buf;
+ let RIL = this.context.RIL;
+ let GsmPDUHelper = this.context.GsmPDUHelper;
+ let strLen = Buf.readInt32();
+ // Each octet is encoded into two chars.
+ let octetLen = strLen / 2;
+
+ if (octetLen < offset + dataLen) {
+ // Data length is not enough. See TS 31.102, clause 4.6.1.1, the
+ // paragraph "Bytes 8 and 9: Length of Image Instance Data."
+ Buf.seekIncoming(octetLen * Buf.PDU_HEX_OCTET_SIZE);
+ Buf.readStringDelimiter(strLen);
+ if (onerror) {
+ onerror();
+ }
+ return;
+ }
+
+ Buf.seekIncoming(offset * Buf.PDU_HEX_OCTET_SIZE);
+
+ let rawData = {
+ width: GsmPDUHelper.readHexOctet(),
+ height: GsmPDUHelper.readHexOctet(),
+ codingScheme: codingScheme
+ };
+
+ switch (codingScheme) {
+ case ICC_IMG_CODING_SCHEME_BASIC:
+ rawData.body = GsmPDUHelper.readHexOctetArray(
+ dataLen - ICC_IMG_HEADER_SIZE_BASIC);
+ Buf.seekIncoming((octetLen - offset - dataLen) * Buf.PDU_HEX_OCTET_SIZE);
+ break;
+
+ case ICC_IMG_CODING_SCHEME_COLOR:
+ case ICC_IMG_CODING_SCHEME_COLOR_TRANSPARENCY:
+ rawData.bitsPerImgPoint = GsmPDUHelper.readHexOctet();
+ let num = GsmPDUHelper.readHexOctet();
+ // The value 0 shall be interpreted as 256. See TS 31.102, Annex B.2.
+ rawData.numOfClutEntries = (num === 0) ? 0x100 : num;
+ rawData.clutOffset = (GsmPDUHelper.readHexOctet() << 8) |
+ GsmPDUHelper.readHexOctet();
+ rawData.body = GsmPDUHelper.readHexOctetArray(
+ dataLen - ICC_IMG_HEADER_SIZE_COLOR);
+
+ Buf.seekIncoming((rawData.clutOffset - offset - dataLen) *
+ Buf.PDU_HEX_OCTET_SIZE);
+ let clut = GsmPDUHelper.readHexOctetArray(rawData.numOfClutEntries *
+ ICC_CLUT_ENTRY_SIZE);
+
+ rawData.clut = clut;
+ }
+
+ Buf.readStringDelimiter(strLen);
+
+ if (onsuccess) {
+ onsuccess(rawData);
+ }
+ }
+
+ this.context.ICCIOHelper.loadTransparentEF({
+ fileId: fileId,
+ pathId: this.context.ICCFileHelper.getEFPath(ICC_EF_IMG),
+ callback: callback.bind(this),
+ onerror: onerror
+ });
+ },
+
+ /**
+ * Read the (U)SIM Service Table from the (U)SIM.
+ */
+ readSST: function() {
+ function callback() {
+ let Buf = this.context.Buf;
+ let RIL = this.context.RIL;
+
+ let strLen = Buf.readInt32();
+ // Each octet is encoded into two chars.
+ let octetLen = strLen / 2;
+ let sst = this.context.GsmPDUHelper.readHexOctetArray(octetLen);
+ Buf.readStringDelimiter(strLen);
+ RIL.iccInfoPrivate.sst = sst;
+ if (DEBUG) {
+ let str = "";
+ for (let i = 0; i < sst.length; i++) {
+ str += sst[i] + ", ";
+ }
+ this.context.debug("SST: " + str);
+ }
+
+ let ICCUtilsHelper = this.context.ICCUtilsHelper;
+ if (ICCUtilsHelper.isICCServiceAvailable("MSISDN")) {
+ if (DEBUG) this.context.debug("MSISDN: MSISDN is available");
+ this.readMSISDN();
+ } else {
+ if (DEBUG) this.context.debug("MSISDN: MSISDN service is not available");
+ }
+
+ // Fetch SPN and PLMN list, if some of them are available.
+ if (ICCUtilsHelper.isICCServiceAvailable("SPN")) {
+ if (DEBUG) this.context.debug("SPN: SPN is available");
+ this.readSPN();
+ } else {
+ if (DEBUG) this.context.debug("SPN: SPN service is not available");
+ }
+
+ if (ICCUtilsHelper.isICCServiceAvailable("MDN")) {
+ if (DEBUG) this.context.debug("MDN: MDN available.");
+ this.readMBDN();
+ } else {
+ if (DEBUG) this.context.debug("MDN: MDN service is not available");
+
+ if (ICCUtilsHelper.isCphsServiceAvailable("MBN")) {
+ // read CPHS_MBN in advance if MBDN is not available.
+ this.readCphsMBN();
+ } else {
+ if (DEBUG) this.context.debug("CPHS_MBN: CPHS_MBN service is not available");
+ }
+ }
+
+ if (ICCUtilsHelper.isICCServiceAvailable("MWIS")) {
+ if (DEBUG) this.context.debug("MWIS: MWIS is available");
+ this.readMWIS();
+ } else {
+ if (DEBUG) this.context.debug("MWIS: MWIS is not available");
+ }
+
+ if (ICCUtilsHelper.isICCServiceAvailable("SPDI")) {
+ if (DEBUG) this.context.debug("SPDI: SPDI available.");
+ this.readSPDI();
+ } else {
+ if (DEBUG) this.context.debug("SPDI: SPDI service is not available");
+ }
+
+ if (ICCUtilsHelper.isICCServiceAvailable("PNN")) {
+ if (DEBUG) this.context.debug("PNN: PNN is available");
+ this.readPNN();
+ } else {
+ if (DEBUG) this.context.debug("PNN: PNN is not available");
+ }
+
+ if (ICCUtilsHelper.isICCServiceAvailable("OPL")) {
+ if (DEBUG) this.context.debug("OPL: OPL is available");
+ this.readOPL();
+ } else {
+ if (DEBUG) this.context.debug("OPL: OPL is not available");
+ }
+
+ if (ICCUtilsHelper.isICCServiceAvailable("GID1")) {
+ if (DEBUG) this.context.debug("GID1: GID1 is available");
+ this.readGID1();
+ } else {
+ if (DEBUG) this.context.debug("GID1: GID1 is not available");
+ }
+
+ if (ICCUtilsHelper.isICCServiceAvailable("CBMI")) {
+ this.readCBMI();
+ } else {
+ RIL.cellBroadcastConfigs.CBMI = null;
+ }
+ if (ICCUtilsHelper.isICCServiceAvailable("DATA_DOWNLOAD_SMS_CB")) {
+ this.readCBMID();
+ } else {
+ RIL.cellBroadcastConfigs.CBMID = null;
+ }
+ if (ICCUtilsHelper.isICCServiceAvailable("CBMIR")) {
+ this.readCBMIR();
+ } else {
+ RIL.cellBroadcastConfigs.CBMIR = null;
+ }
+ RIL._mergeAllCellBroadcastConfigs();
+ }
+
+ // ICC_EF_UST has the same value with ICC_EF_SST.
+ this.context.ICCIOHelper.loadTransparentEF({
+ fileId: ICC_EF_SST,
+ callback: callback.bind(this)
+ });
+ },
+
+ /**
+ * Read (U)SIM MBDN. (Mailbox Dialling Number)
+ *
+ * @see TS 131.102, clause 4.2.60
+ */
+ readMBDN: function() {
+ function callback(options) {
+ let RIL = this.context.RIL;
+ let contact =
+ this.context.ICCPDUHelper.readAlphaIdDiallingNumber(options.recordSize);
+ if ((!contact ||
+ ((!contact.alphaId || contact.alphaId == "") &&
+ (!contact.number || contact.number == ""))) &&
+ this.context.ICCUtilsHelper.isCphsServiceAvailable("MBN")) {
+ // read CPHS_MBN in advance if MBDN is invalid or empty.
+ this.readCphsMBN();
+ return;
+ }
+
+ if (!contact ||
+ (RIL.iccInfoPrivate.mbdn !== undefined &&
+ RIL.iccInfoPrivate.mbdn === contact.number)) {
+ return;
+ }
+ RIL.iccInfoPrivate.mbdn = contact.number;
+ if (DEBUG) {
+ this.context.debug("MBDN, alphaId=" + contact.alphaId +
+ " number=" + contact.number);
+ }
+ contact.rilMessageType = "iccmbdn";
+ RIL.sendChromeMessage(contact);
+ }
+
+ this.context.ICCIOHelper.loadLinearFixedEF({
+ fileId: ICC_EF_MBDN,
+ callback: callback.bind(this)
+ });
+ },
+
+ /**
+ * Read ICC MWIS. (Message Waiting Indication Status)
+ *
+ * @see TS 31.102, clause 4.2.63 for USIM and TS 51.011, clause 10.3.45 for SIM.
+ */
+ readMWIS: function() {
+ function callback(options) {
+ let Buf = this.context.Buf;
+ let RIL = this.context.RIL;
+
+ let strLen = Buf.readInt32();
+ // Each octet is encoded into two chars.
+ let octetLen = strLen / 2;
+ let mwis = this.context.GsmPDUHelper.readHexOctetArray(octetLen);
+ Buf.readStringDelimiter(strLen);
+ if (!mwis) {
+ return;
+ }
+ RIL.iccInfoPrivate.mwis = mwis; //Keep raw MWIS for updateMWIS()
+
+ let mwi = {};
+ // b8 b7 B6 b5 b4 b3 b2 b1 4.2.63, TS 31.102 version 11.6.0
+ // | | | | | | | |__ Voicemail
+ // | | | | | | |_____ Fax
+ // | | | | | |________ Electronic Mail
+ // | | | | |___________ Other
+ // | | | |______________ Videomail
+ // |__|__|_________________ RFU
+ mwi.active = ((mwis[0] & 0x01) != 0);
+
+ if (mwi.active) {
+ // In TS 23.040 msgCount is in the range from 0 to 255.
+ // The value 255 shall be taken to mean 255 or greater.
+ //
+ // However, There is no definition about 0 when MWI is active.
+ //
+ // Normally, when mwi is active, the msgCount must be larger than 0.
+ // Refer to other reference phone,
+ // 0 is usually treated as UNKNOWN for storing 2nd level MWI status (DCS).
+ mwi.msgCount = (mwis[1] === 0) ? GECKO_VOICEMAIL_MESSAGE_COUNT_UNKNOWN
+ : mwis[1];
+ } else {
+ mwi.msgCount = 0;
+ }
+
+ RIL.sendChromeMessage({ rilMessageType: "iccmwis",
+ mwi: mwi });
+ }
+
+ this.context.ICCIOHelper.loadLinearFixedEF({
+ fileId: ICC_EF_MWIS,
+ recordNumber: 1, // Get 1st Subscriber Profile.
+ callback: callback.bind(this)
+ });
+ },
+
+ /**
+ * Update ICC MWIS. (Message Waiting Indication Status)
+ *
+ * @see TS 31.102, clause 4.2.63 for USIM and TS 51.011, clause 10.3.45 for SIM.
+ */
+ updateMWIS: function(mwi) {
+ let RIL = this.context.RIL;
+ if (!RIL.iccInfoPrivate.mwis) {
+ return;
+ }
+
+ function dataWriter(recordSize) {
+ let mwis = RIL.iccInfoPrivate.mwis;
+
+ let msgCount =
+ (mwi.msgCount === GECKO_VOICEMAIL_MESSAGE_COUNT_UNKNOWN) ? 0 : mwi.msgCount;
+
+ [mwis[0], mwis[1]] = (mwi.active) ? [(mwis[0] | 0x01), msgCount]
+ : [(mwis[0] & 0xFE), 0];
+
+ let strLen = recordSize * 2;
+ let Buf = this.context.Buf;
+ Buf.writeInt32(strLen);
+
+ let GsmPDUHelper = this.context.GsmPDUHelper;
+ for (let i = 0; i < mwis.length; i++) {
+ GsmPDUHelper.writeHexOctet(mwis[i]);
+ }
+
+ Buf.writeStringDelimiter(strLen);
+ }
+
+ this.context.ICCIOHelper.updateLinearFixedEF({
+ fileId: ICC_EF_MWIS,
+ recordNumber: 1, // Update 1st Subscriber Profile.
+ dataWriter: dataWriter.bind(this)
+ });
+ },
+
+ /**
+ * Read the SPDI (Service Provider Display Information) from the (U)SIM.
+ *
+ * See TS 131.102 section 4.2.66 for USIM and TS 51.011 section 10.3.50
+ * for SIM.
+ */
+ readSPDI: function() {
+ function callback() {
+ let Buf = this.context.Buf;
+ let strLen = Buf.readInt32();
+ let octetLen = strLen / 2;
+ let readLen = 0;
+ let endLoop = false;
+
+ let RIL = this.context.RIL;
+ RIL.iccInfoPrivate.SPDI = null;
+
+ let GsmPDUHelper = this.context.GsmPDUHelper;
+ while ((readLen < octetLen) && !endLoop) {
+ let tlvTag = GsmPDUHelper.readHexOctet();
+ let tlvLen = GsmPDUHelper.readHexOctet();
+ readLen += 2; // For tag and length fields.
+ switch (tlvTag) {
+ case SPDI_TAG_SPDI:
+ // The value part itself is a TLV.
+ continue;
+ case SPDI_TAG_PLMN_LIST:
+ // This PLMN list is what we want.
+ RIL.iccInfoPrivate.SPDI = this.readPLMNEntries(tlvLen / 3);
+ readLen += tlvLen;
+ endLoop = true;
+ break;
+ default:
+ // We don't care about its content if its tag is not SPDI nor
+ // PLMN_LIST.
+ endLoop = true;
+ break;
+ }
+ }
+
+ // Consume unread octets.
+ Buf.seekIncoming((octetLen - readLen) * Buf.PDU_HEX_OCTET_SIZE);
+ Buf.readStringDelimiter(strLen);
+
+ if (DEBUG) {
+ this.context.debug("SPDI: " + JSON.stringify(RIL.iccInfoPrivate.SPDI));
+ }
+ let ICCUtilsHelper = this.context.ICCUtilsHelper;
+ if (ICCUtilsHelper.updateDisplayCondition()) {
+ ICCUtilsHelper.handleICCInfoChange();
+ }
+ }
+
+ // PLMN List is Servive 51 in USIM, EF_SPDI
+ this.context.ICCIOHelper.loadTransparentEF({
+ fileId: ICC_EF_SPDI,
+ callback: callback.bind(this)
+ });
+ },
+
+ _readCbmiHelper: function(which) {
+ let RIL = this.context.RIL;
+
+ function callback() {
+ let Buf = this.context.Buf;
+ let strLength = Buf.readInt32();
+
+ // Each Message Identifier takes two octets and each octet is encoded
+ // into two chars.
+ let numIds = strLength / 4, list = null;
+ if (numIds) {
+ list = [];
+ let GsmPDUHelper = this.context.GsmPDUHelper;
+ for (let i = 0, id; i < numIds; i++) {
+ id = GsmPDUHelper.readHexOctet() << 8 | GsmPDUHelper.readHexOctet();
+ // `Unused entries shall be set to 'FF FF'.`
+ if (id != 0xFFFF) {
+ list.push(id);
+ list.push(id + 1);
+ }
+ }
+ }
+ if (DEBUG) {
+ this.context.debug(which + ": " + JSON.stringify(list));
+ }
+
+ Buf.readStringDelimiter(strLength);
+
+ RIL.cellBroadcastConfigs[which] = list;
+ RIL._mergeAllCellBroadcastConfigs();
+ }
+
+ function onerror() {
+ RIL.cellBroadcastConfigs[which] = null;
+ RIL._mergeAllCellBroadcastConfigs();
+ }
+
+ let fileId = GLOBAL["ICC_EF_" + which];
+ this.context.ICCIOHelper.loadTransparentEF({
+ fileId: fileId,
+ callback: callback.bind(this),
+ onerror: onerror.bind(this)
+ });
+ },
+
+ /**
+ * Read EFcbmi (Cell Broadcast Message Identifier selection)
+ *
+ * @see 3GPP TS 31.102 v110.02.0 section 4.2.14 EFcbmi
+ * @see 3GPP TS 51.011 v5.0.0 section 10.3.13 EFcbmi
+ */
+ readCBMI: function() {
+ this._readCbmiHelper("CBMI");
+ },
+
+ /**
+ * Read EFcbmid (Cell Broadcast Message Identifier for Data Download)
+ *
+ * @see 3GPP TS 31.102 v110.02.0 section 4.2.20 EFcbmid
+ * @see 3GPP TS 51.011 v5.0.0 section 10.3.26 EFcbmid
+ */
+ readCBMID: function() {
+ this._readCbmiHelper("CBMID");
+ },
+
+ /**
+ * Read EFcbmir (Cell Broadcast Message Identifier Range selection)
+ *
+ * @see 3GPP TS 31.102 v110.02.0 section 4.2.22 EFcbmir
+ * @see 3GPP TS 51.011 v5.0.0 section 10.3.28 EFcbmir
+ */
+ readCBMIR: function() {
+ let RIL = this.context.RIL;
+
+ function callback() {
+ let Buf = this.context.Buf;
+ let strLength = Buf.readInt32();
+
+ // Each Message Identifier range takes four octets and each octet is
+ // encoded into two chars.
+ let numIds = strLength / 8, list = null;
+ if (numIds) {
+ list = [];
+ let GsmPDUHelper = this.context.GsmPDUHelper;
+ for (let i = 0, from, to; i < numIds; i++) {
+ // `Bytes one and two of each range identifier equal the lower value
+ // of a cell broadcast range, bytes three and four equal the upper
+ // value of a cell broadcast range.`
+ from = GsmPDUHelper.readHexOctet() << 8 | GsmPDUHelper.readHexOctet();
+ to = GsmPDUHelper.readHexOctet() << 8 | GsmPDUHelper.readHexOctet();
+ // `Unused entries shall be set to 'FF FF'.`
+ if ((from != 0xFFFF) && (to != 0xFFFF)) {
+ list.push(from);
+ list.push(to + 1);
+ }
+ }
+ }
+ if (DEBUG) {
+ this.context.debug("CBMIR: " + JSON.stringify(list));
+ }
+
+ Buf.readStringDelimiter(strLength);
+
+ RIL.cellBroadcastConfigs.CBMIR = list;
+ RIL._mergeAllCellBroadcastConfigs();
+ }
+
+ function onerror() {
+ RIL.cellBroadcastConfigs.CBMIR = null;
+ RIL._mergeAllCellBroadcastConfigs();
+ }
+
+ this.context.ICCIOHelper.loadTransparentEF({
+ fileId: ICC_EF_CBMIR,
+ callback: callback.bind(this),
+ onerror: onerror.bind(this)
+ });
+ },
+
+ /**
+ * Read OPL (Operator PLMN List) from (U)SIM.
+ *
+ * See 3GPP TS 31.102 Sec. 4.2.59 for USIM
+ * 3GPP TS 51.011 Sec. 10.3.42 for SIM.
+ */
+ readOPL: function() {
+ let ICCIOHelper = this.context.ICCIOHelper;
+ let opl = [];
+ function callback(options) {
+ let Buf = this.context.Buf;
+ let GsmPDUHelper = this.context.GsmPDUHelper;
+
+ let strLen = Buf.readInt32();
+ // The first 7 bytes are LAI (for UMTS) and the format of LAI is defined
+ // in 3GPP TS 23.003, Sec 4.1
+ // +-------------+---------+
+ // | Octet 1 - 3 | MCC/MNC |
+ // +-------------+---------+
+ // | Octet 4 - 7 | LAC |
+ // +-------------+---------+
+ let mccMnc = [GsmPDUHelper.readHexOctet(),
+ GsmPDUHelper.readHexOctet(),
+ GsmPDUHelper.readHexOctet()];
+ if (mccMnc[0] != 0xFF || mccMnc[1] != 0xFF || mccMnc[2] != 0xFF) {
+ let oplElement = {};
+ let semiOctets = [];
+ for (let i = 0; i < mccMnc.length; i++) {
+ semiOctets.push((mccMnc[i] & 0xf0) >> 4);
+ semiOctets.push(mccMnc[i] & 0x0f);
+ }
+ let reformat = [semiOctets[1], semiOctets[0], semiOctets[3],
+ semiOctets[5], semiOctets[4], semiOctets[2]];
+ let buf = "";
+ for (let i = 0; i < reformat.length; i++) {
+ if (reformat[i] != 0xF) {
+ buf += GsmPDUHelper.semiOctetToExtendedBcdChar(reformat[i]);
+ }
+ if (i === 2) {
+ // 0-2: MCC
+ oplElement.mcc = buf;
+ buf = "";
+ } else if (i === 5) {
+ // 3-5: MNC
+ oplElement.mnc = buf;
+ }
+ }
+ // LAC/TAC
+ oplElement.lacTacStart =
+ (GsmPDUHelper.readHexOctet() << 8) | GsmPDUHelper.readHexOctet();
+ oplElement.lacTacEnd =
+ (GsmPDUHelper.readHexOctet() << 8) | GsmPDUHelper.readHexOctet();
+ // PLMN Network Name Record Identifier
+ oplElement.pnnRecordId = GsmPDUHelper.readHexOctet();
+ if (DEBUG) {
+ this.context.debug("OPL: [" + (opl.length + 1) + "]: " +
+ JSON.stringify(oplElement));
+ }
+ opl.push(oplElement);
+ } else {
+ Buf.seekIncoming(5 * Buf.PDU_HEX_OCTET_SIZE);
+ }
+ Buf.readStringDelimiter(strLen);
+
+ let RIL = this.context.RIL;
+ if (options.p1 < options.totalRecords) {
+ ICCIOHelper.loadNextRecord(options);
+ } else {
+ RIL.iccInfoPrivate.OPL = opl;
+ RIL.overrideICCNetworkName();
+ }
+ }
+
+ ICCIOHelper.loadLinearFixedEF({fileId: ICC_EF_OPL,
+ callback: callback.bind(this)});
+ },
+
+ /**
+ * Read PNN (PLMN Network Name) from (U)SIM.
+ *
+ * See 3GPP TS 31.102 Sec. 4.2.58 for USIM
+ * 3GPP TS 51.011 Sec. 10.3.41 for SIM.
+ */
+ readPNN: function() {
+ let ICCIOHelper = this.context.ICCIOHelper;
+ function callback(options) {
+ let pnnElement;
+ let Buf = this.context.Buf;
+ let strLen = Buf.readInt32();
+ let octetLen = strLen / 2;
+ let readLen = 0;
+
+ let GsmPDUHelper = this.context.GsmPDUHelper;
+ while (readLen < octetLen) {
+ let tlvTag = GsmPDUHelper.readHexOctet();
+
+ if (tlvTag == 0xFF) {
+ // Unused byte
+ readLen++;
+ Buf.seekIncoming((octetLen - readLen) * Buf.PDU_HEX_OCTET_SIZE);
+ break;
+ }
+
+ // Needs this check to avoid initializing twice.
+ pnnElement = pnnElement || {};
+
+ let tlvLen = GsmPDUHelper.readHexOctet();
+
+ switch (tlvTag) {
+ case PNN_IEI_FULL_NETWORK_NAME:
+ pnnElement.fullName = GsmPDUHelper.readNetworkName(tlvLen);
+ break;
+ case PNN_IEI_SHORT_NETWORK_NAME:
+ pnnElement.shortName = GsmPDUHelper.readNetworkName(tlvLen);
+ break;
+ default:
+ Buf.seekIncoming(tlvLen * Buf.PDU_HEX_OCTET_SIZE);
+ break;
+ }
+
+ readLen += (tlvLen + 2); // +2 for tlvTag and tlvLen
+ }
+ Buf.readStringDelimiter(strLen);
+
+ pnn.push(pnnElement);
+
+ let RIL = this.context.RIL;
+ if (options.p1 < options.totalRecords) {
+ ICCIOHelper.loadNextRecord(options);
+ } else {
+ if (DEBUG) {
+ for (let i = 0; i < pnn.length; i++) {
+ this.context.debug("PNN: [" + i + "]: " + JSON.stringify(pnn[i]));
+ }
+ }
+ RIL.iccInfoPrivate.PNN = pnn;
+ RIL.overrideICCNetworkName();
+ }
+ }
+
+ let pnn = [];
+ ICCIOHelper.loadLinearFixedEF({fileId: ICC_EF_PNN,
+ callback: callback.bind(this)});
+ },
+
+ /**
+ * Read the list of PLMN (Public Land Mobile Network) entries
+ * We cannot directly rely on readSwappedNibbleBcdToString(),
+ * since it will no correctly handle some corner-cases that are
+ * not a problem in our case (0xFF 0xFF 0xFF).
+ *
+ * @param length The number of PLMN records.
+ * @return An array of string corresponding to the PLMNs.
+ */
+ readPLMNEntries: function(length) {
+ let plmnList = [];
+ // Each PLMN entry has 3 bytes.
+ if (DEBUG) {
+ this.context.debug("PLMN entries length = " + length);
+ }
+ let GsmPDUHelper = this.context.GsmPDUHelper;
+ let index = 0;
+ while (index < length) {
+ // Unused entries will be 0xFFFFFF, according to EF_SPDI
+ // specs (TS 131 102, section 4.2.66)
+ try {
+ let plmn = [GsmPDUHelper.readHexOctet(),
+ GsmPDUHelper.readHexOctet(),
+ GsmPDUHelper.readHexOctet()];
+ if (DEBUG) {
+ this.context.debug("Reading PLMN entry: [" + index + "]: '" + plmn + "'");
+ }
+ if (plmn[0] != 0xFF &&
+ plmn[1] != 0xFF &&
+ plmn[2] != 0xFF) {
+ let semiOctets = [];
+ for (let idx = 0; idx < plmn.length; idx++) {
+ semiOctets.push((plmn[idx] & 0xF0) >> 4);
+ semiOctets.push(plmn[idx] & 0x0F);
+ }
+
+ // According to TS 24.301, 9.9.3.12, the semi octets is arranged
+ // in format:
+ // Byte 1: MCC[2] | MCC[1]
+ // Byte 2: MNC[3] | MCC[3]
+ // Byte 3: MNC[2] | MNC[1]
+ // Therefore, we need to rearrange them.
+ let reformat = [semiOctets[1], semiOctets[0], semiOctets[3],
+ semiOctets[5], semiOctets[4], semiOctets[2]];
+ let buf = "";
+ let plmnEntry = {};
+ for (let i = 0; i < reformat.length; i++) {
+ if (reformat[i] != 0xF) {
+ buf += GsmPDUHelper.semiOctetToExtendedBcdChar(reformat[i]);
+ }
+ if (i === 2) {
+ // 0-2: MCC
+ plmnEntry.mcc = buf;
+ buf = "";
+ } else if (i === 5) {
+ // 3-5: MNC
+ plmnEntry.mnc = buf;
+ }
+ }
+ if (DEBUG) {
+ this.context.debug("PLMN = " + plmnEntry.mcc + ", " + plmnEntry.mnc);
+ }
+ plmnList.push(plmnEntry);
+ }
+ } catch (e) {
+ if (DEBUG) {
+ this.context.debug("PLMN entry " + index + " is invalid.");
+ }
+ break;
+ }
+ index ++;
+ }
+ return plmnList;
+ },
+
+ /**
+ * Read the SMS from the ICC.
+ *
+ * @param recordNumber The number of the record shall be loaded.
+ * @param onsuccess Callback to be called when success.
+ * @param onerror Callback to be called when error.
+ */
+ readSMS: function(recordNumber, onsuccess, onerror) {
+ function callback(options) {
+ let Buf = this.context.Buf;
+ let strLen = Buf.readInt32();
+
+ // TS 51.011, 10.5.3 EF_SMS
+ // b3 b2 b1
+ // 0 0 1 message received by MS from network; message read
+ // 0 1 1 message received by MS from network; message to be read
+ // 1 1 1 MS originating message; message to be sent
+ // 1 0 1 MS originating message; message sent to the network:
+ let GsmPDUHelper = this.context.GsmPDUHelper;
+ let status = GsmPDUHelper.readHexOctet();
+
+ let message = GsmPDUHelper.readMessage();
+ message.simStatus = status;
+
+ // Consumes the remaining buffer
+ Buf.seekIncoming(Buf.getReadAvailable() - Buf.PDU_HEX_OCTET_SIZE);
+
+ Buf.readStringDelimiter(strLen);
+
+ if (message) {
+ onsuccess(message);
+ } else {
+ onerror("Failed to decode SMS on SIM #" + recordNumber);
+ }
+ }
+
+ this.context.ICCIOHelper.loadLinearFixedEF({
+ fileId: ICC_EF_SMS,
+ recordNumber: recordNumber,
+ callback: callback.bind(this),
+ onerror: onerror
+ });
+ },
+
+ readGID1: function() {
+ function callback() {
+ let Buf = this.context.Buf;
+ let RIL = this.context.RIL;
+
+ RIL.iccInfoPrivate.gid1 = Buf.readString();
+ if (DEBUG) {
+ this.context.debug("GID1: " + RIL.iccInfoPrivate.gid1);
+ }
+ }
+
+ this.context.ICCIOHelper.loadTransparentEF({
+ fileId: ICC_EF_GID1,
+ callback: callback.bind(this)
+ });
+ },
+
+ /**
+ * Read CPHS Phase & Service Table from CPHS Info.
+ *
+ * @See B.3.1.1 CPHS Information in CPHS Phase 2.
+ *
+ * @param onsuccess Callback to be called when success.
+ * @param onerror Callback to be called when error.
+ */
+ readCphsInfo: function(onsuccess, onerror) {
+ function callback() {
+ try {
+ let Buf = this.context.Buf;
+ let RIL = this.context.RIL;
+
+ let strLen = Buf.readInt32();
+ // Each octet is encoded into two chars.
+ let octetLen = strLen / 2;
+ let cphsInfo = this.context.GsmPDUHelper.readHexOctetArray(octetLen);
+ Buf.readStringDelimiter(strLen);
+ if (DEBUG) {
+ let str = "";
+ for (let i = 0; i < cphsInfo.length; i++) {
+ str += cphsInfo[i] + ", ";
+ }
+ this.context.debug("CPHS INFO: " + str);
+ }
+
+ /**
+ * CPHS INFORMATION
+ *
+ * Byte 1: CPHS Phase
+ * 01 phase 1
+ * 02 phase 2
+ * etc.
+ *
+ * Byte 2: CPHS Service Table
+ * +----+----+----+----+----+----+----+----+
+ * | b8 | b7 | b6 | b5 | b4 | b3 | b2 | b1 |
+ * +----+----+----+----+----+----+----+----+
+ * | ONSF | MBN | SST | CSP |
+ * | Phase 2 | ALL | Phase 1 | All |
+ * +----+----+----+----+----+----+----+----+
+ *
+ * Byte 3: CPHS Service Table continued
+ * +----+----+----+----+----+----+----+----+
+ * | b8 | b7 | b6 | b5 | b4 | b3 | b2 | b1 |
+ * +----+----+----+----+----+----+----+----+
+ * | RFU | RFU | RFU | INFO_NUM|
+ * | | | | Phase 2 |
+ * +----+----+----+----+----+----+----+----+
+ */
+ let cphsPhase = cphsInfo[0];
+ if (cphsPhase == 1) {
+ // Clear 'Phase 2 only' services.
+ cphsInfo[1] &= 0x3F;
+ // We don't know whether Byte 3 is available in CPHS phase 1 or not.
+ // Add boundary check before accessing it.
+ if (cphsInfo.length > 2) {
+ cphsInfo[2] = 0x00;
+ }
+ } else if (cphsPhase == 2) {
+ // Clear 'Phase 1 only' services.
+ cphsInfo[1] &= 0xF3;
+ } else {
+ throw new Error("Unknown CPHS phase: " + cphsPhase);
+ }
+
+ RIL.iccInfoPrivate.cphsSt = cphsInfo.subarray(1);
+ onsuccess();
+ } catch(e) {
+ onerror(e.toString());
+ }
+ }
+
+ this.context.ICCIOHelper.loadTransparentEF({
+ fileId: ICC_EF_CPHS_INFO,
+ callback: callback.bind(this),
+ onerror: onerror
+ });
+ },
+
+ /**
+ * Read CPHS MBN. (Mailbox Numbers)
+ *
+ * @See B.4.2.2 Voice Message Retrieval and Indicator Clearing
+ */
+ readCphsMBN: function() {
+ function callback(options) {
+ let RIL = this.context.RIL;
+ let contact =
+ this.context.ICCPDUHelper.readAlphaIdDiallingNumber(options.recordSize);
+ if (!contact ||
+ (RIL.iccInfoPrivate.mbdn !== undefined &&
+ RIL.iccInfoPrivate.mbdn === contact.number)) {
+ return;
+ }
+ RIL.iccInfoPrivate.mbdn = contact.number;
+ if (DEBUG) {
+ this.context.debug("CPHS_MDN, alphaId=" + contact.alphaId +
+ " number=" + contact.number);
+ }
+ contact.rilMessageType = "iccmbdn";
+ RIL.sendChromeMessage(contact);
+ }
+
+ this.context.ICCIOHelper.loadLinearFixedEF({
+ fileId: ICC_EF_CPHS_MBN,
+ callback: callback.bind(this)
+ });
+ }
+};
+
+function RuimRecordHelperObject(aContext) {
+ this.context = aContext;
+}
+RuimRecordHelperObject.prototype = {
+ context: null,
+
+ fetchRuimRecords: function() {
+ this.getIMSI_M();
+ this.readCST();
+ this.readCDMAHome();
+ this.context.RIL.getCdmaSubscription();
+ },
+
+ /**
+ * Get IMSI_M from CSIM/RUIM.
+ * See 3GPP2 C.S0065 Sec. 5.2.2
+ */
+ getIMSI_M: function() {
+ function callback() {
+ let Buf = this.context.Buf;
+ let strLen = Buf.readInt32();
+ let encodedImsi = this.context.GsmPDUHelper.readHexOctetArray(strLen / 2);
+ Buf.readStringDelimiter(strLen);
+
+ if ((encodedImsi[CSIM_IMSI_M_PROGRAMMED_BYTE] & 0x80)) { // IMSI_M programmed
+ let RIL = this.context.RIL;
+ RIL.iccInfoPrivate.imsi = this.decodeIMSI(encodedImsi);
+ RIL.sendChromeMessage({rilMessageType: "iccimsi",
+ imsi: RIL.iccInfoPrivate.imsi});
+
+ let ICCUtilsHelper = this.context.ICCUtilsHelper;
+ let mccMnc = ICCUtilsHelper.parseMccMncFromImsi(RIL.iccInfoPrivate.imsi);
+ if (mccMnc) {
+ RIL.iccInfo.mcc = mccMnc.mcc;
+ RIL.iccInfo.mnc = mccMnc.mnc;
+ ICCUtilsHelper.handleICCInfoChange();
+ }
+ }
+ }
+
+ this.context.ICCIOHelper.loadTransparentEF({
+ fileId: ICC_EF_CSIM_IMSI_M,
+ callback: callback.bind(this)
+ });
+ },
+
+ /**
+ * Decode IMSI from IMSI_M
+ * See 3GPP2 C.S0005 Sec. 2.3.1
+ * +---+---------+------------+---+--------+---------+---+---------+--------+
+ * |RFU| MCC | programmed |RFU| MNC | MIN1 |RFU| MIN2 | CLASS |
+ * +---+---------+------------+---+--------+---------+---+---------+--------+
+ * | 6 | 10 bits | 8 bits | 1 | 7 bits | 24 bits | 6 | 10 bits | 8 bits |
+ * +---+---------+------------+---+--------+---------+---+---------+--------+
+ */
+ decodeIMSI: function(encodedImsi) {
+ // MCC: 10 bits, 3 digits
+ let encodedMCC = ((encodedImsi[CSIM_IMSI_M_MCC_BYTE + 1] & 0x03) << 8) +
+ (encodedImsi[CSIM_IMSI_M_MCC_BYTE] & 0xff);
+ let mcc = this.decodeIMSIValue(encodedMCC, 3);
+
+ // MNC: 7 bits, 2 digits
+ let encodedMNC = encodedImsi[CSIM_IMSI_M_MNC_BYTE] & 0x7f;
+ let mnc = this.decodeIMSIValue(encodedMNC, 2);
+
+ // MIN2: 10 bits, 3 digits
+ let encodedMIN2 = ((encodedImsi[CSIM_IMSI_M_MIN2_BYTE + 1] & 0x03) << 8) +
+ (encodedImsi[CSIM_IMSI_M_MIN2_BYTE] & 0xff);
+ let min2 = this.decodeIMSIValue(encodedMIN2, 3);
+
+ // MIN1: 10+4+10 bits, 3+1+3 digits
+ let encodedMIN1First3 = ((encodedImsi[CSIM_IMSI_M_MIN1_BYTE + 2] & 0xff) << 2) +
+ ((encodedImsi[CSIM_IMSI_M_MIN1_BYTE + 1] & 0xc0) >> 6);
+ let min1First3 = this.decodeIMSIValue(encodedMIN1First3, 3);
+
+ let encodedFourthDigit = (encodedImsi[CSIM_IMSI_M_MIN1_BYTE + 1] & 0x3c) >> 2;
+ if (encodedFourthDigit > 9) {
+ encodedFourthDigit = 0;
+ }
+ let fourthDigit = encodedFourthDigit.toString();
+
+ let encodedMIN1Last3 = ((encodedImsi[CSIM_IMSI_M_MIN1_BYTE + 1] & 0x03) << 8) +
+ (encodedImsi[CSIM_IMSI_M_MIN1_BYTE] & 0xff);
+ let min1Last3 = this.decodeIMSIValue(encodedMIN1Last3, 3);
+
+ return mcc + mnc + min2 + min1First3 + fourthDigit + min1Last3;
+ },
+
+ /**
+ * Decode IMSI Helper function
+ * See 3GPP2 C.S0005 section 2.3.1.1
+ */
+ decodeIMSIValue: function(encoded, length) {
+ let offset = length === 3 ? 111 : 11;
+ let value = encoded + offset;
+
+ for (let base = 10, temp = value, i = 0; i < length; i++) {
+ if (temp % 10 === 0) {
+ value -= base;
+ }
+ temp = Math.floor(value / base);
+ base = base * 10;
+ }
+
+ let s = value.toString();
+ while (s.length < length) {
+ s = "0" + s;
+ }
+
+ return s;
+ },
+
+ /**
+ * Read CDMAHOME for CSIM.
+ * See 3GPP2 C.S0023 Sec. 3.4.8.
+ */
+ readCDMAHome: function() {
+ let ICCIOHelper = this.context.ICCIOHelper;
+
+ function callback(options) {
+ let Buf = this.context.Buf;
+ let GsmPDUHelper = this.context.GsmPDUHelper;
+
+ let strLen = Buf.readInt32();
+ let tempOctet = GsmPDUHelper.readHexOctet();
+ cdmaHomeSystemId.push(((GsmPDUHelper.readHexOctet() & 0x7f) << 8) | tempOctet);
+ tempOctet = GsmPDUHelper.readHexOctet();
+ cdmaHomeNetworkId.push(((GsmPDUHelper.readHexOctet() & 0xff) << 8) | tempOctet);
+
+ // Consuming the last octet: band class.
+ Buf.seekIncoming(Buf.PDU_HEX_OCTET_SIZE);
+
+ Buf.readStringDelimiter(strLen);
+ if (options.p1 < options.totalRecords) {
+ ICCIOHelper.loadNextRecord(options);
+ } else {
+ if (DEBUG) {
+ this.context.debug("CDMAHome system id: " +
+ JSON.stringify(cdmaHomeSystemId));
+ this.context.debug("CDMAHome network id: " +
+ JSON.stringify(cdmaHomeNetworkId));
+ }
+ this.context.RIL.cdmaHome = {
+ systemId: cdmaHomeSystemId,
+ networkId: cdmaHomeNetworkId
+ };
+ }
+ }
+
+ let cdmaHomeSystemId = [], cdmaHomeNetworkId = [];
+ ICCIOHelper.loadLinearFixedEF({fileId: ICC_EF_CSIM_CDMAHOME,
+ callback: callback.bind(this)});
+ },
+
+ /**
+ * Read CDMA Service Table.
+ * See 3GPP2 C.S0023 Sec. 3.4.18
+ */
+ readCST: function() {
+ function callback() {
+ let Buf = this.context.Buf;
+ let RIL = this.context.RIL;
+
+ let strLen = Buf.readInt32();
+ // Each octet is encoded into two chars.
+ RIL.iccInfoPrivate.cst =
+ this.context.GsmPDUHelper.readHexOctetArray(strLen / 2);
+ Buf.readStringDelimiter(strLen);
+
+ if (DEBUG) {
+ let str = "";
+ for (let i = 0; i < RIL.iccInfoPrivate.cst.length; i++) {
+ str += RIL.iccInfoPrivate.cst[i] + ", ";
+ }
+ this.context.debug("CST: " + str);
+ }
+
+ if (this.context.ICCUtilsHelper.isICCServiceAvailable("SPN")) {
+ if (DEBUG) this.context.debug("SPN: SPN is available");
+ this.readSPN();
+ }
+ }
+ this.context.ICCIOHelper.loadTransparentEF({
+ fileId: ICC_EF_CSIM_CST,
+ callback: callback.bind(this)
+ });
+ },
+
+ readSPN: function() {
+ function callback() {
+ let Buf = this.context.Buf;
+ let strLen = Buf.readInt32();
+ let octetLen = strLen / 2;
+
+ let GsmPDUHelper = this.context.GsmPDUHelper;
+ let displayCondition = GsmPDUHelper.readHexOctet();
+ let codingScheme = GsmPDUHelper.readHexOctet();
+ // Skip one octet: language indicator.
+ Buf.seekIncoming(Buf.PDU_HEX_OCTET_SIZE);
+ let readLen = 3;
+
+ // SPN String ends up with 0xff.
+ let userDataBuffer = [];
+
+ while (readLen < octetLen) {
+ let octet = GsmPDUHelper.readHexOctet();
+ readLen++;
+ if (octet == 0xff) {
+ break;
+ }
+ userDataBuffer.push(octet);
+ }
+
+ this.context.BitBufferHelper.startRead(userDataBuffer);
+
+ let CdmaPDUHelper = this.context.CdmaPDUHelper;
+ let msgLen;
+ switch (CdmaPDUHelper.getCdmaMsgEncoding(codingScheme)) {
+ case PDU_DCS_MSG_CODING_7BITS_ALPHABET:
+ msgLen = Math.floor(userDataBuffer.length * 8 / 7);
+ break;
+ case PDU_DCS_MSG_CODING_8BITS_ALPHABET:
+ msgLen = userDataBuffer.length;
+ break;
+ case PDU_DCS_MSG_CODING_16BITS_ALPHABET:
+ msgLen = Math.floor(userDataBuffer.length / 2);
+ break;
+ }
+
+ let RIL = this.context.RIL;
+ RIL.iccInfo.spn = CdmaPDUHelper.decodeCdmaPDUMsg(codingScheme, null, msgLen);
+ if (DEBUG) {
+ this.context.debug("CDMA SPN: " + RIL.iccInfo.spn +
+ ", Display condition: " + displayCondition);
+ }
+ RIL.iccInfoPrivate.spnDisplayCondition = displayCondition;
+ Buf.seekIncoming((octetLen - readLen) * Buf.PDU_HEX_OCTET_SIZE);
+ Buf.readStringDelimiter(strLen);
+ }
+
+ this.context.ICCIOHelper.loadTransparentEF({
+ fileId: ICC_EF_CSIM_SPN,
+ callback: callback.bind(this)
+ });
+ }
+};
+
+/**
+ * Helper functions for ICC utilities.
+ */
+function ICCUtilsHelperObject(aContext) {
+ this.context = aContext;
+}
+ICCUtilsHelperObject.prototype = {
+ context: null,
+
+ /**
+ * Get network names by using EF_OPL and EF_PNN
+ *
+ * @See 3GPP TS 31.102 sec. 4.2.58 and sec. 4.2.59 for USIM,
+ * 3GPP TS 51.011 sec. 10.3.41 and sec. 10.3.42 for SIM.
+ *
+ * @param mcc The mobile country code of the network.
+ * @param mnc The mobile network code of the network.
+ * @param lac The location area code of the network.
+ */
+ getNetworkNameFromICC: function(mcc, mnc, lac) {
+ let RIL = this.context.RIL;
+ let iccInfoPriv = RIL.iccInfoPrivate;
+ let iccInfo = RIL.iccInfo;
+ let pnnEntry;
+
+ if (!mcc || !mnc || lac == null || lac < 0) {
+ return null;
+ }
+
+ // We won't get network name if there is no PNN file.
+ if (!iccInfoPriv.PNN) {
+ return null;
+ }
+
+ if (!this.isICCServiceAvailable("OPL")) {
+ // When OPL is not present:
+ // According to 3GPP TS 31.102 Sec. 4.2.58 and 3GPP TS 51.011 Sec. 10.3.41,
+ // If EF_OPL is not present, the first record in this EF is used for the
+ // default network name when registered to the HPLMN.
+ // If we haven't get pnnEntry assigned, we should try to assign default
+ // value to it.
+ if (mcc == iccInfo.mcc && mnc == iccInfo.mnc) {
+ pnnEntry = iccInfoPriv.PNN[0];
+ }
+ } else {
+ let GsmPDUHelper = this.context.GsmPDUHelper;
+ let wildChar = GsmPDUHelper.extendedBcdChars.charAt(0x0d);
+ // According to 3GPP TS 31.102 Sec. 4.2.59 and 3GPP TS 51.011 Sec. 10.3.42,
+ // the ME shall use this EF_OPL in association with the EF_PNN in place
+ // of any network name stored within the ME's internal list and any network
+ // name received when registered to the PLMN.
+ let length = iccInfoPriv.OPL ? iccInfoPriv.OPL.length : 0;
+ for (let i = 0; i < length; i++) {
+ let unmatch = false;
+ let opl = iccInfoPriv.OPL[i];
+ // Try to match the MCC/MNC. Besides, A BCD value of 'D' in any of the
+ // MCC and/or MNC digits shall be used to indicate a "wild" value for
+ // that corresponding MCC/MNC digit.
+ if (opl.mcc.indexOf(wildChar) !== -1) {
+ for (let j = 0; j < opl.mcc.length; j++) {
+ if (opl.mcc[j] !== wildChar && opl.mcc[j] !== mcc[j]) {
+ unmatch = true;
+ break;
+ }
+ }
+ if (unmatch) {
+ continue;
+ }
+ } else {
+ if (mcc !== opl.mcc) {
+ continue;
+ }
+ }
+
+ if (mnc.length !== opl.mnc.length) {
+ continue;
+ }
+
+ if (opl.mnc.indexOf(wildChar) !== -1) {
+ for (let j = 0; j < opl.mnc.length; j++) {
+ if (opl.mnc[j] !== wildChar && opl.mnc[j] !== mnc[j]) {
+ unmatch = true;
+ break;
+ }
+ }
+ if (unmatch) {
+ continue;
+ }
+ } else {
+ if (mnc !== opl.mnc) {
+ continue;
+ }
+ }
+
+ // Try to match the location area code. If current local area code is
+ // covered by lac range that specified in the OPL entry, use the PNN
+ // that specified in the OPL entry.
+ if ((opl.lacTacStart === 0x0 && opl.lacTacEnd == 0xFFFE) ||
+ (opl.lacTacStart <= lac && opl.lacTacEnd >= lac)) {
+ if (opl.pnnRecordId === 0) {
+ // See 3GPP TS 31.102 Sec. 4.2.59 and 3GPP TS 51.011 Sec. 10.3.42,
+ // A value of '00' indicates that the name is to be taken from other
+ // sources.
+ return null;
+ }
+ pnnEntry = iccInfoPriv.PNN[opl.pnnRecordId - 1];
+ break;
+ }
+ }
+ }
+
+ if (!pnnEntry) {
+ return null;
+ }
+
+ // Return a new object to avoid global variable, PNN, be modified by accident.
+ return { fullName: pnnEntry.fullName || "",
+ shortName: pnnEntry.shortName || "" };
+ },
+
+ /**
+ * This will compute the spnDisplay field of the network.
+ * See TS 22.101 Annex A and TS 51.011 10.3.11 for details.
+ *
+ * @return True if some of iccInfo is changed in by this function.
+ */
+ updateDisplayCondition: function() {
+ let RIL = this.context.RIL;
+
+ // If EFspn isn't existed in SIM or it haven't been read yet, we should
+ // just set isDisplayNetworkNameRequired = true and
+ // isDisplaySpnRequired = false
+ let iccInfo = RIL.iccInfo;
+ let iccInfoPriv = RIL.iccInfoPrivate;
+ let displayCondition = iccInfoPriv.spnDisplayCondition;
+ let origIsDisplayNetworkNameRequired = iccInfo.isDisplayNetworkNameRequired;
+ let origIsDisplaySPNRequired = iccInfo.isDisplaySpnRequired;
+
+ if (displayCondition === undefined) {
+ iccInfo.isDisplayNetworkNameRequired = true;
+ iccInfo.isDisplaySpnRequired = false;
+ } else if (RIL._isCdma) {
+ // CDMA family display rule.
+ let cdmaHome = RIL.cdmaHome;
+ let cell = RIL.voiceRegistrationState.cell;
+ let sid = cell && cell.cdmaSystemId;
+ let nid = cell && cell.cdmaNetworkId;
+
+ iccInfo.isDisplayNetworkNameRequired = false;
+
+ // If display condition is 0x0, we don't even need to check network id
+ // or system id.
+ if (displayCondition === 0x0) {
+ iccInfo.isDisplaySpnRequired = false;
+ } else {
+ // CDMA SPN Display condition dosen't specify whenever network name is
+ // reqired.
+ if (!cdmaHome ||
+ !cdmaHome.systemId ||
+ cdmaHome.systemId.length === 0 ||
+ cdmaHome.systemId.length != cdmaHome.networkId.length ||
+ !sid || !nid) {
+ // CDMA Home haven't been ready, or we haven't got the system id and
+ // network id of the network we register to, assuming we are in home
+ // network.
+ iccInfo.isDisplaySpnRequired = true;
+ } else {
+ // Determine if we are registered in the home service area.
+ // System ID and Network ID are described in 3GPP2 C.S0005 Sec. 2.6.5.2.
+ let inHomeArea = false;
+ for (let i = 0; i < cdmaHome.systemId.length; i++) {
+ let homeSid = cdmaHome.systemId[i],
+ homeNid = cdmaHome.networkId[i];
+ if (homeSid === 0 || homeNid === 0 // Reserved system id/network id
+ || homeSid != sid) {
+ continue;
+ }
+ // According to 3GPP2 C.S0005 Sec. 2.6.5.2, NID number 65535 means
+ // all networks in the system should be considered as home.
+ if (homeNid == 65535 || homeNid == nid) {
+ inHomeArea = true;
+ break;
+ }
+ }
+ iccInfo.isDisplaySpnRequired = inHomeArea;
+ }
+ }
+ } else {
+ // GSM family display rule.
+ let operatorMnc = RIL.operator ? RIL.operator.mnc : -1;
+ let operatorMcc = RIL.operator ? RIL.operator.mcc : -1;
+
+ // First detect if we are on HPLMN or one of the PLMN
+ // specified by the SIM card.
+ let isOnMatchingPlmn = false;
+
+ // If the current network is the one defined as mcc/mnc
+ // in SIM card, it's okay.
+ if (iccInfo.mcc == operatorMcc && iccInfo.mnc == operatorMnc) {
+ isOnMatchingPlmn = true;
+ }
+
+ // Test to see if operator's mcc/mnc match mcc/mnc of PLMN.
+ if (!isOnMatchingPlmn && iccInfoPriv.SPDI) {
+ let iccSpdi = iccInfoPriv.SPDI; // PLMN list
+ for (let plmn in iccSpdi) {
+ let plmnMcc = iccSpdi[plmn].mcc;
+ let plmnMnc = iccSpdi[plmn].mnc;
+ isOnMatchingPlmn = (plmnMcc == operatorMcc) && (plmnMnc == operatorMnc);
+ if (isOnMatchingPlmn) {
+ break;
+ }
+ }
+ }
+
+ // See 3GPP TS 22.101 A.4 Service Provider Name indication, and TS 31.102
+ // clause 4.2.12 EF_SPN for detail.
+ if (isOnMatchingPlmn) {
+ // The first bit of display condition tells us if we should display
+ // registered PLMN.
+ if (DEBUG) {
+ this.context.debug("PLMN is HPLMN or PLMN " + "is in PLMN list");
+ }
+
+ // TS 31.102 Sec. 4.2.66 and TS 51.011 Sec. 10.3.50
+ // EF_SPDI contains a list of PLMNs in which the Service Provider Name
+ // shall be displayed.
+ iccInfo.isDisplaySpnRequired = true;
+ iccInfo.isDisplayNetworkNameRequired = (displayCondition & 0x01) !== 0;
+ } else {
+ // The second bit of display condition tells us if we should display
+ // registered PLMN.
+ if (DEBUG) {
+ this.context.debug("PLMN isn't HPLMN and PLMN isn't in PLMN list");
+ }
+
+ iccInfo.isDisplayNetworkNameRequired = true;
+ iccInfo.isDisplaySpnRequired = (displayCondition & 0x02) === 0;
+ }
+ }
+
+ if (DEBUG) {
+ this.context.debug("isDisplayNetworkNameRequired = " +
+ iccInfo.isDisplayNetworkNameRequired);
+ this.context.debug("isDisplaySpnRequired = " + iccInfo.isDisplaySpnRequired);
+ }
+
+ return ((origIsDisplayNetworkNameRequired !== iccInfo.isDisplayNetworkNameRequired) ||
+ (origIsDisplaySPNRequired !== iccInfo.isDisplaySpnRequired));
+ },
+
+ decodeSimTlvs: function(tlvsLen) {
+ let GsmPDUHelper = this.context.GsmPDUHelper;
+
+ let index = 0;
+ let tlvs = [];
+ while (index < tlvsLen) {
+ let simTlv = {
+ tag : GsmPDUHelper.readHexOctet(),
+ length : GsmPDUHelper.readHexOctet(),
+ };
+ simTlv.value = GsmPDUHelper.readHexOctetArray(simTlv.length);
+ tlvs.push(simTlv);
+ index += simTlv.length + 2; // The length of 'tag' and 'length' field.
+ }
+ return tlvs;
+ },
+
+ /**
+ * Parse those TLVs and convert it to an object.
+ */
+ parsePbrTlvs: function(pbrTlvs) {
+ let pbr = {};
+ for (let i = 0; i < pbrTlvs.length; i++) {
+ let pbrTlv = pbrTlvs[i];
+ let anrIndex = 0;
+ for (let j = 0; j < pbrTlv.value.length; j++) {
+ let tlv = pbrTlv.value[j];
+ let tagName = USIM_TAG_NAME[tlv.tag];
+
+ // ANR could have multiple files. We save it as anr0, anr1,...etc.
+ if (tlv.tag == ICC_USIM_EFANR_TAG) {
+ tagName += anrIndex;
+ anrIndex++;
+ }
+ pbr[tagName] = tlv;
+ pbr[tagName].fileType = pbrTlv.tag;
+ pbr[tagName].fileId = (tlv.value[0] << 8) | tlv.value[1];
+ pbr[tagName].sfi = tlv.value[2];
+
+ // For Type 2, the order of files is in the same order in IAP.
+ if (pbrTlv.tag == ICC_USIM_TYPE2_TAG) {
+ pbr[tagName].indexInIAP = j;
+ }
+ }
+ }
+
+ return pbr;
+ },
+
+ /**
+ * Update the ICC information to RadioInterfaceLayer.
+ */
+ handleICCInfoChange: function() {
+ let RIL = this.context.RIL;
+ RIL.iccInfo.rilMessageType = "iccinfochange";
+ RIL.sendChromeMessage(RIL.iccInfo);
+ },
+
+ /**
+ * Get whether specificed (U)SIM service is available.
+ *
+ * @param geckoService
+ * Service name like "ADN", "BDN", etc.
+ *
+ * @return true if the service is enabled, false otherwise.
+ */
+ isICCServiceAvailable: function(geckoService) {
+ let RIL = this.context.RIL;
+ let serviceTable = RIL._isCdma ? RIL.iccInfoPrivate.cst:
+ RIL.iccInfoPrivate.sst;
+ let index, bitmask;
+ if (RIL.appType == CARD_APPTYPE_SIM || RIL.appType == CARD_APPTYPE_RUIM) {
+ /**
+ * Service id is valid in 1..N, and 2 bits are used to code each service.
+ *
+ * +----+-- --+----+----+
+ * | b8 | ... | b2 | b1 |
+ * +----+-- --+----+----+
+ *
+ * b1 = 0, service not allocated.
+ * 1, service allocated.
+ * b2 = 0, service not activated.
+ * 1, service activated.
+ *
+ * @see 3GPP TS 51.011 10.3.7.
+ */
+ let simService;
+ if (RIL.appType == CARD_APPTYPE_SIM) {
+ simService = GECKO_ICC_SERVICES.sim[geckoService];
+ } else {
+ simService = GECKO_ICC_SERVICES.ruim[geckoService];
+ }
+ if (!simService) {
+ return false;
+ }
+ simService -= 1;
+ index = Math.floor(simService / 4);
+ bitmask = 2 << ((simService % 4) << 1);
+ } else if (RIL.appType == CARD_APPTYPE_USIM) {
+ /**
+ * Service id is valid in 1..N, and 1 bit is used to code each service.
+ *
+ * +----+-- --+----+----+
+ * | b8 | ... | b2 | b1 |
+ * +----+-- --+----+----+
+ *
+ * b1 = 0, service not avaiable.
+ * 1, service available.
+ *
+ * @see 3GPP TS 31.102 4.2.8.
+ */
+ let usimService = GECKO_ICC_SERVICES.usim[geckoService];
+ if (!usimService) {
+ return false;
+ }
+ usimService -= 1;
+ index = Math.floor(usimService / 8);
+ bitmask = 1 << ((usimService % 8) << 0);
+ }
+
+ return (serviceTable !== null) &&
+ (index < serviceTable.length) &&
+ ((serviceTable[index] & bitmask) !== 0);
+ },
+
+ /**
+ * Get whether specificed CPHS service is available.
+ *
+ * @param geckoService
+ * Service name like "MDN", etc.
+ *
+ * @return true if the service is enabled, false otherwise.
+ */
+ isCphsServiceAvailable: function(geckoService) {
+ let RIL = this.context.RIL;
+ let serviceTable = RIL.iccInfoPrivate.cphsSt;
+
+ if (!(serviceTable instanceof Uint8Array)) {
+ return false;
+ }
+
+ /**
+ * Service id is valid in 1..N, and 2 bits are used to code each service.
+ *
+ * +----+-- --+----+----+
+ * | b8 | ... | b2 | b1 |
+ * +----+-- --+----+----+
+ *
+ * b1 = 0, service not allocated.
+ * 1, service allocated.
+ * b2 = 0, service not activated.
+ * 1, service activated.
+ *
+ * @See B.3.1.1 CPHS Information in CPHS Phase 2.
+ */
+ let cphsService = GECKO_ICC_SERVICES.cphs[geckoService];
+
+ if (!cphsService) {
+ return false;
+ }
+ cphsService -= 1;
+ let index = Math.floor(cphsService / 4);
+ let bitmask = 2 << ((cphsService % 4) << 1);
+
+ return (index < serviceTable.length) &&
+ ((serviceTable[index] & bitmask) !== 0);
+ },
+
+ /**
+ * Check if the string is of GSM default 7-bit coded alphabets with bit 8
+ * set to 0.
+ *
+ * @param str String to be checked.
+ */
+ isGsm8BitAlphabet: function(str) {
+ if (!str) {
+ return false;
+ }
+
+ const langTable = PDU_NL_LOCKING_SHIFT_TABLES[PDU_NL_IDENTIFIER_DEFAULT];
+ const langShiftTable = PDU_NL_SINGLE_SHIFT_TABLES[PDU_NL_IDENTIFIER_DEFAULT];
+
+ for (let i = 0; i < str.length; i++) {
+ let c = str.charAt(i);
+ let octet = langTable.indexOf(c);
+ if (octet == -1) {
+ octet = langShiftTable.indexOf(c);
+ if (octet == -1) {
+ return false;
+ }
+ }
+ }
+
+ return true;
+ },
+
+ /**
+ * Parse MCC/MNC from IMSI. If there is no available value for the length of
+ * mnc, it will use the data in MCC table to parse.
+ *
+ * @param imsi
+ * The imsi of icc.
+ * @param mncLength [optional]
+ * The length of mnc.
+ * Zero indicates we haven't got a valid mnc length.
+ *
+ * @return An object contains the parsing result of mcc and mnc.
+ * Or null if any error occurred.
+ */
+ parseMccMncFromImsi: function(imsi, mncLength) {
+ if (!imsi) {
+ return null;
+ }
+
+ // MCC is the first 3 digits of IMSI.
+ let mcc = imsi.substr(0,3);
+ if (!mncLength) {
+ // Check the MCC/MNC table for MNC length = 3 first for the case we don't
+ // have the 4th byte data from EF_AD.
+ if (PLMN_HAVING_3DIGITS_MNC[mcc] &&
+ PLMN_HAVING_3DIGITS_MNC[mcc].indexOf(imsi.substr(3, 3)) !== -1) {
+ mncLength = 3;
+ } else {
+ // Check the MCC table to decide the length of MNC.
+ let index = MCC_TABLE_FOR_MNC_LENGTH_IS_3.indexOf(mcc);
+ mncLength = (index !== -1) ? 3 : 2;
+ }
+ }
+ let mnc = imsi.substr(3, mncLength);
+ if (DEBUG) {
+ this.context.debug("IMSI: " + imsi + " MCC: " + mcc + " MNC: " + mnc);
+ }
+
+ return { mcc: mcc, mnc: mnc};
+ },
+};
+
+/**
+ * Helper for ICC Contacts.
+ */
+function ICCContactHelperObject(aContext) {
+ this.context = aContext;
+}
+ICCContactHelperObject.prototype = {
+ context: null,
+
+ /**
+ * Helper function to check DF_PHONEBOOK.
+ */
+ hasDfPhoneBook: function(appType) {
+ switch (appType) {
+ case CARD_APPTYPE_SIM:
+ return false;
+ case CARD_APPTYPE_USIM:
+ return true;
+ case CARD_APPTYPE_RUIM:
+ let ICCUtilsHelper = this.context.ICCUtilsHelper;
+ return ICCUtilsHelper.isICCServiceAvailable("ENHANCED_PHONEBOOK");
+ default:
+ return false;
+ }
+ },
+
+ /**
+ * Helper function to read ICC contacts.
+ *
+ * @param appType One of CARD_APPTYPE_*.
+ * @param contactType One of GECKO_CARDCONTACT_TYPE_*.
+ * @param onsuccess Callback to be called when success.
+ * @param onerror Callback to be called when error.
+ */
+ readICCContacts: function(appType, contactType, onsuccess, onerror) {
+ let ICCRecordHelper = this.context.ICCRecordHelper;
+ let ICCUtilsHelper = this.context.ICCUtilsHelper;
+
+ switch (contactType) {
+ case GECKO_CARDCONTACT_TYPE_ADN:
+ if (!this.hasDfPhoneBook(appType)) {
+ ICCRecordHelper.readADNLike(ICC_EF_ADN,
+ (ICCUtilsHelper.isICCServiceAvailable("EXT1")) ? ICC_EF_EXT1 : null,
+ onsuccess, onerror);
+ } else {
+ this.readUSimContacts(onsuccess, onerror);
+ }
+ break;
+ case GECKO_CARDCONTACT_TYPE_FDN:
+ if (!ICCUtilsHelper.isICCServiceAvailable("FDN")) {
+ onerror(CONTACT_ERR_CONTACT_TYPE_NOT_SUPPORTED);
+ break;
+ }
+ ICCRecordHelper.readADNLike(ICC_EF_FDN,
+ (ICCUtilsHelper.isICCServiceAvailable("EXT2")) ? ICC_EF_EXT2 : null,
+ onsuccess, onerror);
+ break;
+ case GECKO_CARDCONTACT_TYPE_SDN:
+ if (!ICCUtilsHelper.isICCServiceAvailable("SDN")) {
+ onerror(CONTACT_ERR_CONTACT_TYPE_NOT_SUPPORTED);
+ break;
+ }
+
+ ICCRecordHelper.readADNLike(ICC_EF_SDN,
+ (ICCUtilsHelper.isICCServiceAvailable("EXT3")) ? ICC_EF_EXT3 : null,
+ onsuccess, onerror);
+ break;
+ default:
+ if (DEBUG) {
+ this.context.debug("Unsupported contactType :" + contactType);
+ }
+ onerror(CONTACT_ERR_CONTACT_TYPE_NOT_SUPPORTED);
+ break;
+ }
+ },
+
+ /**
+ * Helper function to find free contact record.
+ *
+ * @param appType One of CARD_APPTYPE_*.
+ * @param contactType One of GECKO_CARDCONTACT_TYPE_*.
+ * @param onsuccess Callback to be called when success.
+ * @param onerror Callback to be called when error.
+ */
+ findFreeICCContact: function(appType, contactType, onsuccess, onerror) {
+ let ICCRecordHelper = this.context.ICCRecordHelper;
+
+ switch (contactType) {
+ case GECKO_CARDCONTACT_TYPE_ADN:
+ if (!this.hasDfPhoneBook(appType)) {
+ ICCRecordHelper.findFreeRecordId(ICC_EF_ADN, onsuccess.bind(null, 0), onerror);
+ } else {
+ let gotPbrCb = function gotPbrCb(pbrs) {
+ this.findUSimFreeADNRecordId(pbrs, onsuccess, onerror);
+ }.bind(this);
+
+ ICCRecordHelper.readPBR(gotPbrCb, onerror);
+ }
+ break;
+ case GECKO_CARDCONTACT_TYPE_FDN:
+ ICCRecordHelper.findFreeRecordId(ICC_EF_FDN, onsuccess.bind(null, 0), onerror);
+ break;
+ default:
+ if (DEBUG) {
+ this.context.debug("Unsupported contactType :" + contactType);
+ }
+ onerror(CONTACT_ERR_CONTACT_TYPE_NOT_SUPPORTED);
+ break;
+ }
+ },
+
+ /**
+ * Cache the pbr index of the possible free record.
+ */
+ _freePbrIndex: 0,
+
+ /**
+ * Find free ADN record id in USIM.
+ *
+ * @param pbrs All Phonebook Reference Files read.
+ * @param onsuccess Callback to be called when success.
+ * @param onerror Callback to be called when error.
+ */
+ findUSimFreeADNRecordId: function(pbrs, onsuccess, onerror) {
+ let ICCRecordHelper = this.context.ICCRecordHelper;
+
+ function callback(pbrIndex, recordId) {
+ // Assume other free records are probably in the same phonebook set.
+ this._freePbrIndex = pbrIndex;
+ onsuccess(pbrIndex, recordId);
+ }
+
+ let nextPbrIndex = -1;
+ (function findFreeRecordId(pbrIndex) {
+ if (nextPbrIndex === this._freePbrIndex) {
+ // No free record found, reset the pbr index of free record.
+ this._freePbrIndex = 0;
+ if (DEBUG) {
+ this.context.debug(CONTACT_ERR_NO_FREE_RECORD_FOUND);
+ }
+ onerror(CONTACT_ERR_NO_FREE_RECORD_FOUND);
+ return;
+ }
+
+ let pbr = pbrs[pbrIndex];
+ nextPbrIndex = (pbrIndex + 1) % pbrs.length;
+ ICCRecordHelper.findFreeRecordId(
+ pbr.adn.fileId,
+ callback.bind(this, pbrIndex),
+ findFreeRecordId.bind(this, nextPbrIndex));
+ }).call(this, this._freePbrIndex);
+ },
+
+ /**
+ * Helper function to add a new ICC contact.
+ *
+ * @param appType One of CARD_APPTYPE_*.
+ * @param contactType One of GECKO_CARDCONTACT_TYPE_*.
+ * @param contact The contact will be added.
+ * @param pin2 PIN2 is required for FDN.
+ * @param onsuccess Callback to be called when success.
+ * @param onerror Callback to be called when error.
+ */
+ addICCContact: function(appType, contactType, contact, pin2, onsuccess, onerror) {
+ let foundFreeCb = (function foundFreeCb(pbrIndex, recordId) {
+ contact.pbrIndex = pbrIndex;
+ contact.recordId = recordId;
+ this.updateICCContact(appType, contactType, contact, pin2, onsuccess, onerror);
+ }).bind(this);
+
+ // Find free record first.
+ this.findFreeICCContact(appType, contactType, foundFreeCb, onerror);
+ },
+
+ /**
+ * Helper function to update ICC contact.
+ *
+ * @param appType One of CARD_APPTYPE_*.
+ * @param contactType One of GECKO_CARDCONTACT_TYPE_*.
+ * @param contact The contact will be updated.
+ * @param pin2 PIN2 is required for FDN.
+ * @param onsuccess Callback to be called when success.
+ * @param onerror Callback to be called when error.
+ */
+ updateICCContact: function(appType, contactType, contact, pin2, onsuccess, onerror) {
+ let ICCRecordHelper = this.context.ICCRecordHelper;
+ let ICCUtilsHelper = this.context.ICCUtilsHelper;
+
+ let updateContactCb = (updatedContact) => {
+ updatedContact.pbrIndex = contact.pbrIndex;
+ updatedContact.recordId = contact.recordId;
+ onsuccess(updatedContact);
+ }
+
+ switch (contactType) {
+ case GECKO_CARDCONTACT_TYPE_ADN:
+ if (!this.hasDfPhoneBook(appType)) {
+ if (ICCUtilsHelper.isICCServiceAvailable("EXT1")) {
+ this.updateADNLikeWithExtension(ICC_EF_ADN, ICC_EF_EXT1,
+ contact, null,
+ updateContactCb, onerror);
+ } else {
+ ICCRecordHelper.updateADNLike(ICC_EF_ADN, 0xff,
+ contact, null,
+ updateContactCb, onerror);
+ }
+ } else {
+ this.updateUSimContact(contact, updateContactCb, onerror);
+ }
+ break;
+ case GECKO_CARDCONTACT_TYPE_FDN:
+ if (!pin2) {
+ onerror(GECKO_ERROR_SIM_PIN2);
+ return;
+ }
+ if (!ICCUtilsHelper.isICCServiceAvailable("FDN")) {
+ onerror(CONTACT_ERR_CONTACT_TYPE_NOT_SUPPORTED);
+ break;
+ }
+ if (ICCUtilsHelper.isICCServiceAvailable("EXT2")) {
+ this.updateADNLikeWithExtension(ICC_EF_FDN, ICC_EF_EXT2,
+ contact, pin2,
+ updateContactCb, onerror);
+ } else {
+ ICCRecordHelper.updateADNLike(ICC_EF_FDN,
+ 0xff,
+ contact, pin2,
+ updateContactCb, onerror);
+ }
+ break;
+ default:
+ if (DEBUG) {
+ this.context.debug("Unsupported contactType :" + contactType);
+ }
+ onerror(CONTACT_ERR_CONTACT_TYPE_NOT_SUPPORTED);
+ break;
+ }
+ },
+
+ /**
+ * Read contacts from USIM.
+ *
+ * @param onsuccess Callback to be called when success.
+ * @param onerror Callback to be called when error.
+ */
+ readUSimContacts: function(onsuccess, onerror) {
+ let gotPbrCb = function gotPbrCb(pbrs) {
+ this.readAllPhonebookSets(pbrs, onsuccess, onerror);
+ }.bind(this);
+
+ this.context.ICCRecordHelper.readPBR(gotPbrCb, onerror);
+ },
+
+ /**
+ * Read all Phonebook sets.
+ *
+ * @param pbrs All Phonebook Reference Files read.
+ * @param onsuccess Callback to be called when success.
+ * @param onerror Callback to be called when error.
+ */
+ readAllPhonebookSets: function(pbrs, onsuccess, onerror) {
+ let allContacts = [], pbrIndex = 0;
+ let readPhonebook = function(contacts) {
+ if (contacts) {
+ allContacts = allContacts.concat(contacts);
+ }
+
+ let cLen = contacts ? contacts.length : 0;
+ for (let i = 0; i < cLen; i++) {
+ contacts[i].pbrIndex = pbrIndex;
+ }
+
+ pbrIndex++;
+ if (pbrIndex >= pbrs.length) {
+ if (onsuccess) {
+ onsuccess(allContacts);
+ }
+ return;
+ }
+
+ this.readPhonebookSet(pbrs[pbrIndex], readPhonebook, onerror);
+ }.bind(this);
+
+ this.readPhonebookSet(pbrs[pbrIndex], readPhonebook, onerror);
+ },
+
+ /**
+ * Read from Phonebook Reference File.
+ *
+ * @param pbr Phonebook Reference File to be read.
+ * @param onsuccess Callback to be called when success.
+ * @param onerror Callback to be called when error.
+ */
+ readPhonebookSet: function(pbr, onsuccess, onerror) {
+ let ICCRecordHelper = this.context.ICCRecordHelper;
+ let gotAdnCb = function gotAdnCb(contacts) {
+ this.readSupportedPBRFields(pbr, contacts, onsuccess, onerror);
+ }.bind(this);
+
+ ICCRecordHelper.readADNLike(pbr.adn.fileId,
+ (pbr.ext1) ? pbr.ext1.fileId : null, gotAdnCb, onerror);
+ },
+
+ /**
+ * Read supported Phonebook fields.
+ *
+ * @param pbr Phone Book Reference file.
+ * @param contacts Contacts stored on ICC.
+ * @param onsuccess Callback to be called when success.
+ * @param onerror Callback to be called when error.
+ */
+ readSupportedPBRFields: function(pbr, contacts, onsuccess, onerror) {
+ let fieldIndex = 0;
+ (function readField() {
+ let field = USIM_PBR_FIELDS[fieldIndex];
+ fieldIndex += 1;
+ if (!field) {
+ if (onsuccess) {
+ onsuccess(contacts);
+ }
+ return;
+ }
+
+ this.readPhonebookField(pbr, contacts, field, readField.bind(this), onerror);
+ }).call(this);
+ },
+
+ /**
+ * Read Phonebook field.
+ *
+ * @param pbr The phonebook reference file.
+ * @param contacts Contacts stored on ICC.
+ * @param field Phonebook field to be retrieved.
+ * @param onsuccess Callback to be called when success.
+ * @param onerror Callback to be called when error.
+ */
+ readPhonebookField: function(pbr, contacts, field, onsuccess, onerror) {
+ if (!pbr[field]) {
+ if (onsuccess) {
+ onsuccess(contacts);
+ }
+ return;
+ }
+
+ (function doReadContactField(n) {
+ if (n >= contacts.length) {
+ // All contact's fields are read.
+ if (onsuccess) {
+ onsuccess(contacts);
+ }
+ return;
+ }
+
+ // get n-th contact's field.
+ this.readContactField(pbr, contacts[n], field,
+ doReadContactField.bind(this, n + 1), onerror);
+ }).call(this, 0);
+ },
+
+ /**
+ * Read contact's field from USIM.
+ *
+ * @param pbr The phonebook reference file.
+ * @param contact The contact needs to get field.
+ * @param field Phonebook field to be retrieved.
+ * @param onsuccess Callback to be called when success.
+ * @param onerror Callback to be called when error.
+ */
+ readContactField: function(pbr, contact, field, onsuccess, onerror) {
+ let gotRecordIdCb = function gotRecordIdCb(recordId) {
+ if (recordId == 0xff) {
+ if (onsuccess) {
+ onsuccess();
+ }
+ return;
+ }
+
+ let fileId = pbr[field].fileId;
+ let fileType = pbr[field].fileType;
+ let gotFieldCb = function gotFieldCb(value) {
+ if (value) {
+ // Move anr0 anr1,.. into anr[].
+ if (field.startsWith(USIM_PBR_ANR)) {
+ if (!contact[USIM_PBR_ANR]) {
+ contact[USIM_PBR_ANR] = [];
+ }
+ contact[USIM_PBR_ANR].push(value);
+ } else {
+ contact[field] = value;
+ }
+ }
+
+ if (onsuccess) {
+ onsuccess();
+ }
+ }.bind(this);
+
+ let ICCRecordHelper = this.context.ICCRecordHelper;
+ // Detect EF to be read, for anr, it could have anr0, anr1,...
+ let ef = field.startsWith(USIM_PBR_ANR) ? USIM_PBR_ANR : field;
+ switch (ef) {
+ case USIM_PBR_EMAIL:
+ ICCRecordHelper.readEmail(fileId, fileType, recordId, gotFieldCb, onerror);
+ break;
+ case USIM_PBR_ANR:
+ ICCRecordHelper.readANR(fileId, fileType, recordId, gotFieldCb, onerror);
+ break;
+ default:
+ if (DEBUG) {
+ this.context.debug("Unsupported field :" + field);
+ }
+ onerror(CONTACT_ERR_FIELD_NOT_SUPPORTED);
+ break;
+ }
+ }.bind(this);
+
+ this.getContactFieldRecordId(pbr, contact, field, gotRecordIdCb, onerror);
+ },
+
+ /**
+ * Get the recordId.
+ *
+ * If the fileType of field is ICC_USIM_TYPE1_TAG, use corresponding ADN recordId.
+ * otherwise get the recordId from IAP.
+ *
+ * @see TS 131.102, clause 4.4.2.2
+ *
+ * @param pbr The phonebook reference file.
+ * @param contact The contact will be updated.
+ * @param onsuccess Callback to be called when success.
+ * @param onerror Callback to be called when error.
+ */
+ getContactFieldRecordId: function(pbr, contact, field, onsuccess, onerror) {
+ if (pbr[field].fileType == ICC_USIM_TYPE1_TAG) {
+ // If the file type is ICC_USIM_TYPE1_TAG, use corresponding ADN recordId.
+ if (onsuccess) {
+ onsuccess(contact.recordId);
+ }
+ } else if (pbr[field].fileType == ICC_USIM_TYPE2_TAG) {
+ // If the file type is ICC_USIM_TYPE2_TAG, the recordId shall be got from IAP.
+ let gotIapCb = function gotIapCb(iap) {
+ let indexInIAP = pbr[field].indexInIAP;
+ let recordId = iap[indexInIAP];
+
+ if (onsuccess) {
+ onsuccess(recordId);
+ }
+ }.bind(this);
+
+ this.context.ICCRecordHelper.readIAP(pbr.iap.fileId, contact.recordId,
+ gotIapCb, onerror);
+ } else {
+ if (DEBUG) {
+ this.context.debug("USIM PBR files in Type 3 format are not supported.");
+ }
+ onerror(CONTACT_ERR_REQUEST_NOT_SUPPORTED);
+ }
+ },
+
+ /**
+ * Update USIM contact.
+ *
+ * @param contact The contact will be updated.
+ * @param onsuccess Callback to be called when success.
+ * @param onerror Callback to be called when error.
+ */
+ updateUSimContact: function(contact, onsuccess, onerror) {
+ let gotPbrCb = function gotPbrCb(pbrs) {
+ let pbr = pbrs[contact.pbrIndex];
+ if (!pbr) {
+ if (DEBUG) {
+ this.context.debug(CONTACT_ERR_CANNOT_ACCESS_PHONEBOOK);
+ }
+ onerror(CONTACT_ERR_CANNOT_ACCESS_PHONEBOOK);
+ return;
+ }
+ this.updatePhonebookSet(pbr, contact, onsuccess, onerror);
+ }.bind(this);
+
+ this.context.ICCRecordHelper.readPBR(gotPbrCb, onerror);
+ },
+
+ /**
+ * Update fields in Phonebook Reference File.
+ *
+ * @param pbr Phonebook Reference File to be read.
+ * @param onsuccess Callback to be called when success.
+ * @param onerror Callback to be called when error.
+ */
+ updatePhonebookSet: function(pbr, contact, onsuccess, onerror) {
+ let updateAdnCb = function(updatedContact) {
+ this.updateSupportedPBRFields(pbr, contact, (updatedContactField) => {
+ onsuccess(Object.assign(updatedContact, updatedContactField));
+ }, onerror);
+ }.bind(this);
+
+ if (pbr.ext1) {
+ this.updateADNLikeWithExtension(pbr.adn.fileId, pbr.ext1.fileId,
+ contact, null, updateAdnCb, onerror);
+ } else {
+ this.context.ICCRecordHelper.updateADNLike(pbr.adn.fileId, 0xff, contact,
+ null, updateAdnCb, onerror);
+ }
+ },
+
+ /**
+ * Update supported Phonebook fields.
+ *
+ * @param pbr Phone Book Reference file.
+ * @param contact Contact to be updated.
+ * @param onsuccess Callback to be called when success.
+ * @param onerror Callback to be called when error.
+ */
+ updateSupportedPBRFields: function(pbr, contact, onsuccess, onerror) {
+ let fieldIndex = 0;
+ let contactField = {};
+
+ (function updateField() {
+ let field = USIM_PBR_FIELDS[fieldIndex];
+ fieldIndex += 1;
+
+ if (!field) {
+ if (onsuccess) {
+ onsuccess(contactField);
+ }
+ return;
+ }
+
+ // Check if PBR has this field.
+ if (!pbr[field]) {
+ updateField.call(this);
+ return;
+ }
+
+ this.updateContactField(pbr, contact, field, (fieldEntry) => {
+ contactField = Object.assign(contactField, fieldEntry);
+ updateField.call(this);
+ }, (errorMsg) => {
+ // Bug 1194149, there are some sim cards without sufficient
+ // Type 2 USIM contact fields record. We allow user continue
+ // importing contacts.
+ if (errorMsg === CONTACT_ERR_NO_FREE_RECORD_FOUND) {
+ updateField.call(this);
+ return;
+ }
+ onerror(errorMsg);
+ });
+ }).call(this);
+ },
+
+ /**
+ * Update contact's field from USIM.
+ *
+ * @param pbr The phonebook reference file.
+ * @param contact The contact needs to be updated.
+ * @param field Phonebook field to be updated.
+ * @param onsuccess Callback to be called when success.
+ * @param onerror Callback to be called when error.
+ */
+ updateContactField: function(pbr, contact, field, onsuccess, onerror) {
+ if (pbr[field].fileType === ICC_USIM_TYPE1_TAG) {
+ this.updateContactFieldType1(pbr, contact, field, onsuccess, onerror);
+ } else if (pbr[field].fileType === ICC_USIM_TYPE2_TAG) {
+ this.updateContactFieldType2(pbr, contact, field, onsuccess, onerror);
+ } else {
+ if (DEBUG) {
+ this.context.debug("USIM PBR files in Type 3 format are not supported.");
+ }
+ onerror(CONTACT_ERR_REQUEST_NOT_SUPPORTED);
+ }
+ },
+
+ /**
+ * Update Type 1 USIM contact fields.
+ *
+ * @param pbr The phonebook reference file.
+ * @param contact The contact needs to be updated.
+ * @param field Phonebook field to be updated.
+ * @param onsuccess Callback to be called when success.
+ * @param onerror Callback to be called when error.
+ */
+ updateContactFieldType1: function(pbr, contact, field, onsuccess, onerror) {
+ let ICCRecordHelper = this.context.ICCRecordHelper;
+
+ if (field === USIM_PBR_EMAIL) {
+ ICCRecordHelper.updateEmail(pbr, contact.recordId, contact.email, null,
+ (updatedEmail) => {
+ onsuccess({email: updatedEmail});
+ }, onerror);
+ } else if (field === USIM_PBR_ANR0) {
+ let anr = Array.isArray(contact.anr) ? contact.anr[0] : null;
+ ICCRecordHelper.updateANR(pbr, contact.recordId, anr, null,
+ (updatedANR) => {
+ // ANR could have multiple files. If we support more than one anr,
+ // we will save it as anr0, anr1,...etc.
+ onsuccess((updatedANR) ? {anr: [updatedANR]} : null);
+ }, onerror);
+ } else {
+ if (DEBUG) {
+ this.context.debug("Unsupported field :" + field);
+ }
+ onerror(CONTACT_ERR_FIELD_NOT_SUPPORTED);
+ }
+ },
+
+ /**
+ * Update Type 2 USIM contact fields.
+ *
+ * @param pbr The phonebook reference file.
+ * @param contact The contact needs to be updated.
+ * @param field Phonebook field to be updated.
+ * @param onsuccess Callback to be called when success.
+ * @param onerror Callback to be called when error.
+ */
+ updateContactFieldType2: function(pbr, contact, field, onsuccess, onerror) {
+ let ICCRecordHelper = this.context.ICCRecordHelper;
+
+ // Case 1 : EF_IAP[adnRecordId] doesn't have a value(0xff)
+ // Find a free recordId for EF_field
+ // Update field with that free recordId.
+ // Update IAP.
+ //
+ // Case 2: EF_IAP[adnRecordId] has a value
+ // update EF_field[iap[field.indexInIAP]]
+
+ let gotIapCb = function gotIapCb(iap) {
+ let recordId = iap[pbr[field].indexInIAP];
+ if (recordId === 0xff) {
+ // If the value in IAP[index] is 0xff, which means the contact stored on
+ // the SIM doesn't have the additional attribute (email or anr).
+ // So if the contact to be updated doesn't have the attribute either,
+ // we don't have to update it.
+ if ((field === USIM_PBR_EMAIL && contact.email) ||
+ (field === USIM_PBR_ANR0 &&
+ (Array.isArray(contact.anr) && contact.anr[0]))) {
+ // Case 1.
+ this.addContactFieldType2(pbr, contact, field, onsuccess, onerror);
+ } else {
+ if (onsuccess) {
+ onsuccess();
+ }
+ }
+ return;
+ }
+
+ // Case 2.
+ if (field === USIM_PBR_EMAIL) {
+ ICCRecordHelper.updateEmail(pbr, recordId, contact.email, contact.recordId,
+ (updatedEmail) => {
+ onsuccess({email: updatedEmail});
+ }, onerror);
+ } else if (field === USIM_PBR_ANR0) {
+ let anr = Array.isArray(contact.anr) ? contact.anr[0] : null;
+ ICCRecordHelper.updateANR(pbr, recordId, anr, contact.recordId,
+ (updatedANR) => {
+ // ANR could have multiple files. If we support more than one anr,
+ // we will save it as anr0, anr1,...etc.
+ onsuccess((updatedANR) ? {anr: [updatedANR]} : null);
+ }, onerror);
+ } else {
+ if (DEBUG) {
+ this.context.debug("Unsupported field :" + field);
+ }
+ onerror(CONTACT_ERR_FIELD_NOT_SUPPORTED);
+ }
+
+ }.bind(this);
+
+ ICCRecordHelper.readIAP(pbr.iap.fileId, contact.recordId, gotIapCb, onerror);
+ },
+
+ /**
+ * Add Type 2 USIM contact fields.
+ *
+ * @param pbr The phonebook reference file.
+ * @param contact The contact needs to be updated.
+ * @param field Phonebook field to be updated.
+ * @param onsuccess Callback to be called when success.
+ * @param onerror Callback to be called when error.
+ */
+ addContactFieldType2: function(pbr, contact, field, onsuccess, onerror) {
+ let ICCRecordHelper = this.context.ICCRecordHelper;
+ let successCb = function successCb(recordId) {
+
+ let updateCb = function updateCb(contactField) {
+ this.updateContactFieldIndexInIAP(pbr, contact.recordId, field, recordId, () => {
+ onsuccess(contactField);
+ }, onerror);
+ }.bind(this);
+
+ if (field === USIM_PBR_EMAIL) {
+ ICCRecordHelper.updateEmail(pbr, recordId, contact.email, contact.recordId,
+ (updatedEmail) => {
+ updateCb({email: updatedEmail});
+ }, onerror);
+ } else if (field === USIM_PBR_ANR0) {
+ ICCRecordHelper.updateANR(pbr, recordId, contact.anr[0], contact.recordId,
+ (updatedANR) => {
+ // ANR could have multiple files. If we support more than one anr,
+ // we will save it as anr0, anr1,...etc.
+ updateCb((updatedANR) ? {anr: [updatedANR]} : null);
+ }, onerror);
+ }
+ }.bind(this);
+
+ let errorCb = function errorCb(errorMsg) {
+ if (DEBUG) {
+ this.context.debug(errorMsg + " USIM field " + field);
+ }
+ onerror(errorMsg);
+ }.bind(this);
+
+ ICCRecordHelper.findFreeRecordId(pbr[field].fileId, successCb, errorCb);
+ },
+
+ /**
+ * Update IAP value.
+ *
+ * @param pbr The phonebook reference file.
+ * @param recordNumber The record identifier of EF_IAP.
+ * @param field Phonebook field.
+ * @param value The value of 'field' in IAP.
+ * @param onsuccess Callback to be called when success.
+ * @param onerror Callback to be called when error.
+ *
+ */
+ updateContactFieldIndexInIAP: function(pbr, recordNumber, field, value, onsuccess, onerror) {
+ let ICCRecordHelper = this.context.ICCRecordHelper;
+
+ let gotIAPCb = function gotIAPCb(iap) {
+ iap[pbr[field].indexInIAP] = value;
+ ICCRecordHelper.updateIAP(pbr.iap.fileId, recordNumber, iap, onsuccess, onerror);
+ }.bind(this);
+ ICCRecordHelper.readIAP(pbr.iap.fileId, recordNumber, gotIAPCb, onerror);
+ },
+
+ /**
+ * Update ICC ADN like EFs with Extension, like EF_ADN, EF_FDN.
+ *
+ * @param fileId EF id of the ADN or FDN.
+ * @param extFileId EF id of the EXT.
+ * @param contact The contact will be updated. (Shall have recordId property)
+ * @param pin2 PIN2 is required when updating ICC_EF_FDN.
+ * @param onsuccess Callback to be called when success.
+ * @param onerror Callback to be called when error.
+ */
+ updateADNLikeWithExtension: function(fileId, extFileId, contact, pin2, onsuccess, onerror) {
+ let ICCRecordHelper = this.context.ICCRecordHelper;
+ let extNumber;
+
+ if (contact.number) {
+ let numStart = contact.number[0] == "+" ? 1 : 0;
+ let number = contact.number.substring(0, numStart) +
+ this.context.GsmPDUHelper.stringToExtendedBcd(
+ contact.number.substring(numStart));
+ extNumber = number.substr(numStart + ADN_MAX_NUMBER_DIGITS,
+ EXT_MAX_NUMBER_DIGITS);
+ }
+
+ ICCRecordHelper.getADNLikeExtensionRecordNumber(fileId, contact.recordId,
+ (extRecordNumber) => {
+ let updateADNLike = (extRecordNumber) => {
+ ICCRecordHelper.updateADNLike(fileId, extRecordNumber, contact,
+ pin2, (updatedContact) => {
+ if (extNumber && extRecordNumber != 0xff) {
+ updatedContact.number = updatedContact.number.concat(extNumber);
+ }
+ onsuccess(updatedContact);
+ }, onerror);
+ };
+
+ let updateExtension = (extRecordNumber) => {
+ ICCRecordHelper.updateExtension(extFileId, extRecordNumber, extNumber,
+ () => updateADNLike(extRecordNumber),
+ () => updateADNLike(0xff));
+ };
+
+ if (extNumber) {
+ if (extRecordNumber != 0xff) {
+ updateExtension(extRecordNumber);
+ return;
+ }
+
+ ICCRecordHelper.findFreeRecordId(extFileId,
+ (extRecordNumber) => updateExtension(extRecordNumber),
+ (errorMsg) => {
+ if (DEBUG) {
+ this.context.debug("Couldn't find free extension record Id for " + extFileId + ": " + errorMsg);
+ }
+ updateADNLike(0xff);
+ });
+ return;
+ }
+
+ if (extRecordNumber != 0xff) {
+ ICCRecordHelper.cleanEFRecord(extFileId, extRecordNumber,
+ () => updateADNLike(0xff), onerror);
+ return;
+ }
+
+ updateADNLike(0xff);
+ }, onerror);
+ },
+};
+
+function IconLoaderObject(aContext) {
+ this.context = aContext;
+}
+IconLoaderObject.prototype = {
+ context: null,
+
+ /**
+ * Load icons.
+ *
+ * @param recordNumbers Array of the record identifiers of EF_IMG.
+ * @param onsuccess Callback to be called when success.
+ * @param onerror Callback to be called when error.
+ */
+ loadIcons: function(recordNumbers, onsuccess, onerror) {
+ if (!recordNumbers || !recordNumbers.length) {
+ if (onerror) {
+ onerror();
+ }
+ return;
+ }
+
+ this._start({
+ recordNumbers: recordNumbers,
+ onsuccess: onsuccess,
+ onerror: onerror});
+ },
+
+ _start: function(options) {
+ let callback = (function(icons) {
+ if (!options.icons) {
+ options.icons = [];
+ }
+ for (let i = 0; i < icons.length; i++) {
+ icons[i] = this._parseRawData(icons[i]);
+ }
+ options.icons[options.currentRecordIndex] = icons;
+ options.currentRecordIndex++;
+
+ let recordNumbers = options.recordNumbers;
+ if (options.currentRecordIndex < recordNumbers.length) {
+ let recordNumber = recordNumbers[options.currentRecordIndex];
+ this.context.SimRecordHelper.readIMG(recordNumber,
+ callback,
+ options.onerror);
+ } else {
+ if (options.onsuccess) {
+ options.onsuccess(options.icons);
+ }
+ }
+ }).bind(this);
+
+ options.currentRecordIndex = 0;
+ this.context.SimRecordHelper.readIMG(options.recordNumbers[0],
+ callback,
+ options.onerror);
+ },
+
+ _parseRawData: function(rawData) {
+ let codingScheme = rawData.codingScheme;
+
+ switch (codingScheme) {
+ case ICC_IMG_CODING_SCHEME_BASIC:
+ return this._decodeBasicImage(rawData.width, rawData.height, rawData.body);
+
+ case ICC_IMG_CODING_SCHEME_COLOR:
+ case ICC_IMG_CODING_SCHEME_COLOR_TRANSPARENCY:
+ return this._decodeColorImage(codingScheme,
+ rawData.width, rawData.height,
+ rawData.bitsPerImgPoint,
+ rawData.numOfClutEntries,
+ rawData.clut, rawData.body);
+ }
+
+ return null;
+ },
+
+ _decodeBasicImage: function(width, height, body) {
+ let numOfPixels = width * height;
+ let pixelIndex = 0;
+ let currentByteIndex = 0;
+ let currentByte = 0x00;
+
+ const BLACK = 0x000000FF;
+ const WHITE = 0xFFFFFFFF;
+
+ let pixels = [];
+ while (pixelIndex < numOfPixels) {
+ // Reassign data and index for every byte (8 bits).
+ if (pixelIndex % 8 == 0) {
+ currentByte = body[currentByteIndex++];
+ }
+ let bit = (currentByte >> (7 - (pixelIndex % 8))) & 0x01;
+ pixels[pixelIndex++] = bit ? WHITE : BLACK;
+ }
+
+ return {pixels: pixels,
+ codingScheme: GECKO_IMG_CODING_SCHEME_BASIC,
+ width: width,
+ height: height};
+ },
+
+ _decodeColorImage: function(codingScheme, width, height, bitsPerImgPoint,
+ numOfClutEntries, clut, body) {
+ let mask = 0xff >> (8 - bitsPerImgPoint);
+ let bitsStartOffset = 8 - bitsPerImgPoint;
+ let bitIndex = bitsStartOffset;
+ let numOfPixels = width * height;
+ let pixelIndex = 0;
+ let currentByteIndex = 0;
+ let currentByte = body[currentByteIndex++];
+
+ let pixels = [];
+ while (pixelIndex < numOfPixels) {
+ // Reassign data and index for every byte (8 bits).
+ if (bitIndex < 0) {
+ currentByte = body[currentByteIndex++];
+ bitIndex = bitsStartOffset;
+ }
+ let clutEntry = ((currentByte >> bitIndex) & mask);
+ let clutIndex = clutEntry * ICC_CLUT_ENTRY_SIZE;
+ let alpha = codingScheme == ICC_IMG_CODING_SCHEME_COLOR_TRANSPARENCY &&
+ clutEntry == numOfClutEntries - 1;
+ pixels[pixelIndex++] = alpha ? 0x00
+ : (clut[clutIndex] << 24 |
+ clut[clutIndex + 1] << 16 |
+ clut[clutIndex + 2] << 8 |
+ 0xFF) >>> 0;
+ bitIndex -= bitsPerImgPoint;
+ }
+
+ return {pixels: pixels,
+ codingScheme: ICC_IMG_CODING_SCHEME_TO_GECKO[codingScheme],
+ width: width,
+ height: height};
+ },
+};
+
+/**
+ * Global stuff.
+ */
+
+function Context(aClientId) {
+ this.clientId = aClientId;
+
+ this.Buf = new BufObject(this);
+ this.RIL = new RilObject(this);
+ this.RIL.initRILState();
+}
+Context.prototype = {
+ clientId: null,
+ Buf: null,
+ RIL: null,
+
+ debug: function(aMessage) {
+ GLOBAL.debug("[" + this.clientId + "] " + aMessage);
+ }
+};
+
+(function() {
+ let lazySymbols = [
+ "BerTlvHelper", "BitBufferHelper", "CdmaPDUHelper",
+ "ComprehensionTlvHelper", "GsmPDUHelper", "ICCContactHelper",
+ "ICCFileHelper", "ICCIOHelper", "ICCPDUHelper", "ICCRecordHelper",
+ "ICCUtilsHelper", "RuimRecordHelper", "SimRecordHelper",
+ "StkCommandParamsFactory", "StkProactiveCmdHelper", "IconLoader",
+ ];
+
+ for (let i = 0; i < lazySymbols.length; i++) {
+ let symbol = lazySymbols[i];
+ Object.defineProperty(Context.prototype, symbol, {
+ get: function() {
+ let real = new GLOBAL[symbol + "Object"](this);
+ Object.defineProperty(this, symbol, {
+ value: real,
+ enumerable: true
+ });
+ return real;
+ },
+ configurable: true,
+ enumerable: true
+ });
+ }
+})();
+
+var ContextPool = {
+ _contexts: [],
+
+ handleRilMessage: function(aClientId, aUint8Array) {
+ let context = this._contexts[aClientId];
+ context.Buf.processIncoming(aUint8Array);
+ },
+
+ handleChromeMessage: function(aMessage) {
+ let clientId = aMessage.rilMessageClientId;
+ if (clientId != null) {
+ let context = this._contexts[clientId];
+ context.RIL.handleChromeMessage(aMessage);
+ return;
+ }
+
+ if (DEBUG) debug("Received global chrome message " + JSON.stringify(aMessage));
+ let method = this[aMessage.rilMessageType];
+ if (typeof method != "function") {
+ if (DEBUG) {
+ debug("Don't know what to do");
+ }
+ return;
+ }
+ method.call(this, aMessage);
+ },
+
+ setInitialOptions: function(aOptions) {
+ DEBUG = DEBUG_WORKER || aOptions.debug;
+
+ let quirks = aOptions.quirks;
+ RILQUIRKS_CALLSTATE_EXTRA_UINT32 = quirks.callstateExtraUint32;
+ RILQUIRKS_REQUEST_USE_DIAL_EMERGENCY_CALL = quirks.requestUseDialEmergencyCall;
+ RILQUIRKS_SIM_APP_STATE_EXTRA_FIELDS = quirks.simAppStateExtraFields;
+ RILQUIRKS_EXTRA_UINT32_2ND_CALL = quirks.extraUint2ndCall;
+ RILQUIRKS_HAVE_QUERY_ICC_LOCK_RETRY_COUNT = quirks.haveQueryIccLockRetryCount;
+ RILQUIRKS_SEND_STK_PROFILE_DOWNLOAD = quirks.sendStkProfileDownload;
+ RILQUIRKS_DATA_REGISTRATION_ON_DEMAND = quirks.dataRegistrationOnDemand;
+ RILQUIRKS_SUBSCRIPTION_CONTROL = quirks.subscriptionControl;
+ RILQUIRKS_SIGNAL_EXTRA_INT32 = quirks.signalExtraInt;
+ RILQUIRKS_AVAILABLE_NETWORKS_EXTRA_STRING = quirks.availableNetworkExtraStr;
+ RILQUIRKS_SMSC_ADDRESS_FORMAT = quirks.smscAddressFormat;
+ },
+
+ setDebugFlag: function(aOptions) {
+ DEBUG = DEBUG_WORKER || aOptions.debug;
+ },
+
+ registerClient: function(aOptions) {
+ let clientId = aOptions.clientId;
+ this._contexts[clientId] = new Context(clientId);
+ },
+};
+
+function onRILMessage(aClientId, aUint8Array) {
+ ContextPool.handleRilMessage(aClientId, aUint8Array);
+}
+
+onmessage = function onmessage(event) {
+ ContextPool.handleChromeMessage(event.data);
+};
+
+onerror = function onerror(event) {
+ if (DEBUG) debug("onerror" + event.message + "\n");
+};