diff options
Diffstat (limited to 'dom/system/gonk/ril_worker.js')
-rw-r--r-- | dom/system/gonk/ril_worker.js | 15206 |
1 files changed, 0 insertions, 15206 deletions
diff --git a/dom/system/gonk/ril_worker.js b/dom/system/gonk/ril_worker.js deleted file mode 100644 index 0608a5be3..000000000 --- a/dom/system/gonk/ril_worker.js +++ /dev/null @@ -1,15206 +0,0 @@ -/* 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"); -}; |