michael@0: /* Copyright 2012 Mozilla Foundation and Mozilla contributors michael@0: * michael@0: * Licensed under the Apache License, Version 2.0 (the "License"); michael@0: * you may not use this file except in compliance with the License. michael@0: * You may obtain a copy of the License at michael@0: * michael@0: * http://www.apache.org/licenses/LICENSE-2.0 michael@0: * michael@0: * Unless required by applicable law or agreed to in writing, software michael@0: * distributed under the License is distributed on an "AS IS" BASIS, michael@0: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. michael@0: * See the License for the specific language governing permissions and michael@0: * limitations under the License. michael@0: */ michael@0: michael@0: /** michael@0: * This file implements the RIL worker thread. It communicates with michael@0: * the main thread to provide a high-level API to the phone's RIL michael@0: * stack, and with the RIL IPC thread to communicate with the RIL michael@0: * device itself. These communication channels use message events as michael@0: * known from Web Workers: michael@0: * michael@0: * - postMessage()/"message" events for main thread communication michael@0: * michael@0: * - postRILMessage()/"RILMessageEvent" events for RIL IPC thread michael@0: * communication. michael@0: * michael@0: * The two main objects in this file represent individual parts of this michael@0: * communication chain: michael@0: * michael@0: * - RILMessageEvent -> Buf -> RIL -> postMessage() -> nsIRadioInterfaceLayer michael@0: * - nsIRadioInterfaceLayer -> postMessage() -> RIL -> Buf -> postRILMessage() michael@0: * michael@0: * Note: The code below is purposely lean on abstractions to be as lean in michael@0: * terms of object allocations. As a result, it may look more like C than michael@0: * JavaScript, and that's intended. michael@0: */ michael@0: michael@0: "use strict"; michael@0: michael@0: importScripts("ril_consts.js"); michael@0: importScripts("resource://gre/modules/workers/require.js"); michael@0: michael@0: // set to true in ril_consts.js to see debug messages michael@0: let DEBUG = DEBUG_WORKER; michael@0: let GLOBAL = this; michael@0: michael@0: if (!this.debug) { michael@0: // Debugging stub that goes nowhere. michael@0: this.debug = function debug(message) { michael@0: dump("RIL Worker: " + message + "\n"); michael@0: }; michael@0: } michael@0: michael@0: let RIL_CELLBROADCAST_DISABLED; michael@0: let RIL_CLIR_MODE; michael@0: let RIL_EMERGENCY_NUMBERS; michael@0: const DEFAULT_EMERGENCY_NUMBERS = ["112", "911"]; michael@0: michael@0: // Timeout value for emergency callback mode. michael@0: const EMERGENCY_CB_MODE_TIMEOUT_MS = 300000; // 5 mins = 300000 ms. michael@0: michael@0: const ICC_MAX_LINEAR_FIXED_RECORDS = 0xfe; michael@0: michael@0: // MMI match groups michael@0: const MMI_MATCH_GROUP_FULL_MMI = 1; michael@0: const MMI_MATCH_GROUP_MMI_PROCEDURE = 2; michael@0: const MMI_MATCH_GROUP_SERVICE_CODE = 3; michael@0: const MMI_MATCH_GROUP_SIA = 5; michael@0: const MMI_MATCH_GROUP_SIB = 7; michael@0: const MMI_MATCH_GROUP_SIC = 9; michael@0: const MMI_MATCH_GROUP_PWD_CONFIRM = 11; michael@0: const MMI_MATCH_GROUP_DIALING_NUMBER = 12; michael@0: michael@0: const MMI_MAX_LENGTH_SHORT_CODE = 2; michael@0: michael@0: const MMI_END_OF_USSD = "#"; michael@0: michael@0: // Should match the value we set in dom/telephony/TelephonyCommon.h michael@0: const OUTGOING_PLACEHOLDER_CALL_INDEX = 0xffffffff; michael@0: michael@0: let RILQUIRKS_CALLSTATE_EXTRA_UINT32; michael@0: // This may change at runtime since in RIL v6 and later, we get the version michael@0: // number via the UNSOLICITED_RIL_CONNECTED parcel. michael@0: let RILQUIRKS_V5_LEGACY; michael@0: let RILQUIRKS_REQUEST_USE_DIAL_EMERGENCY_CALL; michael@0: let RILQUIRKS_SIM_APP_STATE_EXTRA_FIELDS; michael@0: // Needed for call-waiting on Peak device michael@0: let RILQUIRKS_EXTRA_UINT32_2ND_CALL; michael@0: // On the emulator we support querying the number of lock retries michael@0: let RILQUIRKS_HAVE_QUERY_ICC_LOCK_RETRY_COUNT; michael@0: michael@0: // Ril quirk to Send STK Profile Download michael@0: let RILQUIRKS_SEND_STK_PROFILE_DOWNLOAD; michael@0: michael@0: // Ril quirk to attach data registration on demand. michael@0: let RILQUIRKS_DATA_REGISTRATION_ON_DEMAND; michael@0: michael@0: function BufObject(aContext) { michael@0: this.context = aContext; michael@0: } michael@0: BufObject.prototype = { michael@0: context: null, michael@0: michael@0: mToken: 0, michael@0: mTokenRequestMap: null, michael@0: michael@0: init: function() { michael@0: this._init(); michael@0: michael@0: // This gets incremented each time we send out a parcel. michael@0: this.mToken = 1; michael@0: michael@0: // Maps tokens we send out with requests to the request type, so that michael@0: // when we get a response parcel back, we know what request it was for. michael@0: this.mTokenRequestMap = new Map(); michael@0: }, michael@0: michael@0: /** michael@0: * Process one parcel. michael@0: */ michael@0: processParcel: function() { michael@0: let response_type = this.readInt32(); michael@0: michael@0: let request_type, options; michael@0: if (response_type == RESPONSE_TYPE_SOLICITED) { michael@0: let token = this.readInt32(); michael@0: let error = this.readInt32(); michael@0: michael@0: options = this.mTokenRequestMap.get(token); michael@0: if (!options) { michael@0: if (DEBUG) { michael@0: this.context.debug("Suspicious uninvited request found: " + michael@0: token + ". Ignored!"); michael@0: } michael@0: return; michael@0: } michael@0: michael@0: this.mTokenRequestMap.delete(token); michael@0: request_type = options.rilRequestType; michael@0: michael@0: options.rilRequestError = error; michael@0: if (DEBUG) { michael@0: this.context.debug("Solicited response for request type " + request_type + michael@0: ", token " + token + ", error " + error); michael@0: } michael@0: } else if (response_type == RESPONSE_TYPE_UNSOLICITED) { michael@0: request_type = this.readInt32(); michael@0: if (DEBUG) { michael@0: this.context.debug("Unsolicited response for request type " + request_type); michael@0: } michael@0: } else { michael@0: if (DEBUG) { michael@0: this.context.debug("Unknown response type: " + response_type); michael@0: } michael@0: return; michael@0: } michael@0: michael@0: this.context.RIL.handleParcel(request_type, this.readAvailable, options); michael@0: }, michael@0: michael@0: /** michael@0: * Start a new outgoing parcel. michael@0: * michael@0: * @param type michael@0: * Integer specifying the request type. michael@0: * @param options [optional] michael@0: * Object containing information about the request, e.g. the michael@0: * original main thread message object that led to the RIL request. michael@0: */ michael@0: newParcel: function(type, options) { michael@0: if (DEBUG) this.context.debug("New outgoing parcel of type " + type); michael@0: michael@0: // We're going to leave room for the parcel size at the beginning. michael@0: this.outgoingIndex = this.PARCEL_SIZE_SIZE; michael@0: this.writeInt32(type); michael@0: this.writeInt32(this.mToken); michael@0: michael@0: if (!options) { michael@0: options = {}; michael@0: } michael@0: options.rilRequestType = type; michael@0: options.rilRequestError = null; michael@0: this.mTokenRequestMap.set(this.mToken, options); michael@0: this.mToken++; michael@0: return this.mToken; michael@0: }, michael@0: michael@0: simpleRequest: function(type, options) { michael@0: this.newParcel(type, options); michael@0: this.sendParcel(); michael@0: }, michael@0: michael@0: onSendParcel: function(parcel) { michael@0: postRILMessage(this.context.clientId, parcel); michael@0: } michael@0: }; michael@0: michael@0: (function() { michael@0: let base = require("resource://gre/modules/workers/worker_buf.js").Buf; michael@0: for (let p in base) { michael@0: BufObject.prototype[p] = base[p]; michael@0: } michael@0: })(); michael@0: michael@0: /** michael@0: * The RIL state machine. michael@0: * michael@0: * This object communicates with rild via parcels and with the main thread michael@0: * via post messages. It maintains state about the radio, ICC, calls, etc. michael@0: * and acts upon state changes accordingly. michael@0: */ michael@0: function RilObject(aContext) { michael@0: this.context = aContext; michael@0: michael@0: this.currentCalls = {}; michael@0: this.currentConference = {state: null, participants: {}}; michael@0: this.currentDataCalls = {}; michael@0: this._pendingSentSmsMap = {}; michael@0: this.pendingNetworkType = {}; michael@0: this._receivedSmsCbPagesMap = {}; michael@0: michael@0: // Init properties that are only initialized once. michael@0: this.v5Legacy = RILQUIRKS_V5_LEGACY; michael@0: this.cellBroadcastDisabled = RIL_CELLBROADCAST_DISABLED; michael@0: this.clirMode = RIL_CLIR_MODE; michael@0: } michael@0: RilObject.prototype = { michael@0: context: null, michael@0: michael@0: v5Legacy: null, michael@0: michael@0: /** michael@0: * Valid calls. michael@0: */ michael@0: currentCalls: null, michael@0: michael@0: /** michael@0: * Existing conference call and its participants. michael@0: */ michael@0: currentConference: null, michael@0: michael@0: /** michael@0: * Existing data calls. michael@0: */ michael@0: currentDataCalls: null, michael@0: michael@0: /** michael@0: * Outgoing messages waiting for SMS-STATUS-REPORT. michael@0: */ michael@0: _pendingSentSmsMap: null, michael@0: michael@0: /** michael@0: * Index of the RIL_PREFERRED_NETWORK_TYPE_TO_GECKO. Its value should be michael@0: * preserved over rild reset. michael@0: */ michael@0: preferredNetworkType: null, michael@0: michael@0: /** michael@0: * Marker object. michael@0: */ michael@0: pendingNetworkType: null, michael@0: michael@0: /** michael@0: * Global Cell Broadcast switch. michael@0: */ michael@0: cellBroadcastDisabled: false, michael@0: michael@0: /** michael@0: * Global CLIR mode settings. michael@0: */ michael@0: clirMode: CLIR_DEFAULT, michael@0: michael@0: /** michael@0: * Parsed Cell Broadcast search lists. michael@0: * cellBroadcastConfigs.MMI should be preserved over rild reset. michael@0: */ michael@0: cellBroadcastConfigs: null, michael@0: mergedCellBroadcastConfig: null, michael@0: michael@0: _receivedSmsCbPagesMap: null, michael@0: michael@0: initRILState: function() { michael@0: /** michael@0: * One of the RADIO_STATE_* constants. michael@0: */ michael@0: this.radioState = GECKO_RADIOSTATE_UNAVAILABLE; michael@0: michael@0: /** michael@0: * True if we are on a CDMA phone. michael@0: */ michael@0: this._isCdma = false; michael@0: michael@0: /** michael@0: * True if we are in emergency callback mode. michael@0: */ michael@0: this._isInEmergencyCbMode = false; michael@0: michael@0: /** michael@0: * Set when radio is ready but radio tech is unknown. That is, we are michael@0: * waiting for REQUEST_VOICE_RADIO_TECH michael@0: */ michael@0: this._waitingRadioTech = false; michael@0: michael@0: /** michael@0: * ICC status. Keeps a reference of the data response to the michael@0: * getICCStatus request. michael@0: */ michael@0: this.iccStatus = null; michael@0: michael@0: /** michael@0: * Card state michael@0: */ michael@0: this.cardState = GECKO_CARDSTATE_UNINITIALIZED; michael@0: michael@0: /** michael@0: * Strings michael@0: */ michael@0: this.IMEI = null; michael@0: this.IMEISV = null; michael@0: this.ESN = null; michael@0: this.MEID = null; michael@0: this.SMSC = null; michael@0: michael@0: /** michael@0: * ICC information that is not exposed to Gaia. michael@0: */ michael@0: this.iccInfoPrivate = {}; michael@0: michael@0: /** michael@0: * ICC information, such as MSISDN, MCC, MNC, SPN...etc. michael@0: */ michael@0: this.iccInfo = {}; michael@0: michael@0: /** michael@0: * CDMA specific information. ex. CDMA Network ID, CDMA System ID... etc. michael@0: */ michael@0: this.cdmaHome = null; michael@0: michael@0: /** michael@0: * Application identification for apps in ICC. michael@0: */ michael@0: this.aid = null; michael@0: michael@0: /** michael@0: * Application type for apps in ICC. michael@0: */ michael@0: this.appType = null; michael@0: michael@0: this.networkSelectionMode = null; michael@0: michael@0: this.voiceRegistrationState = {}; michael@0: this.dataRegistrationState = {}; michael@0: michael@0: /** michael@0: * List of strings identifying the network operator. michael@0: */ michael@0: this.operator = null; michael@0: michael@0: /** michael@0: * String containing the baseband version. michael@0: */ michael@0: this.basebandVersion = null; michael@0: michael@0: // Clean up this.currentCalls: rild might have restarted. michael@0: for each (let currentCall in this.currentCalls) { michael@0: delete this.currentCalls[currentCall.callIndex]; michael@0: this._handleDisconnectedCall(currentCall); michael@0: } michael@0: michael@0: // Deactivate this.currentDataCalls: rild might have restarted. michael@0: for each (let datacall in this.currentDataCalls) { michael@0: this.deactivateDataCall(datacall); michael@0: } michael@0: michael@0: // Don't clean up this._pendingSentSmsMap michael@0: // because on rild restart: we may continue with the pending segments. michael@0: michael@0: /** michael@0: * Whether or not the multiple requests in requestNetworkInfo() are currently michael@0: * being processed michael@0: */ michael@0: this._processingNetworkInfo = false; michael@0: michael@0: /** michael@0: * Multiple requestNetworkInfo() in a row before finishing the first michael@0: * request, hence we need to fire requestNetworkInfo() again after michael@0: * gathering all necessary stuffs. This is to make sure that ril_worker michael@0: * gets precise network information. michael@0: */ michael@0: this._needRepollNetworkInfo = false; michael@0: michael@0: /** michael@0: * Pending messages to be send in batch from requestNetworkInfo() michael@0: */ michael@0: this._pendingNetworkInfo = {rilMessageType: "networkinfochanged"}; michael@0: michael@0: /** michael@0: * USSD session flag. michael@0: * Only one USSD session may exist at a time, and the session is assumed michael@0: * to exist until: michael@0: * a) There's a call to cancelUSSD() michael@0: * b) The implementation sends a UNSOLICITED_ON_USSD with a type code michael@0: * of "0" (USSD-Notify/no further action) or "2" (session terminated) michael@0: */ michael@0: this._ussdSession = null; michael@0: michael@0: /** michael@0: * Regular expresion to parse MMI codes. michael@0: */ michael@0: this._mmiRegExp = null; michael@0: michael@0: /** michael@0: * Cell Broadcast Search Lists. michael@0: */ michael@0: let cbmmi = this.cellBroadcastConfigs && this.cellBroadcastConfigs.MMI; michael@0: this.cellBroadcastConfigs = { michael@0: MMI: cbmmi || null michael@0: }; michael@0: this.mergedCellBroadcastConfig = null; michael@0: }, michael@0: michael@0: /** michael@0: * Parse an integer from a string, falling back to a default value michael@0: * if the the provided value is not a string or does not contain a valid michael@0: * number. michael@0: * michael@0: * @param string michael@0: * String to be parsed. michael@0: * @param defaultValue [optional] michael@0: * Default value to be used. michael@0: * @param radix [optional] michael@0: * A number that represents the numeral system to be used. Default 10. michael@0: */ michael@0: parseInt: function(string, defaultValue, radix) { michael@0: let number = parseInt(string, radix || 10); michael@0: if (!isNaN(number)) { michael@0: return number; michael@0: } michael@0: if (defaultValue === undefined) { michael@0: defaultValue = null; michael@0: } michael@0: return defaultValue; michael@0: }, michael@0: michael@0: michael@0: /** michael@0: * Outgoing requests to the RIL. These can be triggered from the michael@0: * main thread via messages that look like this: michael@0: * michael@0: * {rilMessageType: "methodName", michael@0: * extra: "parameters", michael@0: * go: "here"} michael@0: * michael@0: * So if one of the following methods takes arguments, it takes only one, michael@0: * an object, which then contains all of the parameters as attributes. michael@0: * The "@param" documentation is to be interpreted accordingly. michael@0: */ michael@0: michael@0: /** michael@0: * Retrieve the ICC's status. michael@0: */ michael@0: getICCStatus: function() { michael@0: this.context.Buf.simpleRequest(REQUEST_GET_SIM_STATUS); michael@0: }, michael@0: michael@0: /** michael@0: * Helper function for unlocking ICC locks. michael@0: */ michael@0: iccUnlockCardLock: function(options) { michael@0: switch (options.lockType) { michael@0: case GECKO_CARDLOCK_PIN: michael@0: this.enterICCPIN(options); michael@0: break; michael@0: case GECKO_CARDLOCK_PIN2: michael@0: this.enterICCPIN2(options); michael@0: break; michael@0: case GECKO_CARDLOCK_PUK: michael@0: this.enterICCPUK(options); michael@0: break; michael@0: case GECKO_CARDLOCK_PUK2: michael@0: this.enterICCPUK2(options); michael@0: break; michael@0: case GECKO_CARDLOCK_NCK: michael@0: case GECKO_CARDLOCK_NCK1: michael@0: case GECKO_CARDLOCK_NCK2: michael@0: case GECKO_CARDLOCK_HNCK: michael@0: case GECKO_CARDLOCK_CCK: michael@0: case GECKO_CARDLOCK_SPCK: michael@0: case GECKO_CARDLOCK_RCCK: // Fall through. michael@0: case GECKO_CARDLOCK_RSPCK: { michael@0: let type = GECKO_PERSO_LOCK_TO_CARD_PERSO_LOCK[options.lockType]; michael@0: this.enterDepersonalization(type, options.pin, options); michael@0: break; michael@0: } michael@0: case GECKO_CARDLOCK_NCK_PUK: michael@0: case GECKO_CARDLOCK_NCK1_PUK: michael@0: case GECKO_CARDLOCK_NCK2_PUK: michael@0: case GECKO_CARDLOCK_HNCK_PUK: michael@0: case GECKO_CARDLOCK_CCK_PUK: michael@0: case GECKO_CARDLOCK_SPCK_PUK: michael@0: case GECKO_CARDLOCK_RCCK_PUK: // Fall through. michael@0: case GECKO_CARDLOCK_RSPCK_PUK: { michael@0: let type = GECKO_PERSO_LOCK_TO_CARD_PERSO_LOCK[options.lockType]; michael@0: this.enterDepersonalization(type, options.puk, options); michael@0: break; michael@0: } michael@0: default: michael@0: options.errorMsg = "Unsupported Card Lock."; michael@0: options.success = false; michael@0: this.sendChromeMessage(options); michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Enter a PIN to unlock the ICC. michael@0: * michael@0: * @param pin michael@0: * String containing the PIN. michael@0: * @param [optional] aid michael@0: * AID value. michael@0: */ michael@0: enterICCPIN: function(options) { michael@0: let Buf = this.context.Buf; michael@0: Buf.newParcel(REQUEST_ENTER_SIM_PIN, options); michael@0: Buf.writeInt32(this.v5Legacy ? 1 : 2); michael@0: Buf.writeString(options.pin); michael@0: if (!this.v5Legacy) { michael@0: Buf.writeString(options.aid || this.aid); michael@0: } michael@0: Buf.sendParcel(); michael@0: }, michael@0: michael@0: /** michael@0: * Enter a PIN2 to unlock the ICC. michael@0: * michael@0: * @param pin michael@0: * String containing the PIN2. michael@0: * @param [optional] aid michael@0: * AID value. michael@0: */ michael@0: enterICCPIN2: function(options) { michael@0: let Buf = this.context.Buf; michael@0: Buf.newParcel(REQUEST_ENTER_SIM_PIN2, options); michael@0: Buf.writeInt32(this.v5Legacy ? 1 : 2); michael@0: Buf.writeString(options.pin); michael@0: if (!this.v5Legacy) { michael@0: Buf.writeString(options.aid || this.aid); michael@0: } michael@0: Buf.sendParcel(); michael@0: }, michael@0: michael@0: /** michael@0: * Requests a network personalization be deactivated. michael@0: * michael@0: * @param type michael@0: * Integer indicating the network personalization be deactivated. michael@0: * @param password michael@0: * String containing the password. michael@0: */ michael@0: enterDepersonalization: function(type, password, options) { michael@0: let Buf = this.context.Buf; michael@0: Buf.newParcel(REQUEST_ENTER_NETWORK_DEPERSONALIZATION_CODE, options); michael@0: Buf.writeInt32(type); michael@0: Buf.writeString(password); michael@0: Buf.sendParcel(); michael@0: }, michael@0: michael@0: /** michael@0: * Helper function for changing ICC locks. michael@0: */ michael@0: iccSetCardLock: function(options) { michael@0: if (options.newPin !== undefined) { // Change PIN lock. michael@0: switch (options.lockType) { michael@0: case GECKO_CARDLOCK_PIN: michael@0: this.changeICCPIN(options); michael@0: break; michael@0: case GECKO_CARDLOCK_PIN2: michael@0: this.changeICCPIN2(options); michael@0: break; michael@0: default: michael@0: options.errorMsg = "Unsupported Card Lock."; michael@0: options.success = false; michael@0: this.sendChromeMessage(options); michael@0: } michael@0: } else { // Enable/Disable lock. michael@0: switch (options.lockType) { michael@0: case GECKO_CARDLOCK_PIN: michael@0: options.facility = ICC_CB_FACILITY_SIM; michael@0: options.password = options.pin; michael@0: break; michael@0: case GECKO_CARDLOCK_FDN: michael@0: options.facility = ICC_CB_FACILITY_FDN; michael@0: options.password = options.pin2; michael@0: break; michael@0: default: michael@0: options.errorMsg = "Unsupported Card Lock."; michael@0: options.success = false; michael@0: this.sendChromeMessage(options); michael@0: return; michael@0: } michael@0: options.enabled = options.enabled; michael@0: options.serviceClass = ICC_SERVICE_CLASS_VOICE | michael@0: ICC_SERVICE_CLASS_DATA | michael@0: ICC_SERVICE_CLASS_FAX; michael@0: this.setICCFacilityLock(options); michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Change the current ICC PIN number. michael@0: * michael@0: * @param pin michael@0: * String containing the old PIN value michael@0: * @param newPin michael@0: * String containing the new PIN value michael@0: * @param [optional] aid michael@0: * AID value. michael@0: */ michael@0: changeICCPIN: function(options) { michael@0: let Buf = this.context.Buf; michael@0: Buf.newParcel(REQUEST_CHANGE_SIM_PIN, options); michael@0: Buf.writeInt32(this.v5Legacy ? 2 : 3); michael@0: Buf.writeString(options.pin); michael@0: Buf.writeString(options.newPin); michael@0: if (!this.v5Legacy) { michael@0: Buf.writeString(options.aid || this.aid); michael@0: } michael@0: Buf.sendParcel(); michael@0: }, michael@0: michael@0: /** michael@0: * Change the current ICC PIN2 number. michael@0: * michael@0: * @param pin michael@0: * String containing the old PIN2 value michael@0: * @param newPin michael@0: * String containing the new PIN2 value michael@0: * @param [optional] aid michael@0: * AID value. michael@0: */ michael@0: changeICCPIN2: function(options) { michael@0: let Buf = this.context.Buf; michael@0: Buf.newParcel(REQUEST_CHANGE_SIM_PIN2, options); michael@0: Buf.writeInt32(this.v5Legacy ? 2 : 3); michael@0: Buf.writeString(options.pin); michael@0: Buf.writeString(options.newPin); michael@0: if (!this.v5Legacy) { michael@0: Buf.writeString(options.aid || this.aid); michael@0: } michael@0: Buf.sendParcel(); michael@0: }, michael@0: /** michael@0: * Supplies ICC PUK and a new PIN to unlock the ICC. michael@0: * michael@0: * @param puk michael@0: * String containing the PUK value. michael@0: * @param newPin michael@0: * String containing the new PIN value. michael@0: * @param [optional] aid michael@0: * AID value. michael@0: */ michael@0: enterICCPUK: function(options) { michael@0: let Buf = this.context.Buf; michael@0: Buf.newParcel(REQUEST_ENTER_SIM_PUK, options); michael@0: Buf.writeInt32(this.v5Legacy ? 2 : 3); michael@0: Buf.writeString(options.puk); michael@0: Buf.writeString(options.newPin); michael@0: if (!this.v5Legacy) { michael@0: Buf.writeString(options.aid || this.aid); michael@0: } michael@0: Buf.sendParcel(); michael@0: }, michael@0: michael@0: /** michael@0: * Supplies ICC PUK2 and a new PIN2 to unlock the ICC. michael@0: * michael@0: * @param puk michael@0: * String containing the PUK2 value. michael@0: * @param newPin michael@0: * String containing the new PIN2 value. michael@0: * @param [optional] aid michael@0: * AID value. michael@0: */ michael@0: enterICCPUK2: function(options) { michael@0: let Buf = this.context.Buf; michael@0: Buf.newParcel(REQUEST_ENTER_SIM_PUK2, options); michael@0: Buf.writeInt32(this.v5Legacy ? 2 : 3); michael@0: Buf.writeString(options.puk); michael@0: Buf.writeString(options.newPin); michael@0: if (!this.v5Legacy) { michael@0: Buf.writeString(options.aid || this.aid); michael@0: } michael@0: Buf.sendParcel(); michael@0: }, michael@0: michael@0: /** michael@0: * Helper function for fetching the state of ICC locks. michael@0: */ michael@0: iccGetCardLockState: function(options) { michael@0: switch (options.lockType) { michael@0: case GECKO_CARDLOCK_PIN: michael@0: options.facility = ICC_CB_FACILITY_SIM; michael@0: break; michael@0: case GECKO_CARDLOCK_FDN: michael@0: options.facility = ICC_CB_FACILITY_FDN; michael@0: break; michael@0: default: michael@0: options.errorMsg = "Unsupported Card Lock."; michael@0: options.success = false; michael@0: this.sendChromeMessage(options); michael@0: return; michael@0: } michael@0: michael@0: options.password = ""; // For query no need to provide pin. michael@0: options.serviceClass = ICC_SERVICE_CLASS_VOICE | michael@0: ICC_SERVICE_CLASS_DATA | michael@0: ICC_SERVICE_CLASS_FAX; michael@0: this.queryICCFacilityLock(options); michael@0: }, michael@0: michael@0: /** michael@0: * Helper function for fetching the number of unlock retries of ICC locks. michael@0: * michael@0: * We only query the retry count when we're on the emulator. The phones do michael@0: * not support the request id and their rild doesn't return an error. michael@0: */ michael@0: iccGetCardLockRetryCount: function(options) { michael@0: var selCode = { michael@0: pin: ICC_SEL_CODE_SIM_PIN, michael@0: puk: ICC_SEL_CODE_SIM_PUK, michael@0: pin2: ICC_SEL_CODE_SIM_PIN2, michael@0: puk2: ICC_SEL_CODE_SIM_PUK2, michael@0: nck: ICC_SEL_CODE_PH_NET_PIN, michael@0: cck: ICC_SEL_CODE_PH_CORP_PIN, michael@0: spck: ICC_SEL_CODE_PH_SP_PIN michael@0: }; michael@0: michael@0: if (typeof(selCode[options.lockType]) === 'undefined') { michael@0: /* unknown lock type */ michael@0: options.errorMsg = GECKO_ERROR_GENERIC_FAILURE; michael@0: options.success = false; michael@0: this.sendChromeMessage(options); michael@0: return; michael@0: } michael@0: michael@0: if (RILQUIRKS_HAVE_QUERY_ICC_LOCK_RETRY_COUNT) { michael@0: /* Only the emulator supports this request, ... */ michael@0: options.selCode = selCode[options.lockType]; michael@0: this.queryICCLockRetryCount(options); michael@0: } else { michael@0: /* ... while the phones do not. */ michael@0: options.errorMsg = GECKO_ERROR_REQUEST_NOT_SUPPORTED; michael@0: options.success = false; michael@0: this.sendChromeMessage(options); michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Query ICC lock retry count. michael@0: * michael@0: * @param selCode michael@0: * One of ICC_SEL_CODE_*. michael@0: * @param serviceClass michael@0: * One of ICC_SERVICE_CLASS_*. michael@0: */ michael@0: queryICCLockRetryCount: function(options) { michael@0: let Buf = this.context.Buf; michael@0: Buf.newParcel(REQUEST_GET_UNLOCK_RETRY_COUNT, options); michael@0: Buf.writeInt32(1); michael@0: Buf.writeString(options.selCode); michael@0: Buf.sendParcel(); michael@0: }, michael@0: michael@0: /** michael@0: * Query ICC facility lock. michael@0: * michael@0: * @param facility michael@0: * One of ICC_CB_FACILITY_*. michael@0: * @param password michael@0: * Password for the facility, or "" if not required. michael@0: * @param serviceClass michael@0: * One of ICC_SERVICE_CLASS_*. michael@0: * @param [optional] aid michael@0: * AID value. michael@0: */ michael@0: queryICCFacilityLock: function(options) { michael@0: let Buf = this.context.Buf; michael@0: Buf.newParcel(REQUEST_QUERY_FACILITY_LOCK, options); michael@0: Buf.writeInt32(this.v5Legacy ? 3 : 4); michael@0: Buf.writeString(options.facility); michael@0: Buf.writeString(options.password); michael@0: Buf.writeString(options.serviceClass.toString()); michael@0: if (!this.v5Legacy) { michael@0: Buf.writeString(options.aid || this.aid); michael@0: } michael@0: Buf.sendParcel(); michael@0: }, michael@0: michael@0: /** michael@0: * Set ICC facility lock. michael@0: * michael@0: * @param facility michael@0: * One of ICC_CB_FACILITY_*. michael@0: * @param enabled michael@0: * true to enable, false to disable. michael@0: * @param password michael@0: * Password for the facility, or "" if not required. michael@0: * @param serviceClass michael@0: * One of ICC_SERVICE_CLASS_*. michael@0: * @param [optional] aid michael@0: * AID value. michael@0: */ michael@0: setICCFacilityLock: function(options) { michael@0: let Buf = this.context.Buf; michael@0: Buf.newParcel(REQUEST_SET_FACILITY_LOCK, options); michael@0: Buf.writeInt32(this.v5Legacy ? 4 : 5); michael@0: Buf.writeString(options.facility); michael@0: Buf.writeString(options.enabled ? "1" : "0"); michael@0: Buf.writeString(options.password); michael@0: Buf.writeString(options.serviceClass.toString()); michael@0: if (!this.v5Legacy) { michael@0: Buf.writeString(options.aid || this.aid); michael@0: } michael@0: Buf.sendParcel(); michael@0: }, michael@0: michael@0: /** michael@0: * Request an ICC I/O operation. michael@0: * michael@0: * See TS 27.007 "restricted SIM" operation, "AT Command +CRSM". michael@0: * The sequence is in the same order as how libril reads this parcel, michael@0: * see the struct RIL_SIM_IO_v5 or RIL_SIM_IO_v6 defined in ril.h michael@0: * michael@0: * @param command michael@0: * The I/O command, one of the ICC_COMMAND_* constants. michael@0: * @param fileId michael@0: * The file to operate on, one of the ICC_EF_* constants. michael@0: * @param pathId michael@0: * String type, check the 'pathid' parameter from TS 27.007 +CRSM. michael@0: * @param p1, p2, p3 michael@0: * Arbitrary integer parameters for the command. michael@0: * @param [optional] dataWriter michael@0: * The function for writing string parameter for the ICC_COMMAND_UPDATE_RECORD. michael@0: * @param [optional] pin2 michael@0: * String containing the PIN2. michael@0: * @param [optional] aid michael@0: * AID value. michael@0: */ michael@0: iccIO: function(options) { michael@0: let Buf = this.context.Buf; michael@0: Buf.newParcel(REQUEST_SIM_IO, options); michael@0: Buf.writeInt32(options.command); michael@0: Buf.writeInt32(options.fileId); michael@0: Buf.writeString(options.pathId); michael@0: Buf.writeInt32(options.p1); michael@0: Buf.writeInt32(options.p2); michael@0: Buf.writeInt32(options.p3); michael@0: michael@0: // Write data. michael@0: if (options.command == ICC_COMMAND_UPDATE_RECORD && michael@0: options.dataWriter) { michael@0: options.dataWriter(options.p3); michael@0: } else { michael@0: Buf.writeString(null); michael@0: } michael@0: michael@0: // Write pin2. michael@0: if (options.command == ICC_COMMAND_UPDATE_RECORD && michael@0: options.pin2) { michael@0: Buf.writeString(options.pin2); michael@0: } else { michael@0: Buf.writeString(null); michael@0: } michael@0: michael@0: if (!this.v5Legacy) { michael@0: Buf.writeString(options.aid || this.aid); michael@0: } michael@0: Buf.sendParcel(); michael@0: }, michael@0: michael@0: /** michael@0: * Get IMSI. michael@0: * michael@0: * @param [optional] aid michael@0: * AID value. michael@0: */ michael@0: getIMSI: function(aid) { michael@0: let Buf = this.context.Buf; michael@0: if (this.v5Legacy) { michael@0: Buf.simpleRequest(REQUEST_GET_IMSI); michael@0: return; michael@0: } michael@0: Buf.newParcel(REQUEST_GET_IMSI); michael@0: Buf.writeInt32(1); michael@0: Buf.writeString(aid || this.aid); michael@0: Buf.sendParcel(); michael@0: }, michael@0: michael@0: /** michael@0: * Read UICC Phonebook contacts. michael@0: * michael@0: * @param contactType michael@0: * "adn" or "fdn". michael@0: * @param requestId michael@0: * Request id from RadioInterfaceLayer. michael@0: */ michael@0: readICCContacts: function(options) { michael@0: if (!this.appType) { michael@0: options.errorMsg = CONTACT_ERR_REQUEST_NOT_SUPPORTED; michael@0: this.sendChromeMessage(options); michael@0: return; michael@0: } michael@0: michael@0: this.context.ICCContactHelper.readICCContacts( michael@0: this.appType, michael@0: options.contactType, michael@0: function onsuccess(contacts) { michael@0: for (let i = 0; i < contacts.length; i++) { michael@0: let contact = contacts[i]; michael@0: let pbrIndex = contact.pbrIndex || 0; michael@0: let recordIndex = pbrIndex * ICC_MAX_LINEAR_FIXED_RECORDS + contact.recordId; michael@0: contact.contactId = this.iccInfo.iccid + recordIndex; michael@0: } michael@0: // Reuse 'options' to get 'requestId' and 'contactType'. michael@0: options.contacts = contacts; michael@0: this.sendChromeMessage(options); michael@0: }.bind(this), michael@0: function onerror(errorMsg) { michael@0: options.errorMsg = errorMsg; michael@0: this.sendChromeMessage(options); michael@0: }.bind(this)); michael@0: }, michael@0: michael@0: /** michael@0: * Update UICC Phonebook. michael@0: * michael@0: * @param contactType "adn" or "fdn". michael@0: * @param contact The contact will be updated. michael@0: * @param pin2 PIN2 is required for updating FDN. michael@0: * @param requestId Request id from RadioInterfaceLayer. michael@0: */ michael@0: updateICCContact: function(options) { michael@0: let onsuccess = function onsuccess() { michael@0: let recordIndex = michael@0: contact.pbrIndex * ICC_MAX_LINEAR_FIXED_RECORDS + contact.recordId; michael@0: contact.contactId = this.iccInfo.iccid + recordIndex; michael@0: // Reuse 'options' to get 'requestId' and 'contactType'. michael@0: this.sendChromeMessage(options); michael@0: }.bind(this); michael@0: michael@0: let onerror = function onerror(errorMsg) { michael@0: options.errorMsg = errorMsg; michael@0: this.sendChromeMessage(options); michael@0: }.bind(this); michael@0: michael@0: if (!this.appType || !options.contact) { michael@0: onerror(CONTACT_ERR_REQUEST_NOT_SUPPORTED ); michael@0: return; michael@0: } michael@0: michael@0: let contact = options.contact; michael@0: let iccid = this.iccInfo.iccid; michael@0: let isValidRecordId = false; michael@0: if (typeof contact.contactId === "string" && michael@0: contact.contactId.startsWith(iccid)) { michael@0: let recordIndex = contact.contactId.substring(iccid.length); michael@0: contact.pbrIndex = Math.floor(recordIndex / ICC_MAX_LINEAR_FIXED_RECORDS); michael@0: contact.recordId = recordIndex % ICC_MAX_LINEAR_FIXED_RECORDS; michael@0: isValidRecordId = contact.recordId > 0 && contact.recordId < 0xff; michael@0: } michael@0: michael@0: if (DEBUG) { michael@0: this.context.debug("Update ICC Contact " + JSON.stringify(contact)); michael@0: } michael@0: michael@0: let ICCContactHelper = this.context.ICCContactHelper; michael@0: // If contact has 'recordId' property, updates corresponding record. michael@0: // If not, inserts the contact into a free record. michael@0: if (isValidRecordId) { michael@0: ICCContactHelper.updateICCContact( michael@0: this.appType, options.contactType, contact, options.pin2, onsuccess, onerror); michael@0: } else { michael@0: ICCContactHelper.addICCContact( michael@0: this.appType, options.contactType, contact, options.pin2, onsuccess, onerror); michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Request the phone's radio to be enabled or disabled. michael@0: * michael@0: * @param enabled michael@0: * Boolean indicating the desired state. michael@0: */ michael@0: setRadioEnabled: function(options) { michael@0: let Buf = this.context.Buf; michael@0: Buf.newParcel(REQUEST_RADIO_POWER, options); michael@0: Buf.writeInt32(1); michael@0: Buf.writeInt32(options.enabled ? 1 : 0); michael@0: Buf.sendParcel(); michael@0: }, michael@0: michael@0: /** michael@0: * Query call waiting status via MMI. michael@0: */ michael@0: _handleQueryMMICallWaiting: function(options) { michael@0: let Buf = this.context.Buf; michael@0: michael@0: function callback(options) { michael@0: options.length = Buf.readInt32(); michael@0: options.enabled = (Buf.readInt32() === 1); michael@0: let services = Buf.readInt32(); michael@0: if (options.enabled) { michael@0: options.statusMessage = MMI_SM_KS_SERVICE_ENABLED_FOR; michael@0: let serviceClass = []; michael@0: for (let serviceClassMask = 1; michael@0: serviceClassMask <= ICC_SERVICE_CLASS_MAX; michael@0: serviceClassMask <<= 1) { michael@0: if ((serviceClassMask & services) !== 0) { michael@0: serviceClass.push(MMI_KS_SERVICE_CLASS_MAPPING[serviceClassMask]); michael@0: } michael@0: } michael@0: options.additionalInformation = serviceClass; michael@0: } else { michael@0: options.statusMessage = MMI_SM_KS_SERVICE_DISABLED; michael@0: } michael@0: michael@0: // Prevent DataCloneError when sending chrome messages. michael@0: delete options.callback; michael@0: this.sendChromeMessage(options); michael@0: } michael@0: michael@0: options.callback = callback; michael@0: this.queryCallWaiting(options); michael@0: }, michael@0: michael@0: /** michael@0: * Set call waiting status via MMI. michael@0: */ michael@0: _handleSetMMICallWaiting: function(options) { michael@0: function callback(options) { michael@0: if (options.enabled) { michael@0: options.statusMessage = MMI_SM_KS_SERVICE_ENABLED; michael@0: } else { michael@0: options.statusMessage = MMI_SM_KS_SERVICE_DISABLED; michael@0: } michael@0: michael@0: // Prevent DataCloneError when sending chrome messages. michael@0: delete options.callback; michael@0: this.sendChromeMessage(options); michael@0: } michael@0: michael@0: options.callback = callback; michael@0: this.setCallWaiting(options); michael@0: }, michael@0: michael@0: /** michael@0: * Query call waiting status. michael@0: * michael@0: */ michael@0: queryCallWaiting: function(options) { michael@0: let Buf = this.context.Buf; michael@0: Buf.newParcel(REQUEST_QUERY_CALL_WAITING, options); michael@0: Buf.writeInt32(1); michael@0: // As per 3GPP TS 24.083, section 1.6 UE doesn't need to send service michael@0: // class parameter in call waiting interrogation to network michael@0: Buf.writeInt32(ICC_SERVICE_CLASS_NONE); michael@0: Buf.sendParcel(); michael@0: }, michael@0: michael@0: /** michael@0: * Set call waiting status. michael@0: * michael@0: * @param on michael@0: * Boolean indicating the desired waiting status. michael@0: */ michael@0: setCallWaiting: function(options) { michael@0: let Buf = this.context.Buf; michael@0: Buf.newParcel(REQUEST_SET_CALL_WAITING, options); michael@0: Buf.writeInt32(2); michael@0: Buf.writeInt32(options.enabled ? 1 : 0); michael@0: Buf.writeInt32(options.serviceClass !== undefined ? michael@0: options.serviceClass : ICC_SERVICE_CLASS_VOICE); michael@0: Buf.sendParcel(); michael@0: }, michael@0: michael@0: /** michael@0: * Queries current CLIP status. michael@0: * michael@0: * (MMI request for code "*#30#") michael@0: * michael@0: */ michael@0: queryCLIP: function(options) { michael@0: this.context.Buf.simpleRequest(REQUEST_QUERY_CLIP, options); michael@0: }, michael@0: michael@0: /** michael@0: * Queries current CLIR status. michael@0: * michael@0: */ michael@0: getCLIR: function(options) { michael@0: this.context.Buf.simpleRequest(REQUEST_GET_CLIR, options); michael@0: }, michael@0: michael@0: /** michael@0: * Enables or disables the presentation of the calling line identity (CLI) to michael@0: * the called party when originating a call. michael@0: * michael@0: * @param options.clirMode michael@0: * Is one of the CLIR_* constants in michael@0: * nsIDOMMozMobileConnection interface. michael@0: */ michael@0: setCLIR: function(options) { michael@0: if (options) { michael@0: this.clirMode = options.clirMode; michael@0: } michael@0: let Buf = this.context.Buf; michael@0: Buf.newParcel(REQUEST_SET_CLIR, options); michael@0: Buf.writeInt32(1); michael@0: Buf.writeInt32(this.clirMode); michael@0: Buf.sendParcel(); michael@0: }, michael@0: michael@0: /** michael@0: * Set screen state. michael@0: * michael@0: * @param on michael@0: * Boolean indicating whether the screen should be on or off. michael@0: */ michael@0: setScreenState: function(options) { michael@0: let Buf = this.context.Buf; michael@0: Buf.newParcel(REQUEST_SCREEN_STATE); michael@0: Buf.writeInt32(1); michael@0: Buf.writeInt32(options.on ? 1 : 0); michael@0: Buf.sendParcel(); michael@0: }, michael@0: michael@0: getVoiceRegistrationState: function() { michael@0: this.context.Buf.simpleRequest(REQUEST_VOICE_REGISTRATION_STATE); michael@0: }, michael@0: michael@0: getVoiceRadioTechnology: function() { michael@0: this.context.Buf.simpleRequest(REQUEST_VOICE_RADIO_TECH); michael@0: }, michael@0: michael@0: getDataRegistrationState: function() { michael@0: this.context.Buf.simpleRequest(REQUEST_DATA_REGISTRATION_STATE); michael@0: }, michael@0: michael@0: getOperator: function() { michael@0: this.context.Buf.simpleRequest(REQUEST_OPERATOR); michael@0: }, michael@0: michael@0: /** michael@0: * Set the preferred network type. michael@0: * michael@0: * @param options An object contains a valid index of michael@0: * RIL_PREFERRED_NETWORK_TYPE_TO_GECKO as its `networkType` michael@0: * attribute, or undefined to set current preferred network michael@0: * type. michael@0: */ michael@0: setPreferredNetworkType: function(options) { michael@0: if (options) { michael@0: this.preferredNetworkType = options.networkType; michael@0: } michael@0: if (this.preferredNetworkType == null) { michael@0: return; michael@0: } michael@0: michael@0: let Buf = this.context.Buf; michael@0: Buf.newParcel(REQUEST_SET_PREFERRED_NETWORK_TYPE, options); michael@0: Buf.writeInt32(1); michael@0: Buf.writeInt32(this.preferredNetworkType); michael@0: Buf.sendParcel(); michael@0: }, michael@0: michael@0: /** michael@0: * Get the preferred network type. michael@0: */ michael@0: getPreferredNetworkType: function(options) { michael@0: this.context.Buf.simpleRequest(REQUEST_GET_PREFERRED_NETWORK_TYPE, options); michael@0: }, michael@0: michael@0: /** michael@0: * Request various states about the network. michael@0: */ michael@0: requestNetworkInfo: function() { michael@0: if (this._processingNetworkInfo) { michael@0: if (DEBUG) { michael@0: this.context.debug("Network info requested, but we're already " + michael@0: "requesting network info."); michael@0: } michael@0: this._needRepollNetworkInfo = true; michael@0: return; michael@0: } michael@0: michael@0: if (DEBUG) this.context.debug("Requesting network info"); michael@0: michael@0: this._processingNetworkInfo = true; michael@0: this.getVoiceRegistrationState(); michael@0: this.getDataRegistrationState(); //TODO only GSM michael@0: this.getOperator(); michael@0: this.getNetworkSelectionMode(); michael@0: this.getSignalStrength(); michael@0: }, michael@0: michael@0: /** michael@0: * Get the available networks michael@0: */ michael@0: getAvailableNetworks: function(options) { michael@0: if (DEBUG) this.context.debug("Getting available networks"); michael@0: let Buf = this.context.Buf; michael@0: Buf.newParcel(REQUEST_QUERY_AVAILABLE_NETWORKS, options); michael@0: Buf.sendParcel(); michael@0: }, michael@0: michael@0: /** michael@0: * Request the radio's network selection mode michael@0: */ michael@0: getNetworkSelectionMode: function() { michael@0: if (DEBUG) this.context.debug("Getting network selection mode"); michael@0: this.context.Buf.simpleRequest(REQUEST_QUERY_NETWORK_SELECTION_MODE); michael@0: }, michael@0: michael@0: /** michael@0: * Tell the radio to automatically choose a voice/data network michael@0: */ michael@0: selectNetworkAuto: function(options) { michael@0: if (DEBUG) this.context.debug("Setting automatic network selection"); michael@0: this.context.Buf.simpleRequest(REQUEST_SET_NETWORK_SELECTION_AUTOMATIC, options); michael@0: }, michael@0: michael@0: /** michael@0: * Set the roaming preference mode michael@0: */ michael@0: setRoamingPreference: function(options) { michael@0: let roamingMode = CDMA_ROAMING_PREFERENCE_TO_GECKO.indexOf(options.mode); michael@0: michael@0: if (roamingMode === -1) { michael@0: options.errorMsg = GECKO_ERROR_INVALID_PARAMETER; michael@0: this.sendChromeMessage(options); michael@0: return; michael@0: } michael@0: michael@0: let Buf = this.context.Buf; michael@0: Buf.newParcel(REQUEST_CDMA_SET_ROAMING_PREFERENCE, options); michael@0: Buf.writeInt32(1); michael@0: Buf.writeInt32(roamingMode); michael@0: Buf.sendParcel(); michael@0: }, michael@0: michael@0: /** michael@0: * Get the roaming preference mode michael@0: */ michael@0: queryRoamingPreference: function(options) { michael@0: this.context.Buf.simpleRequest(REQUEST_CDMA_QUERY_ROAMING_PREFERENCE, options); michael@0: }, michael@0: michael@0: /** michael@0: * Set the voice privacy mode michael@0: */ michael@0: setVoicePrivacyMode: function(options) { michael@0: let Buf = this.context.Buf; michael@0: Buf.newParcel(REQUEST_CDMA_SET_PREFERRED_VOICE_PRIVACY_MODE, options); michael@0: Buf.writeInt32(1); michael@0: Buf.writeInt32(options.enabled ? 1 : 0); michael@0: Buf.sendParcel(); michael@0: }, michael@0: michael@0: /** michael@0: * Get the voice privacy mode michael@0: */ michael@0: queryVoicePrivacyMode: function(options) { michael@0: this.context.Buf.simpleRequest(REQUEST_CDMA_QUERY_PREFERRED_VOICE_PRIVACY_MODE, options); michael@0: }, michael@0: michael@0: /** michael@0: * Open Logical UICC channel (aid) for Secure Element access michael@0: */ michael@0: iccOpenChannel: function(options) { michael@0: if (DEBUG) { michael@0: this.context.debug("iccOpenChannel: " + JSON.stringify(options)); michael@0: } michael@0: michael@0: let Buf = this.context.Buf; michael@0: Buf.newParcel(REQUEST_SIM_OPEN_CHANNEL, options); michael@0: Buf.writeString(options.aid); michael@0: Buf.sendParcel(); michael@0: }, michael@0: michael@0: /** michael@0: * Exchange APDU data on an open Logical UICC channel michael@0: */ michael@0: iccExchangeAPDU: function(options) { michael@0: if (DEBUG) this.context.debug("iccExchangeAPDU: " + JSON.stringify(options)); michael@0: michael@0: let cla = options.apdu.cla; michael@0: let command = options.apdu.command; michael@0: let channel = options.channel; michael@0: let path = options.apdu.path || ""; michael@0: let data = options.apdu.data || ""; michael@0: let data2 = options.apdu.data2 || ""; michael@0: michael@0: let p1 = options.apdu.p1; michael@0: let p2 = options.apdu.p2; michael@0: let p3 = options.apdu.p3; // Extra michael@0: michael@0: let Buf = this.context.Buf; michael@0: Buf.newParcel(REQUEST_SIM_ACCESS_CHANNEL, options); michael@0: Buf.writeInt32(cla); michael@0: Buf.writeInt32(command); michael@0: Buf.writeInt32(channel); michael@0: Buf.writeString(path); // path michael@0: Buf.writeInt32(p1); michael@0: Buf.writeInt32(p2); michael@0: Buf.writeInt32(p3); michael@0: Buf.writeString(data); // generic data field. michael@0: Buf.writeString(data2); michael@0: michael@0: Buf.sendParcel(); michael@0: }, michael@0: michael@0: /** michael@0: * Close Logical UICC channel michael@0: */ michael@0: iccCloseChannel: function(options) { michael@0: if (DEBUG) this.context.debug("iccCloseChannel: " + JSON.stringify(options)); michael@0: michael@0: let Buf = this.context.Buf; michael@0: Buf.newParcel(REQUEST_SIM_CLOSE_CHANNEL, options); michael@0: Buf.writeInt32(1); michael@0: Buf.writeInt32(options.channel); michael@0: Buf.sendParcel(); michael@0: }, michael@0: michael@0: /** michael@0: * Tell the radio to choose a specific voice/data network michael@0: */ michael@0: selectNetwork: function(options) { michael@0: if (DEBUG) { michael@0: this.context.debug("Setting manual network selection: " + michael@0: options.mcc + ", " + options.mnc); michael@0: } michael@0: michael@0: let numeric = (options.mcc && options.mnc) ? options.mcc + options.mnc : null; michael@0: let Buf = this.context.Buf; michael@0: Buf.newParcel(REQUEST_SET_NETWORK_SELECTION_MANUAL, options); michael@0: Buf.writeString(numeric); michael@0: Buf.sendParcel(); michael@0: }, michael@0: michael@0: /** michael@0: * Get current calls. michael@0: */ michael@0: getCurrentCalls: function() { michael@0: this.context.Buf.simpleRequest(REQUEST_GET_CURRENT_CALLS); michael@0: }, michael@0: michael@0: /** michael@0: * Get the signal strength. michael@0: */ michael@0: getSignalStrength: function() { michael@0: this.context.Buf.simpleRequest(REQUEST_SIGNAL_STRENGTH); michael@0: }, michael@0: michael@0: getIMEI: function(options) { michael@0: this.context.Buf.simpleRequest(REQUEST_GET_IMEI, options); michael@0: }, michael@0: michael@0: getIMEISV: function() { michael@0: this.context.Buf.simpleRequest(REQUEST_GET_IMEISV); michael@0: }, michael@0: michael@0: getDeviceIdentity: function() { michael@0: this.context.Buf.simpleRequest(REQUEST_DEVICE_IDENTITY); michael@0: }, michael@0: michael@0: getBasebandVersion: function() { michael@0: this.context.Buf.simpleRequest(REQUEST_BASEBAND_VERSION); michael@0: }, michael@0: michael@0: sendExitEmergencyCbModeRequest: function(options) { michael@0: this.context.Buf.simpleRequest(REQUEST_EXIT_EMERGENCY_CALLBACK_MODE, options); michael@0: }, michael@0: michael@0: getCdmaSubscription: function() { michael@0: this.context.Buf.simpleRequest(REQUEST_CDMA_SUBSCRIPTION); michael@0: }, michael@0: michael@0: exitEmergencyCbMode: function(options) { michael@0: // The function could be called by an API from RadioInterfaceLayer or by michael@0: // ril_worker itself. From ril_worker, we won't pass the parameter michael@0: // 'options'. In this case, it is marked as internal. michael@0: if (!options) { michael@0: options = {internal: true}; michael@0: } michael@0: this._cancelEmergencyCbModeTimeout(); michael@0: this.sendExitEmergencyCbModeRequest(options); michael@0: }, michael@0: michael@0: /** michael@0: * Cache the request for making an emergency call when radio is off. The michael@0: * request shall include two types of callback functions. 'callback' is michael@0: * called when radio is ready, and 'onerror' is called when turning radio michael@0: * on fails. michael@0: */ michael@0: cachedDialRequest : null, michael@0: michael@0: /** michael@0: * Dial the phone. michael@0: * michael@0: * @param number michael@0: * String containing the number to dial. michael@0: * @param clirMode michael@0: * Integer for showing/hidding the caller Id to the called party. michael@0: * @param uusInfo michael@0: * Integer doing something XXX TODO michael@0: */ michael@0: dial: function(options) { michael@0: let onerror = (function onerror(options, errorMsg) { michael@0: options.success = false; michael@0: options.errorMsg = errorMsg; michael@0: this.sendChromeMessage(options); michael@0: }).bind(this, options); michael@0: michael@0: if (this._isEmergencyNumber(options.number)) { michael@0: this.dialEmergencyNumber(options, onerror); michael@0: } else { michael@0: if (!this._isCdma) { michael@0: // TODO: Both dial() and sendMMI() functions should be unified at some michael@0: // point in the future. In the mean time we handle temporary CLIR MMI michael@0: // commands through the dial() function. Please see bug 889737. michael@0: let mmi = this._parseMMI(options.number); michael@0: if (mmi && this._isTemporaryModeCLIR(mmi)) { michael@0: options.number = mmi.dialNumber; michael@0: // In temporary mode, MMI_PROCEDURE_ACTIVATION means allowing CLI michael@0: // presentation, i.e. CLIR_SUPPRESSION. See TS 22.030, Annex B. michael@0: options.clirMode = mmi.procedure == MMI_PROCEDURE_ACTIVATION ? michael@0: CLIR_SUPPRESSION : CLIR_INVOCATION; michael@0: } michael@0: } michael@0: this.dialNonEmergencyNumber(options, onerror); michael@0: } michael@0: }, michael@0: michael@0: dialNonEmergencyNumber: function(options, onerror) { michael@0: if (this.radioState == GECKO_RADIOSTATE_OFF) { michael@0: // Notify error in establishing the call without radio. michael@0: onerror(GECKO_ERROR_RADIO_NOT_AVAILABLE); michael@0: return; michael@0: } michael@0: michael@0: if (this.voiceRegistrationState.emergencyCallsOnly || michael@0: options.isDialEmergency) { michael@0: onerror(RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[CALL_FAIL_UNOBTAINABLE_NUMBER]); michael@0: return; michael@0: } michael@0: michael@0: // Exit emergency callback mode when user dial a non-emergency call. michael@0: if (this._isInEmergencyCbMode) { michael@0: this.exitEmergencyCbMode(); michael@0: } michael@0: michael@0: if (this._isCdma && Object.keys(this.currentCalls).length == 1) { michael@0: // Make a Cdma 3way call. michael@0: options.featureStr = options.number; michael@0: this.sendCdmaFlashCommand(options); michael@0: } else { michael@0: options.request = REQUEST_DIAL; michael@0: this.sendDialRequest(options); michael@0: } michael@0: }, michael@0: michael@0: dialEmergencyNumber: function(options, onerror) { michael@0: options.request = RILQUIRKS_REQUEST_USE_DIAL_EMERGENCY_CALL ? michael@0: REQUEST_DIAL_EMERGENCY_CALL : REQUEST_DIAL; michael@0: if (this.radioState == GECKO_RADIOSTATE_OFF) { michael@0: if (DEBUG) { michael@0: this.context.debug("Automatically enable radio for an emergency call."); michael@0: } michael@0: michael@0: if (!this.cachedDialRequest) { michael@0: this.cachedDialRequest = {}; michael@0: } michael@0: this.cachedDialRequest.onerror = onerror; michael@0: this.cachedDialRequest.callback = this.sendDialRequest.bind(this, options); michael@0: this.setRadioEnabled({enabled: true}); michael@0: return; michael@0: } michael@0: michael@0: if (this._isCdma && Object.keys(this.currentCalls).length == 1) { michael@0: // Make a Cdma 3way call. michael@0: options.featureStr = options.number; michael@0: this.sendCdmaFlashCommand(options); michael@0: } else { michael@0: this.sendDialRequest(options); michael@0: } michael@0: }, michael@0: michael@0: sendDialRequest: function(options) { michael@0: // Always succeed. michael@0: options.success = true; michael@0: this.sendChromeMessage(options); michael@0: this._createPendingOutgoingCall(options); michael@0: michael@0: let Buf = this.context.Buf; michael@0: Buf.newParcel(options.request, options); michael@0: Buf.writeString(options.number); michael@0: Buf.writeInt32(options.clirMode || 0); michael@0: Buf.writeInt32(options.uusInfo || 0); michael@0: // TODO Why do we need this extra 0? It was put it in to make this michael@0: // match the format of the binary message. michael@0: Buf.writeInt32(0); michael@0: Buf.sendParcel(); michael@0: }, michael@0: michael@0: sendCdmaFlashCommand: function(options) { michael@0: let Buf = this.context.Buf; michael@0: options.isCdma = true; michael@0: options.request = REQUEST_CDMA_FLASH; michael@0: Buf.newParcel(options.request, options); michael@0: Buf.writeString(options.featureStr); michael@0: Buf.sendParcel(); michael@0: }, michael@0: michael@0: /** michael@0: * Hang up all calls michael@0: */ michael@0: hangUpAll: function() { michael@0: for (let callIndex in this.currentCalls) { michael@0: this.hangUp({callIndex: callIndex}); michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Hang up the phone. michael@0: * michael@0: * @param callIndex michael@0: * Call index (1-based) as reported by REQUEST_GET_CURRENT_CALLS. michael@0: */ michael@0: hangUp: function(options) { michael@0: let call = this.currentCalls[options.callIndex]; michael@0: if (!call) { michael@0: return; michael@0: } michael@0: michael@0: let callIndex = call.callIndex; michael@0: if (callIndex === OUTGOING_PLACEHOLDER_CALL_INDEX) { michael@0: if (DEBUG) this.context.debug("Hang up pending outgoing call."); michael@0: this._removeVoiceCall(call, GECKO_CALL_ERROR_NORMAL_CALL_CLEARING); michael@0: return; michael@0: } michael@0: michael@0: call.hangUpLocal = true; michael@0: michael@0: if (call.state === CALL_STATE_HOLDING) { michael@0: this.sendHangUpBackgroundRequest(callIndex); michael@0: } else { michael@0: this.sendHangUpRequest(callIndex); michael@0: } michael@0: }, michael@0: michael@0: sendHangUpRequest: function(callIndex) { michael@0: let Buf = this.context.Buf; michael@0: Buf.newParcel(REQUEST_HANGUP); michael@0: Buf.writeInt32(1); michael@0: Buf.writeInt32(callIndex); michael@0: Buf.sendParcel(); michael@0: }, michael@0: michael@0: sendHangUpBackgroundRequest: function(callIndex) { michael@0: let Buf = this.context.Buf; michael@0: Buf.simpleRequest(REQUEST_HANGUP_WAITING_OR_BACKGROUND); michael@0: }, michael@0: michael@0: /** michael@0: * Mute or unmute the radio. michael@0: * michael@0: * @param mute michael@0: * Boolean to indicate whether to mute or unmute the radio. michael@0: */ michael@0: setMute: function(options) { michael@0: let Buf = this.context.Buf; michael@0: Buf.newParcel(REQUEST_SET_MUTE); michael@0: Buf.writeInt32(1); michael@0: Buf.writeInt32(options.muted ? 1 : 0); michael@0: Buf.sendParcel(); michael@0: }, michael@0: michael@0: /** michael@0: * Answer an incoming/waiting call. michael@0: * michael@0: * @param callIndex michael@0: * Call index of the call to answer. michael@0: */ michael@0: answerCall: function(options) { michael@0: // Check for races. Since we dispatched the incoming/waiting call michael@0: // notification the incoming/waiting call may have changed. The main michael@0: // thread thinks that it is answering the call with the given index, michael@0: // so only answer if that is still incoming/waiting. michael@0: let call = this.currentCalls[options.callIndex]; michael@0: if (!call) { michael@0: return; michael@0: } michael@0: michael@0: let Buf = this.context.Buf; michael@0: switch (call.state) { michael@0: case CALL_STATE_INCOMING: michael@0: Buf.simpleRequest(REQUEST_ANSWER); michael@0: break; michael@0: case CALL_STATE_WAITING: michael@0: // Answer the waiting (second) call, and hold the first call. michael@0: Buf.simpleRequest(REQUEST_SWITCH_WAITING_OR_HOLDING_AND_ACTIVE); michael@0: break; michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Reject an incoming/waiting call. michael@0: * michael@0: * @param callIndex michael@0: * Call index of the call to reject. michael@0: */ michael@0: rejectCall: function(options) { michael@0: // Check for races. Since we dispatched the incoming/waiting call michael@0: // notification the incoming/waiting call may have changed. The main michael@0: // thread thinks that it is rejecting the call with the given index, michael@0: // so only reject if that is still incoming/waiting. michael@0: let call = this.currentCalls[options.callIndex]; michael@0: if (!call) { michael@0: return; michael@0: } michael@0: michael@0: call.hangUpLocal = true; michael@0: michael@0: let Buf = this.context.Buf; michael@0: if (this._isCdma) { michael@0: // AT+CHLD=0 means "release held or UDUB." michael@0: Buf.simpleRequest(REQUEST_HANGUP_WAITING_OR_BACKGROUND); michael@0: return; michael@0: } michael@0: michael@0: switch (call.state) { michael@0: case CALL_STATE_INCOMING: michael@0: Buf.simpleRequest(REQUEST_UDUB); michael@0: break; michael@0: case CALL_STATE_WAITING: michael@0: // Reject the waiting (second) call, and remain the first call. michael@0: Buf.simpleRequest(REQUEST_HANGUP_WAITING_OR_BACKGROUND); michael@0: break; michael@0: } michael@0: }, michael@0: michael@0: holdCall: function(options) { michael@0: let call = this.currentCalls[options.callIndex]; michael@0: if (!call) { michael@0: options.errorMsg = GECKO_ERROR_GENERIC_FAILURE; michael@0: options.success = false; michael@0: this.sendChromeMessage(options); michael@0: return; michael@0: } michael@0: michael@0: let Buf = this.context.Buf; michael@0: if (this._isCdma) { michael@0: options.featureStr = ""; michael@0: this.sendCdmaFlashCommand(options); michael@0: } else if (call.state == CALL_STATE_ACTIVE) { michael@0: Buf.simpleRequest(REQUEST_SWITCH_WAITING_OR_HOLDING_AND_ACTIVE, options); michael@0: } michael@0: }, michael@0: michael@0: resumeCall: function(options) { michael@0: let call = this.currentCalls[options.callIndex]; michael@0: if (!call) { michael@0: options.errorMsg = GECKO_ERROR_GENERIC_FAILURE; michael@0: options.success = false; michael@0: this.sendChromeMessage(options); michael@0: return; michael@0: } michael@0: michael@0: let Buf = this.context.Buf; michael@0: if (this._isCdma) { michael@0: options.featureStr = ""; michael@0: this.sendCdmaFlashCommand(options); michael@0: } else if (call.state == CALL_STATE_HOLDING) { michael@0: Buf.simpleRequest(REQUEST_SWITCH_WAITING_OR_HOLDING_AND_ACTIVE, options); michael@0: } michael@0: }, michael@0: michael@0: // Flag indicating whether user has requested making a conference call. michael@0: _hasConferenceRequest: false, michael@0: michael@0: conferenceCall: function(options) { michael@0: let Buf = this.context.Buf; michael@0: if (this._isCdma) { michael@0: options.featureStr = ""; michael@0: this.sendCdmaFlashCommand(options); michael@0: } else { michael@0: this._hasConferenceRequest = true; michael@0: Buf.simpleRequest(REQUEST_CONFERENCE, options); michael@0: } michael@0: }, michael@0: michael@0: separateCall: function(options) { michael@0: let call = this.currentCalls[options.callIndex]; michael@0: if (!call) { michael@0: options.errorName = "removeError"; michael@0: options.errorMsg = GECKO_ERROR_GENERIC_FAILURE; michael@0: options.success = false; michael@0: this.sendChromeMessage(options); michael@0: return; michael@0: } michael@0: michael@0: let Buf = this.context.Buf; michael@0: if (this._isCdma) { michael@0: options.featureStr = ""; michael@0: this.sendCdmaFlashCommand(options); michael@0: } else { michael@0: Buf.newParcel(REQUEST_SEPARATE_CONNECTION, options); michael@0: Buf.writeInt32(1); michael@0: Buf.writeInt32(options.callIndex); michael@0: Buf.sendParcel(); michael@0: } michael@0: }, michael@0: michael@0: holdConference: function() { michael@0: if (this._isCdma) { michael@0: return; michael@0: } michael@0: michael@0: this.context.Buf.simpleRequest(REQUEST_SWITCH_WAITING_OR_HOLDING_AND_ACTIVE); michael@0: }, michael@0: michael@0: resumeConference: function() { michael@0: if (this._isCdma) { michael@0: return; michael@0: } michael@0: michael@0: this.context.Buf.simpleRequest(REQUEST_SWITCH_WAITING_OR_HOLDING_AND_ACTIVE); michael@0: }, michael@0: michael@0: /** michael@0: * Send an SMS. michael@0: * michael@0: * The `options` parameter object should contain the following attributes: michael@0: * michael@0: * @param number michael@0: * String containing the recipient number. michael@0: * @param body michael@0: * String containing the message text. michael@0: * @param envelopeId michael@0: * Numeric value identifying the sms request. michael@0: */ michael@0: sendSMS: function(options) { michael@0: options.langIndex = options.langIndex || PDU_NL_IDENTIFIER_DEFAULT; michael@0: options.langShiftIndex = options.langShiftIndex || PDU_NL_IDENTIFIER_DEFAULT; michael@0: michael@0: if (!options.retryCount) { michael@0: options.retryCount = 0; michael@0: } michael@0: michael@0: if (!options.segmentSeq) { michael@0: // Fist segment to send michael@0: options.segmentSeq = 1; michael@0: options.body = options.segments[0].body; michael@0: options.encodedBodyLength = options.segments[0].encodedBodyLength; michael@0: } michael@0: michael@0: let Buf = this.context.Buf; michael@0: if (this._isCdma) { michael@0: Buf.newParcel(REQUEST_CDMA_SEND_SMS, options); michael@0: this.context.CdmaPDUHelper.writeMessage(options); michael@0: } else { michael@0: Buf.newParcel(REQUEST_SEND_SMS, options); michael@0: Buf.writeInt32(2); michael@0: Buf.writeString(options.SMSC); michael@0: this.context.GsmPDUHelper.writeMessage(options); michael@0: } michael@0: Buf.sendParcel(); michael@0: }, michael@0: michael@0: /** michael@0: * Acknowledge the receipt and handling of an SMS. michael@0: * michael@0: * @param success michael@0: * Boolean indicating whether the message was successfuly handled. michael@0: * @param cause michael@0: * SMS_* constant indicating the reason for unsuccessful handling. michael@0: */ michael@0: acknowledgeGsmSms: function(success, cause) { michael@0: let Buf = this.context.Buf; michael@0: Buf.newParcel(REQUEST_SMS_ACKNOWLEDGE); michael@0: Buf.writeInt32(2); michael@0: Buf.writeInt32(success ? 1 : 0); michael@0: Buf.writeInt32(cause); michael@0: Buf.sendParcel(); michael@0: }, michael@0: michael@0: /** michael@0: * Acknowledge the receipt and handling of an SMS. michael@0: * michael@0: * @param success michael@0: * Boolean indicating whether the message was successfuly handled. michael@0: */ michael@0: ackSMS: function(options) { michael@0: if (options.result == PDU_FCS_RESERVED) { michael@0: return; michael@0: } michael@0: if (this._isCdma) { michael@0: this.acknowledgeCdmaSms(options.result == PDU_FCS_OK, options.result); michael@0: } else { michael@0: this.acknowledgeGsmSms(options.result == PDU_FCS_OK, options.result); michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Acknowledge the receipt and handling of a CDMA SMS. michael@0: * michael@0: * @param success michael@0: * Boolean indicating whether the message was successfuly handled. michael@0: * @param cause michael@0: * SMS_* constant indicating the reason for unsuccessful handling. michael@0: */ michael@0: acknowledgeCdmaSms: function(success, cause) { michael@0: let Buf = this.context.Buf; michael@0: Buf.newParcel(REQUEST_CDMA_SMS_ACKNOWLEDGE); michael@0: Buf.writeInt32(success ? 0 : 1); michael@0: Buf.writeInt32(cause); michael@0: Buf.sendParcel(); michael@0: }, michael@0: michael@0: /** michael@0: * Update received MWI into EF_MWIS. michael@0: */ michael@0: updateMwis: function(options) { michael@0: if (this.context.ICCUtilsHelper.isICCServiceAvailable("MWIS")) { michael@0: this.context.SimRecordHelper.updateMWIS(options.mwi); michael@0: } michael@0: }, michael@0: michael@0: setCellBroadcastDisabled: function(options) { michael@0: this.cellBroadcastDisabled = options.disabled; michael@0: michael@0: // If |this.mergedCellBroadcastConfig| is null, either we haven't finished michael@0: // reading required SIM files, or no any channel is ever configured. In michael@0: // the former case, we'll call |this.updateCellBroadcastConfig()| later michael@0: // with correct configs; in the latter case, we don't bother resetting CB michael@0: // to disabled again. michael@0: if (this.mergedCellBroadcastConfig) { michael@0: this.updateCellBroadcastConfig(); michael@0: } michael@0: }, michael@0: michael@0: setCellBroadcastSearchList: function(options) { michael@0: let getSearchListStr = function(aSearchList) { michael@0: if (typeof aSearchList === "string" || aSearchList instanceof String) { michael@0: return aSearchList; michael@0: } michael@0: michael@0: // TODO: Set search list for CDMA/GSM individually. Bug 990926 michael@0: let prop = this._isCdma ? "cdma" : "gsm"; michael@0: michael@0: return aSearchList && aSearchList[prop]; michael@0: }.bind(this); michael@0: michael@0: try { michael@0: let str = getSearchListStr(options.searchList); michael@0: this.cellBroadcastConfigs.MMI = this._convertCellBroadcastSearchList(str); michael@0: options.success = true; michael@0: } catch (e) { michael@0: if (DEBUG) { michael@0: this.context.debug("Invalid Cell Broadcast search list: " + e); michael@0: } michael@0: options.success = false; michael@0: } michael@0: michael@0: this.sendChromeMessage(options); michael@0: if (!options.success) { michael@0: return; michael@0: } michael@0: michael@0: this._mergeAllCellBroadcastConfigs(); michael@0: }, michael@0: michael@0: updateCellBroadcastConfig: function() { michael@0: let activate = !this.cellBroadcastDisabled && michael@0: (this.mergedCellBroadcastConfig != null) && michael@0: (this.mergedCellBroadcastConfig.length > 0); michael@0: if (activate) { michael@0: this.setSmsBroadcastConfig(this.mergedCellBroadcastConfig); michael@0: } else { michael@0: // It's unnecessary to set config first if we're deactivating. michael@0: this.setSmsBroadcastActivation(false); michael@0: } michael@0: }, michael@0: michael@0: setGsmSmsBroadcastConfig: function(config) { michael@0: let Buf = this.context.Buf; michael@0: Buf.newParcel(REQUEST_GSM_SET_BROADCAST_SMS_CONFIG); michael@0: michael@0: let numConfigs = config ? config.length / 2 : 0; michael@0: Buf.writeInt32(numConfigs); michael@0: for (let i = 0; i < config.length;) { michael@0: Buf.writeInt32(config[i++]); michael@0: Buf.writeInt32(config[i++]); michael@0: Buf.writeInt32(0x00); michael@0: Buf.writeInt32(0xFF); michael@0: Buf.writeInt32(1); michael@0: } michael@0: michael@0: Buf.sendParcel(); michael@0: }, michael@0: michael@0: /** michael@0: * Send CDMA SMS broadcast config. michael@0: * michael@0: * @see 3GPP2 C.R1001 Sec. 9.2 and 9.3 michael@0: */ michael@0: setCdmaSmsBroadcastConfig: function(config) { michael@0: let Buf = this.context.Buf; michael@0: // |config| is an array of half-closed range: [[from, to), [from, to), ...]. michael@0: // It will be further decomposed, ex: [1, 4) => 1, 2, 3. michael@0: Buf.newParcel(REQUEST_CDMA_SET_BROADCAST_SMS_CONFIG); michael@0: michael@0: let numConfigs = 0; michael@0: for (let i = 0; i < config.length; i += 2) { michael@0: numConfigs += (config[i+1] - config[i]); michael@0: } michael@0: michael@0: Buf.writeInt32(numConfigs); michael@0: for (let i = 0; i < config.length;) { michael@0: let begin = config[i++]; michael@0: let end = config[i++]; michael@0: michael@0: for (let j = begin; j < end; ++j) { michael@0: Buf.writeInt32(j); michael@0: Buf.writeInt32(0); // Language Indicator: Unknown or unspecified. michael@0: Buf.writeInt32(1); michael@0: } michael@0: } michael@0: michael@0: Buf.sendParcel(); michael@0: }, michael@0: michael@0: setSmsBroadcastConfig: function(config) { michael@0: if (this._isCdma) { michael@0: this.setCdmaSmsBroadcastConfig(config); michael@0: } else { michael@0: this.setGsmSmsBroadcastConfig(config); michael@0: } michael@0: }, michael@0: michael@0: setSmsBroadcastActivation: function(activate) { michael@0: let parcelType = this._isCdma ? REQUEST_CDMA_SMS_BROADCAST_ACTIVATION : michael@0: REQUEST_GSM_SMS_BROADCAST_ACTIVATION; michael@0: let Buf = this.context.Buf; michael@0: Buf.newParcel(parcelType); michael@0: Buf.writeInt32(1); michael@0: // See hardware/ril/include/telephony/ril.h, 0 - Activate, 1 - Turn off. michael@0: Buf.writeInt32(activate ? 0 : 1); michael@0: Buf.sendParcel(); michael@0: }, michael@0: michael@0: /** michael@0: * Start a DTMF Tone. michael@0: * michael@0: * @param dtmfChar michael@0: * DTMF signal to send, 0-9, *, + michael@0: */ michael@0: startTone: function(options) { michael@0: let Buf = this.context.Buf; michael@0: Buf.newParcel(REQUEST_DTMF_START); michael@0: Buf.writeString(options.dtmfChar); michael@0: Buf.sendParcel(); michael@0: }, michael@0: michael@0: stopTone: function() { michael@0: this.context.Buf.simpleRequest(REQUEST_DTMF_STOP); michael@0: }, michael@0: michael@0: /** michael@0: * Send a DTMF tone. michael@0: * michael@0: * @param dtmfChar michael@0: * DTMF signal to send, 0-9, *, + michael@0: */ michael@0: sendTone: function(options) { michael@0: let Buf = this.context.Buf; michael@0: Buf.newParcel(REQUEST_DTMF); michael@0: Buf.writeString(options.dtmfChar); michael@0: Buf.sendParcel(); michael@0: }, michael@0: michael@0: /** michael@0: * Get the Short Message Service Center address. michael@0: */ michael@0: getSmscAddress: function(options) { michael@0: if (!this.SMSC) { michael@0: this.context.Buf.simpleRequest(REQUEST_GET_SMSC_ADDRESS, options); michael@0: return; michael@0: } michael@0: michael@0: if (!options || options.rilMessageType !== "getSmscAddress") { michael@0: return; michael@0: } michael@0: michael@0: options.smscAddress = this.SMSC; michael@0: options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError]; michael@0: this.sendChromeMessage(options); michael@0: }, michael@0: michael@0: /** michael@0: * Set the Short Message Service Center address. michael@0: * michael@0: * @param smscAddress michael@0: * Short Message Service Center address in PDU format. michael@0: */ michael@0: setSmscAddress: function(options) { michael@0: let Buf = this.context.Buf; michael@0: Buf.newParcel(REQUEST_SET_SMSC_ADDRESS, options); michael@0: Buf.writeString(options.smscAddress); michael@0: Buf.sendParcel(); michael@0: }, michael@0: michael@0: /** michael@0: * Setup a data call. michael@0: * michael@0: * @param radioTech michael@0: * Integer to indicate radio technology. michael@0: * DATACALL_RADIOTECHNOLOGY_CDMA => CDMA. michael@0: * DATACALL_RADIOTECHNOLOGY_GSM => GSM. michael@0: * @param apn michael@0: * String containing the name of the APN to connect to. michael@0: * @param user michael@0: * String containing the username for the APN. michael@0: * @param passwd michael@0: * String containing the password for the APN. michael@0: * @param chappap michael@0: * Integer containing CHAP/PAP auth type. michael@0: * DATACALL_AUTH_NONE => PAP and CHAP is never performed. michael@0: * DATACALL_AUTH_PAP => PAP may be performed. michael@0: * DATACALL_AUTH_CHAP => CHAP may be performed. michael@0: * DATACALL_AUTH_PAP_OR_CHAP => PAP / CHAP may be performed. michael@0: * @param pdptype michael@0: * String containing PDP type to request. ("IP", "IPV6", ...) michael@0: */ michael@0: setupDataCall: function(options) { michael@0: // From ./hardware/ril/include/telephony/ril.h: michael@0: // ((const char **)data)[0] Radio technology to use: 0-CDMA, 1-GSM/UMTS, 2... michael@0: // for values above 2 this is RIL_RadioTechnology + 2. michael@0: // michael@0: // From frameworks/base/telephony/java/com/android/internal/telephony/DataConnection.java: michael@0: // if the mRilVersion < 6, radio technology must be GSM/UMTS or CDMA. michael@0: // Otherwise, it must be + 2 michael@0: // michael@0: // See also bug 901232 and 867873 michael@0: let radioTech; michael@0: if (this.v5Legacy) { michael@0: radioTech = this._isCdma ? DATACALL_RADIOTECHNOLOGY_CDMA michael@0: : DATACALL_RADIOTECHNOLOGY_GSM; michael@0: } else { michael@0: radioTech = options.radioTech + 2; michael@0: } michael@0: let Buf = this.context.Buf; michael@0: let token = Buf.newParcel(REQUEST_SETUP_DATA_CALL, options); michael@0: Buf.writeInt32(7); michael@0: Buf.writeString(radioTech.toString()); michael@0: Buf.writeString(DATACALL_PROFILE_DEFAULT.toString()); michael@0: Buf.writeString(options.apn); michael@0: Buf.writeString(options.user); michael@0: Buf.writeString(options.passwd); michael@0: Buf.writeString(options.chappap.toString()); michael@0: Buf.writeString(options.pdptype); michael@0: Buf.sendParcel(); michael@0: return token; michael@0: }, michael@0: michael@0: /** michael@0: * Deactivate a data call. michael@0: * michael@0: * @param cid michael@0: * String containing CID. michael@0: * @param reason michael@0: * One of DATACALL_DEACTIVATE_* constants. michael@0: */ michael@0: deactivateDataCall: function(options) { michael@0: let datacall = this.currentDataCalls[options.cid]; michael@0: if (!datacall) { michael@0: return; michael@0: } michael@0: michael@0: let Buf = this.context.Buf; michael@0: Buf.newParcel(REQUEST_DEACTIVATE_DATA_CALL, options); michael@0: Buf.writeInt32(2); michael@0: Buf.writeString(options.cid); michael@0: Buf.writeString(options.reason || DATACALL_DEACTIVATE_NO_REASON); michael@0: Buf.sendParcel(); michael@0: michael@0: datacall.state = GECKO_NETWORK_STATE_DISCONNECTING; michael@0: this.sendChromeMessage(datacall); michael@0: }, michael@0: michael@0: /** michael@0: * Get a list of data calls. michael@0: */ michael@0: getDataCallList: function() { michael@0: this.context.Buf.simpleRequest(REQUEST_DATA_CALL_LIST); michael@0: }, michael@0: michael@0: _attachDataRegistration: false, michael@0: /** michael@0: * Manually attach/detach data registration. michael@0: * michael@0: * @param attach michael@0: * Boolean value indicating attach or detach. michael@0: */ michael@0: setDataRegistration: function(options) { michael@0: let request = options.attach ? RIL_REQUEST_GPRS_ATTACH : michael@0: RIL_REQUEST_GPRS_DETACH; michael@0: this._attachDataRegistration = options.attach; michael@0: this.context.Buf.simpleRequest(request); michael@0: }, michael@0: michael@0: /** michael@0: * Get failure casue code for the most recently failed PDP context. michael@0: */ michael@0: getFailCauseCode: function(callback) { michael@0: this.context.Buf.simpleRequest(REQUEST_LAST_CALL_FAIL_CAUSE, michael@0: {callback: callback}); michael@0: }, michael@0: michael@0: /** michael@0: * Helper to parse MMI/USSD string. TS.22.030 Figure 3.5.3.2. michael@0: */ michael@0: _parseMMI: function(mmiString) { michael@0: if (!mmiString || !mmiString.length) { michael@0: return null; michael@0: } michael@0: michael@0: let matches = this._matchMMIRegexp(mmiString); michael@0: if (matches) { michael@0: // After successfully executing the regular expresion over the MMI string, michael@0: // the following match groups should contain: michael@0: // 1 = full MMI string that might be used as a USSD request. michael@0: // 2 = MMI procedure. michael@0: // 3 = Service code. michael@0: // 5 = SIA. michael@0: // 7 = SIB. michael@0: // 9 = SIC. michael@0: // 11 = Password registration. michael@0: // 12 = Dialing number. michael@0: return { michael@0: fullMMI: matches[MMI_MATCH_GROUP_FULL_MMI], michael@0: procedure: matches[MMI_MATCH_GROUP_MMI_PROCEDURE], michael@0: serviceCode: matches[MMI_MATCH_GROUP_SERVICE_CODE], michael@0: sia: matches[MMI_MATCH_GROUP_SIA], michael@0: sib: matches[MMI_MATCH_GROUP_SIB], michael@0: sic: matches[MMI_MATCH_GROUP_SIC], michael@0: pwd: matches[MMI_MATCH_GROUP_PWD_CONFIRM], michael@0: dialNumber: matches[MMI_MATCH_GROUP_DIALING_NUMBER] michael@0: }; michael@0: } michael@0: michael@0: if (this._isPoundString(mmiString) || michael@0: this._isMMIShortString(mmiString)) { michael@0: return { michael@0: fullMMI: mmiString michael@0: }; michael@0: } michael@0: michael@0: return null; michael@0: }, michael@0: michael@0: /** michael@0: * Helper to parse MMI string via regular expression. TS.22.030 Figure michael@0: * 3.5.3.2. michael@0: */ michael@0: _matchMMIRegexp: function(mmiString) { michael@0: // Regexp to parse and process the MMI code. michael@0: if (this._mmiRegExp == null) { michael@0: // The first group of the regexp takes the whole MMI string. michael@0: // The second group takes the MMI procedure that can be: michael@0: // - Activation (*SC*SI#). michael@0: // - Deactivation (#SC*SI#). michael@0: // - Interrogation (*#SC*SI#). michael@0: // - Registration (**SC*SI#). michael@0: // - Erasure (##SC*SI#). michael@0: // where SC = Service Code (2 or 3 digits) and SI = Supplementary Info michael@0: // (variable length). michael@0: let pattern = "((\\*[*#]?|##?)"; michael@0: michael@0: // Third group of the regexp looks for the MMI Service code, which is a michael@0: // 2 or 3 digits that uniquely specifies the Supplementary Service michael@0: // associated with the MMI code. michael@0: pattern += "(\\d{2,3})"; michael@0: michael@0: // Groups from 4 to 9 looks for the MMI Supplementary Information SIA, michael@0: // SIB and SIC. SIA may comprise e.g. a PIN code or Directory Number, michael@0: // SIB may be used to specify the tele or bearer service and SIC to michael@0: // specify the value of the "No Reply Condition Timer". Where a particular michael@0: // service request does not require any SI, "*SI" is not entered. The use michael@0: // of SIA, SIB and SIC is optional and shall be entered in any of the michael@0: // following formats: michael@0: // - *SIA*SIB*SIC# michael@0: // - *SIA*SIB# michael@0: // - *SIA**SIC# michael@0: // - *SIA# michael@0: // - **SIB*SIC# michael@0: // - ***SISC# michael@0: pattern += "(\\*([^*#]*)(\\*([^*#]*)(\\*([^*#]*)"; michael@0: michael@0: // The eleventh group takes the password for the case of a password michael@0: // registration procedure. michael@0: pattern += "(\\*([^*#]*))?)?)?)?#)"; michael@0: michael@0: // The last group takes the dial string after the #. michael@0: pattern += "([^#]*)"; michael@0: michael@0: this._mmiRegExp = new RegExp(pattern); michael@0: } michael@0: michael@0: // Regex only applys for those well-defined MMI strings (refer to TS.22.030 michael@0: // Annex B), otherwise, null should be the expected return value. michael@0: return this._mmiRegExp.exec(mmiString); michael@0: }, michael@0: michael@0: /** michael@0: * Helper to parse # string. TS.22.030 Figure 3.5.3.2. michael@0: */ michael@0: _isPoundString: function(mmiString) { michael@0: return (mmiString.charAt(mmiString.length - 1) === MMI_END_OF_USSD); michael@0: }, michael@0: michael@0: /** michael@0: * Helper to parse short string. TS.22.030 Figure 3.5.3.2. michael@0: */ michael@0: _isMMIShortString: function(mmiString) { michael@0: if (mmiString.length > 2) { michael@0: return false; michael@0: } michael@0: michael@0: if (this._isEmergencyNumber(mmiString)) { michael@0: return false; michael@0: } michael@0: michael@0: // In a call case. michael@0: if (Object.getOwnPropertyNames(this.currentCalls).length > 0) { michael@0: return true; michael@0: } michael@0: michael@0: if ((mmiString.length != 2) || (mmiString.charAt(0) !== '1')) { michael@0: return true; michael@0: } michael@0: michael@0: return false; michael@0: }, michael@0: michael@0: sendMMI: function(options) { michael@0: if (DEBUG) { michael@0: this.context.debug("SendMMI " + JSON.stringify(options)); michael@0: } michael@0: let mmiString = options.mmi; michael@0: let mmi = this._parseMMI(mmiString); michael@0: michael@0: let _sendMMIError = (function(errorMsg, mmiServiceCode) { michael@0: options.success = false; michael@0: options.errorMsg = errorMsg; michael@0: if (mmiServiceCode) { michael@0: options.mmiServiceCode = mmiServiceCode; michael@0: } michael@0: this.sendChromeMessage(options); michael@0: }).bind(this); michael@0: michael@0: function _isValidPINPUKRequest(mmiServiceCode) { michael@0: // The only allowed MMI procedure for ICC PIN, PIN2, PUK and PUK2 handling michael@0: // is "Registration" (**). michael@0: if (!mmi.procedure || mmi.procedure != MMI_PROCEDURE_REGISTRATION ) { michael@0: _sendMMIError(MMI_ERROR_KS_INVALID_ACTION, mmiServiceCode); michael@0: return false; michael@0: } michael@0: michael@0: if (!mmi.sia || !mmi.sia.length || !mmi.sib || !mmi.sib.length || michael@0: !mmi.sic || !mmi.sic.length) { michael@0: _sendMMIError(MMI_ERROR_KS_ERROR, mmiServiceCode); michael@0: return false; michael@0: } michael@0: michael@0: if (mmi.sib != mmi.sic) { michael@0: _sendMMIError(MMI_ERROR_KS_MISMATCH_PIN, mmiServiceCode); michael@0: return false; michael@0: } michael@0: michael@0: if (mmi.sia.length < 4 || mmi.sia.length > 8 || michael@0: mmi.sib.length < 4 || mmi.sib.length > 8 || michael@0: mmi.sic.length < 4 || mmi.sic.length > 8) { michael@0: _sendMMIError(MMI_ERROR_KS_INVALID_PIN, mmiServiceCode); michael@0: return false; michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: let _isRadioAvailable = (function(mmiServiceCode) { michael@0: if (this.radioState !== GECKO_RADIOSTATE_READY) { michael@0: _sendMMIError(GECKO_ERROR_RADIO_NOT_AVAILABLE, mmiServiceCode); michael@0: return false; michael@0: } michael@0: return true; michael@0: }).bind(this); michael@0: michael@0: // If we couldn't parse the MMI code, we'll send it as an USSD request. michael@0: if (mmi === null) { michael@0: if (this._ussdSession) { michael@0: if (!_isRadioAvailable(MMI_KS_SC_USSD)) { michael@0: return; michael@0: } michael@0: options.ussd = mmiString; michael@0: this.sendUSSD(options); michael@0: return; michael@0: } michael@0: michael@0: _sendMMIError(MMI_ERROR_KS_ERROR); michael@0: return; michael@0: } michael@0: michael@0: if (DEBUG) { michael@0: this.context.debug("MMI " + JSON.stringify(mmi)); michael@0: } michael@0: michael@0: // We check if the MMI service code is supported and in that case we michael@0: // trigger the appropriate RIL request if possible. michael@0: let sc = mmi.serviceCode; michael@0: switch (sc) { michael@0: // Call forwarding michael@0: case MMI_SC_CFU: michael@0: case MMI_SC_CF_BUSY: michael@0: case MMI_SC_CF_NO_REPLY: michael@0: case MMI_SC_CF_NOT_REACHABLE: michael@0: case MMI_SC_CF_ALL: michael@0: case MMI_SC_CF_ALL_CONDITIONAL: michael@0: if (!_isRadioAvailable(MMI_KS_SC_CALL_FORWARDING)) { michael@0: return; michael@0: } michael@0: // Call forwarding requires at least an action, given by the MMI michael@0: // procedure, and a reason, given by the MMI service code, but there michael@0: // is no way that we get this far without a valid procedure or service michael@0: // code. michael@0: options.mmiServiceCode = MMI_KS_SC_CALL_FORWARDING; michael@0: options.action = MMI_PROC_TO_CF_ACTION[mmi.procedure]; michael@0: options.reason = MMI_SC_TO_CF_REASON[sc]; michael@0: options.number = mmi.sia; michael@0: options.serviceClass = this._siToServiceClass(mmi.sib); michael@0: if (options.action == CALL_FORWARD_ACTION_QUERY_STATUS) { michael@0: this.queryCallForwardStatus(options); michael@0: return; michael@0: } michael@0: michael@0: options.isSetCallForward = true; michael@0: options.timeSeconds = mmi.sic; michael@0: this.setCallForward(options); michael@0: return; michael@0: michael@0: // Change the current ICC PIN number. michael@0: case MMI_SC_PIN: michael@0: // As defined in TS.122.030 6.6.2 to change the ICC PIN we should expect michael@0: // an MMI code of the form **04*OLD_PIN*NEW_PIN*NEW_PIN#, where old PIN michael@0: // should be entered as the SIA parameter and the new PIN as SIB and michael@0: // SIC. michael@0: if (!_isRadioAvailable(MMI_KS_SC_PIN) || michael@0: !_isValidPINPUKRequest(MMI_KS_SC_PIN)) { michael@0: return; michael@0: } michael@0: michael@0: options.mmiServiceCode = MMI_KS_SC_PIN; michael@0: options.pin = mmi.sia; michael@0: options.newPin = mmi.sib; michael@0: this.changeICCPIN(options); michael@0: return; michael@0: michael@0: // Change the current ICC PIN2 number. michael@0: case MMI_SC_PIN2: michael@0: // As defined in TS.122.030 6.6.2 to change the ICC PIN2 we should michael@0: // enter and MMI code of the form **042*OLD_PIN2*NEW_PIN2*NEW_PIN2#, michael@0: // where the old PIN2 should be entered as the SIA parameter and the michael@0: // new PIN2 as SIB and SIC. michael@0: if (!_isRadioAvailable(MMI_KS_SC_PIN2) || michael@0: !_isValidPINPUKRequest(MMI_KS_SC_PIN2)) { michael@0: return; michael@0: } michael@0: michael@0: options.mmiServiceCode = MMI_KS_SC_PIN2; michael@0: options.pin = mmi.sia; michael@0: options.newPin = mmi.sib; michael@0: this.changeICCPIN2(options); michael@0: return; michael@0: michael@0: // Unblock ICC PIN. michael@0: case MMI_SC_PUK: michael@0: // As defined in TS.122.030 6.6.3 to unblock the ICC PIN we should michael@0: // enter an MMI code of the form **05*PUK*NEW_PIN*NEW_PIN#, where PUK michael@0: // should be entered as the SIA parameter and the new PIN as SIB and michael@0: // SIC. michael@0: if (!_isRadioAvailable(MMI_KS_SC_PUK) || michael@0: !_isValidPINPUKRequest(MMI_KS_SC_PUK)) { michael@0: return; michael@0: } michael@0: michael@0: options.mmiServiceCode = MMI_KS_SC_PUK; michael@0: options.puk = mmi.sia; michael@0: options.newPin = mmi.sib; michael@0: this.enterICCPUK(options); michael@0: return; michael@0: michael@0: // Unblock ICC PIN2. michael@0: case MMI_SC_PUK2: michael@0: // As defined in TS.122.030 6.6.3 to unblock the ICC PIN2 we should michael@0: // enter an MMI code of the form **052*PUK2*NEW_PIN2*NEW_PIN2#, where michael@0: // PUK2 should be entered as the SIA parameter and the new PIN2 as SIB michael@0: // and SIC. michael@0: if (!_isRadioAvailable(MMI_KS_SC_PUK2) || michael@0: !_isValidPINPUKRequest(MMI_KS_SC_PUK2)) { michael@0: return; michael@0: } michael@0: michael@0: options.mmiServiceCode = MMI_KS_SC_PUK2; michael@0: options.puk = mmi.sia; michael@0: options.newPin = mmi.sib; michael@0: this.enterICCPUK2(options); michael@0: return; michael@0: michael@0: // IMEI michael@0: case MMI_SC_IMEI: michael@0: // A device's IMEI can't change, so we only need to request it once. michael@0: if (this.IMEI == null) { michael@0: this.getIMEI(options); michael@0: return; michael@0: } michael@0: // If we already had the device's IMEI, we just send it to chrome. michael@0: options.mmiServiceCode = MMI_KS_SC_IMEI; michael@0: options.success = true; michael@0: options.statusMessage = this.IMEI; michael@0: this.sendChromeMessage(options); michael@0: return; michael@0: michael@0: // CLIP michael@0: case MMI_SC_CLIP: michael@0: options.mmiServiceCode = MMI_KS_SC_CLIP; michael@0: options.procedure = mmi.procedure; michael@0: if (options.procedure === MMI_PROCEDURE_INTERROGATION) { michael@0: this.queryCLIP(options); michael@0: } else { michael@0: _sendMMIError(MMI_ERROR_KS_NOT_SUPPORTED, MMI_KS_SC_CLIP); michael@0: } michael@0: return; michael@0: michael@0: // CLIR (non-temporary ones) michael@0: // TODO: Both dial() and sendMMI() functions should be unified at some michael@0: // point in the future. In the mean time we handle temporary CLIR MMI michael@0: // commands through the dial() function. Please see bug 889737. michael@0: case MMI_SC_CLIR: michael@0: options.mmiServiceCode = MMI_KS_SC_CLIR; michael@0: options.procedure = mmi.procedure; michael@0: switch (options.procedure) { michael@0: case MMI_PROCEDURE_INTERROGATION: michael@0: this.getCLIR(options); michael@0: return; michael@0: case MMI_PROCEDURE_ACTIVATION: michael@0: options.clirMode = CLIR_INVOCATION; michael@0: break; michael@0: case MMI_PROCEDURE_DEACTIVATION: michael@0: options.clirMode = CLIR_SUPPRESSION; michael@0: break; michael@0: default: michael@0: _sendMMIError(MMI_ERROR_KS_NOT_SUPPORTED, MMI_KS_SC_CLIR); michael@0: return; michael@0: } michael@0: options.isSetCLIR = true; michael@0: this.setCLIR(options); michael@0: return; michael@0: michael@0: // Call barring michael@0: case MMI_SC_BAOC: michael@0: case MMI_SC_BAOIC: michael@0: case MMI_SC_BAOICxH: michael@0: case MMI_SC_BAIC: michael@0: case MMI_SC_BAICr: michael@0: case MMI_SC_BA_ALL: michael@0: case MMI_SC_BA_MO: michael@0: case MMI_SC_BA_MT: michael@0: options.mmiServiceCode = MMI_KS_SC_CALL_BARRING; michael@0: options.password = mmi.sia || ""; michael@0: options.serviceClass = this._siToServiceClass(mmi.sib); michael@0: options.facility = MMI_SC_TO_CB_FACILITY[sc]; michael@0: options.procedure = mmi.procedure; michael@0: if (mmi.procedure === MMI_PROCEDURE_INTERROGATION) { michael@0: this.queryICCFacilityLock(options); michael@0: return; michael@0: } michael@0: if (mmi.procedure === MMI_PROCEDURE_ACTIVATION) { michael@0: options.enabled = 1; michael@0: } else if (mmi.procedure === MMI_PROCEDURE_DEACTIVATION) { michael@0: options.enabled = 0; michael@0: } else { michael@0: _sendMMIError(MMI_ERROR_KS_NOT_SUPPORTED, MMI_KS_SC_CALL_BARRING); michael@0: return; michael@0: } michael@0: this.setICCFacilityLock(options); michael@0: return; michael@0: michael@0: // Call waiting michael@0: case MMI_SC_CALL_WAITING: michael@0: if (!_isRadioAvailable(MMI_KS_SC_CALL_WAITING)) { michael@0: return; michael@0: } michael@0: michael@0: options.mmiServiceCode = MMI_KS_SC_CALL_WAITING; michael@0: michael@0: if (mmi.procedure === MMI_PROCEDURE_INTERROGATION) { michael@0: this._handleQueryMMICallWaiting(options); michael@0: return; michael@0: } michael@0: michael@0: if (mmi.procedure === MMI_PROCEDURE_ACTIVATION) { michael@0: options.enabled = true; michael@0: } else if (mmi.procedure === MMI_PROCEDURE_DEACTIVATION) { michael@0: options.enabled = false; michael@0: } else { michael@0: _sendMMIError(MMI_ERROR_KS_NOT_SUPPORTED, MMI_KS_SC_CALL_WAITING); michael@0: return; michael@0: } michael@0: michael@0: options.serviceClass = this._siToServiceClass(mmi.sia); michael@0: this._handleSetMMICallWaiting(options); michael@0: return; michael@0: } michael@0: michael@0: // If the MMI code is not a known code and is a recognized USSD request, michael@0: // it shall still be sent as a USSD request. michael@0: if (mmi.fullMMI) { michael@0: if (!_isRadioAvailable(MMI_KS_SC_USSD)) { michael@0: return; michael@0: } michael@0: michael@0: options.ussd = mmi.fullMMI; michael@0: options.mmiServiceCode = MMI_KS_SC_USSD; michael@0: this.sendUSSD(options); michael@0: return; michael@0: } michael@0: michael@0: // At this point, the MMI string is considered as not valid MMI code and michael@0: // not valid USSD code. michael@0: _sendMMIError(MMI_ERROR_KS_ERROR); michael@0: }, michael@0: michael@0: /** michael@0: * Send USSD. michael@0: * michael@0: * @param ussd michael@0: * String containing the USSD code. michael@0: * michael@0: */ michael@0: sendUSSD: function(options) { michael@0: let Buf = this.context.Buf; michael@0: Buf.newParcel(REQUEST_SEND_USSD, options); michael@0: Buf.writeString(options.ussd); michael@0: Buf.sendParcel(); michael@0: }, michael@0: michael@0: /** michael@0: * Cancel pending USSD. michael@0: */ michael@0: cancelUSSD: function(options) { michael@0: options.mmiServiceCode = MMI_KS_SC_USSD; michael@0: this.context.Buf.simpleRequest(REQUEST_CANCEL_USSD, options); michael@0: }, michael@0: michael@0: /** michael@0: * Queries current call forward rules. michael@0: * michael@0: * @param reason michael@0: * One of nsIDOMMozMobileCFInfo.CALL_FORWARD_REASON_* constants. michael@0: * @param serviceClass michael@0: * One of ICC_SERVICE_CLASS_* constants. michael@0: * @param number michael@0: * Phone number of forwarding address. michael@0: */ michael@0: queryCallForwardStatus: function(options) { michael@0: let Buf = this.context.Buf; michael@0: let number = options.number || ""; michael@0: Buf.newParcel(REQUEST_QUERY_CALL_FORWARD_STATUS, options); michael@0: Buf.writeInt32(CALL_FORWARD_ACTION_QUERY_STATUS); michael@0: Buf.writeInt32(options.reason); michael@0: Buf.writeInt32(options.serviceClass || ICC_SERVICE_CLASS_NONE); michael@0: Buf.writeInt32(this._toaFromString(number)); michael@0: Buf.writeString(number); michael@0: Buf.writeInt32(0); michael@0: Buf.sendParcel(); michael@0: }, michael@0: michael@0: /** michael@0: * Configures call forward rule. michael@0: * michael@0: * @param action michael@0: * One of nsIDOMMozMobileCFInfo.CALL_FORWARD_ACTION_* constants. michael@0: * @param reason michael@0: * One of nsIDOMMozMobileCFInfo.CALL_FORWARD_REASON_* constants. michael@0: * @param serviceClass michael@0: * One of ICC_SERVICE_CLASS_* constants. michael@0: * @param number michael@0: * Phone number of forwarding address. michael@0: * @param timeSeconds michael@0: * Time in seconds to wait beforec all is forwarded. michael@0: */ michael@0: setCallForward: function(options) { michael@0: let Buf = this.context.Buf; michael@0: Buf.newParcel(REQUEST_SET_CALL_FORWARD, options); michael@0: Buf.writeInt32(options.action); michael@0: Buf.writeInt32(options.reason); michael@0: Buf.writeInt32(options.serviceClass); michael@0: Buf.writeInt32(this._toaFromString(options.number)); michael@0: Buf.writeString(options.number); michael@0: Buf.writeInt32(options.timeSeconds); michael@0: Buf.sendParcel(); michael@0: }, michael@0: michael@0: /** michael@0: * Queries current call barring rules. michael@0: * michael@0: * @param program michael@0: * One of nsIDOMMozMobileConnection.CALL_BARRING_PROGRAM_* constants. michael@0: * @param serviceClass michael@0: * One of ICC_SERVICE_CLASS_* constants. michael@0: */ michael@0: queryCallBarringStatus: function(options) { michael@0: options.facility = CALL_BARRING_PROGRAM_TO_FACILITY[options.program]; michael@0: options.password = ""; // For query no need to provide it. michael@0: this.queryICCFacilityLock(options); michael@0: }, michael@0: michael@0: /** michael@0: * Configures call barring rule. michael@0: * michael@0: * @param program michael@0: * One of nsIDOMMozMobileConnection.CALL_BARRING_PROGRAM_* constants. michael@0: * @param enabled michael@0: * Enable or disable the call barring. michael@0: * @param password michael@0: * Barring password. michael@0: * @param serviceClass michael@0: * One of ICC_SERVICE_CLASS_* constants. michael@0: */ michael@0: setCallBarring: function(options) { michael@0: options.facility = CALL_BARRING_PROGRAM_TO_FACILITY[options.program]; michael@0: this.setICCFacilityLock(options); michael@0: }, michael@0: michael@0: /** michael@0: * Change call barring facility password. michael@0: * michael@0: * @param pin michael@0: * Old password. michael@0: * @param newPin michael@0: * New password. michael@0: */ michael@0: changeCallBarringPassword: function(options) { michael@0: let Buf = this.context.Buf; michael@0: Buf.newParcel(REQUEST_CHANGE_BARRING_PASSWORD, options); michael@0: Buf.writeInt32(3); michael@0: // Set facility to ICC_CB_FACILITY_BA_ALL by following TS.22.030 clause michael@0: // 6.5.4 and Table B.1. michael@0: Buf.writeString(ICC_CB_FACILITY_BA_ALL); michael@0: Buf.writeString(options.pin); michael@0: Buf.writeString(options.newPin); michael@0: Buf.sendParcel(); michael@0: }, michael@0: michael@0: /** michael@0: * Handle STK CALL_SET_UP request. michael@0: * michael@0: * @param hasConfirmed michael@0: * Does use have confirmed the call requested from ICC? michael@0: */ michael@0: stkHandleCallSetup: function(options) { michael@0: let Buf = this.context.Buf; michael@0: Buf.newParcel(REQUEST_STK_HANDLE_CALL_SETUP_REQUESTED_FROM_SIM); michael@0: Buf.writeInt32(1); michael@0: Buf.writeInt32(options.hasConfirmed ? 1 : 0); michael@0: Buf.sendParcel(); michael@0: }, michael@0: michael@0: /** michael@0: * Send STK Profile Download. michael@0: * michael@0: * @param profile Profile supported by ME. michael@0: */ michael@0: sendStkTerminalProfile: function(profile) { michael@0: let Buf = this.context.Buf; michael@0: let GsmPDUHelper = this.context.GsmPDUHelper; michael@0: michael@0: Buf.newParcel(REQUEST_STK_SET_PROFILE); michael@0: Buf.writeInt32(profile.length * 2); michael@0: for (let i = 0; i < profile.length; i++) { michael@0: GsmPDUHelper.writeHexOctet(profile[i]); michael@0: } michael@0: Buf.writeInt32(0); michael@0: Buf.sendParcel(); michael@0: }, michael@0: michael@0: /** michael@0: * Send STK terminal response. michael@0: * michael@0: * @param command michael@0: * @param deviceIdentities michael@0: * @param resultCode michael@0: * @param [optional] itemIdentifier michael@0: * @param [optional] input michael@0: * @param [optional] isYesNo michael@0: * @param [optional] hasConfirmed michael@0: * @param [optional] localInfo michael@0: * @param [optional] timer michael@0: */ michael@0: sendStkTerminalResponse: function(response) { michael@0: if (response.hasConfirmed !== undefined) { michael@0: this.stkHandleCallSetup(response); michael@0: return; michael@0: } michael@0: michael@0: let Buf = this.context.Buf; michael@0: let ComprehensionTlvHelper = this.context.ComprehensionTlvHelper; michael@0: let GsmPDUHelper = this.context.GsmPDUHelper; michael@0: michael@0: let command = response.command; michael@0: Buf.newParcel(REQUEST_STK_SEND_TERMINAL_RESPONSE); michael@0: michael@0: // 1st mark for Parcel size michael@0: Buf.startCalOutgoingSize(function(size) { michael@0: // Parcel size is in string length, which costs 2 uint8 per char. michael@0: Buf.writeInt32(size / 2); michael@0: }); michael@0: michael@0: // Command Details michael@0: GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_COMMAND_DETAILS | michael@0: COMPREHENSIONTLV_FLAG_CR); michael@0: GsmPDUHelper.writeHexOctet(3); michael@0: if (response.command) { michael@0: GsmPDUHelper.writeHexOctet(command.commandNumber); michael@0: GsmPDUHelper.writeHexOctet(command.typeOfCommand); michael@0: GsmPDUHelper.writeHexOctet(command.commandQualifier); michael@0: } else { michael@0: GsmPDUHelper.writeHexOctet(0x00); michael@0: GsmPDUHelper.writeHexOctet(0x00); michael@0: GsmPDUHelper.writeHexOctet(0x00); michael@0: } michael@0: michael@0: // Device Identifier michael@0: // According to TS102.223/TS31.111 section 6.8 Structure of michael@0: // TERMINAL RESPONSE, "For all SIMPLE-TLV objects with Min=N, michael@0: // the ME should set the CR(comprehension required) flag to michael@0: // comprehension not required.(CR=0)" michael@0: // Since DEVICE_IDENTITIES and DURATION TLVs have Min=N, michael@0: // the CR flag is not set. michael@0: GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_DEVICE_ID); michael@0: GsmPDUHelper.writeHexOctet(2); michael@0: GsmPDUHelper.writeHexOctet(STK_DEVICE_ID_ME); michael@0: GsmPDUHelper.writeHexOctet(STK_DEVICE_ID_SIM); michael@0: michael@0: // Result michael@0: GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_RESULT | michael@0: COMPREHENSIONTLV_FLAG_CR); michael@0: GsmPDUHelper.writeHexOctet(1); michael@0: GsmPDUHelper.writeHexOctet(response.resultCode); michael@0: michael@0: // Item Identifier michael@0: if (response.itemIdentifier != null) { michael@0: GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_ITEM_ID | michael@0: COMPREHENSIONTLV_FLAG_CR); michael@0: GsmPDUHelper.writeHexOctet(1); michael@0: GsmPDUHelper.writeHexOctet(response.itemIdentifier); michael@0: } michael@0: michael@0: // No need to process Text data if user requests help information. michael@0: if (response.resultCode != STK_RESULT_HELP_INFO_REQUIRED) { michael@0: let text; michael@0: if (response.isYesNo !== undefined) { michael@0: // GET_INKEY michael@0: // When the ME issues a successful TERMINAL RESPONSE for a GET INKEY michael@0: // ("Yes/No") command with command qualifier set to "Yes/No", it shall michael@0: // supply the value '01' when the answer is "positive" and the value michael@0: // '00' when the answer is "negative" in the Text string data object. michael@0: text = response.isYesNo ? 0x01 : 0x00; michael@0: } else { michael@0: text = response.input; michael@0: } michael@0: michael@0: if (text) { michael@0: GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_TEXT_STRING | michael@0: COMPREHENSIONTLV_FLAG_CR); michael@0: michael@0: // 2nd mark for text length michael@0: Buf.startCalOutgoingSize(function(size) { michael@0: // Text length is in number of hexOctets, which costs 4 uint8 per hexOctet. michael@0: GsmPDUHelper.writeHexOctet(size / 4); michael@0: }); michael@0: michael@0: let coding = command.options.isUCS2 ? michael@0: STK_TEXT_CODING_UCS2 : michael@0: (command.options.isPacked ? michael@0: STK_TEXT_CODING_GSM_7BIT_PACKED : michael@0: STK_TEXT_CODING_GSM_8BIT); michael@0: GsmPDUHelper.writeHexOctet(coding); michael@0: michael@0: // Write Text String. michael@0: switch (coding) { michael@0: case STK_TEXT_CODING_UCS2: michael@0: GsmPDUHelper.writeUCS2String(text); michael@0: break; michael@0: case STK_TEXT_CODING_GSM_7BIT_PACKED: michael@0: GsmPDUHelper.writeStringAsSeptets(text, 0, 0, 0); michael@0: break; michael@0: case STK_TEXT_CODING_GSM_8BIT: michael@0: for (let i = 0; i < text.length; i++) { michael@0: GsmPDUHelper.writeHexOctet(text.charCodeAt(i)); michael@0: } michael@0: break; michael@0: } michael@0: michael@0: // Calculate and write text length to 2nd mark michael@0: Buf.stopCalOutgoingSize(); michael@0: } michael@0: } michael@0: michael@0: // Local Information michael@0: if (response.localInfo) { michael@0: let localInfo = response.localInfo; michael@0: michael@0: // Location Infomation michael@0: if (localInfo.locationInfo) { michael@0: ComprehensionTlvHelper.writeLocationInfoTlv(localInfo.locationInfo); michael@0: } michael@0: michael@0: // IMEI michael@0: if (localInfo.imei != null) { michael@0: let imei = localInfo.imei; michael@0: if (imei.length == 15) { michael@0: imei = imei + "0"; michael@0: } michael@0: michael@0: GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_IMEI); michael@0: GsmPDUHelper.writeHexOctet(8); michael@0: for (let i = 0; i < imei.length / 2; i++) { michael@0: GsmPDUHelper.writeHexOctet(parseInt(imei.substr(i * 2, 2), 16)); michael@0: } michael@0: } michael@0: michael@0: // Date and Time Zone michael@0: if (localInfo.date != null) { michael@0: ComprehensionTlvHelper.writeDateTimeZoneTlv(localInfo.date); michael@0: } michael@0: michael@0: // Language michael@0: if (localInfo.language) { michael@0: ComprehensionTlvHelper.writeLanguageTlv(localInfo.language); michael@0: } michael@0: } michael@0: michael@0: // Timer michael@0: if (response.timer) { michael@0: let timer = response.timer; michael@0: michael@0: if (timer.timerId) { michael@0: GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_TIMER_IDENTIFIER); michael@0: GsmPDUHelper.writeHexOctet(1); michael@0: GsmPDUHelper.writeHexOctet(timer.timerId); michael@0: } michael@0: michael@0: if (timer.timerValue) { michael@0: ComprehensionTlvHelper.writeTimerValueTlv(timer.timerValue, false); michael@0: } michael@0: } michael@0: michael@0: // Calculate and write Parcel size to 1st mark michael@0: Buf.stopCalOutgoingSize(); michael@0: michael@0: Buf.writeInt32(0); michael@0: Buf.sendParcel(); michael@0: }, michael@0: michael@0: /** michael@0: * Send STK Envelope(Menu Selection) command. michael@0: * michael@0: * @param itemIdentifier michael@0: * @param helpRequested michael@0: */ michael@0: sendStkMenuSelection: function(command) { michael@0: command.tag = BER_MENU_SELECTION_TAG; michael@0: command.deviceId = { michael@0: sourceId :STK_DEVICE_ID_KEYPAD, michael@0: destinationId: STK_DEVICE_ID_SIM michael@0: }; michael@0: this.sendICCEnvelopeCommand(command); michael@0: }, michael@0: michael@0: /** michael@0: * Send STK Envelope(Timer Expiration) command. michael@0: * michael@0: * @param timer michael@0: */ michael@0: sendStkTimerExpiration: function(command) { michael@0: command.tag = BER_TIMER_EXPIRATION_TAG; michael@0: command.deviceId = { michael@0: sourceId: STK_DEVICE_ID_ME, michael@0: destinationId: STK_DEVICE_ID_SIM michael@0: }; michael@0: command.timerId = command.timer.timerId; michael@0: command.timerValue = command.timer.timerValue; michael@0: this.sendICCEnvelopeCommand(command); michael@0: }, michael@0: michael@0: /** michael@0: * Send STK Envelope(Event Download) command. michael@0: * @param event michael@0: */ michael@0: sendStkEventDownload: function(command) { michael@0: command.tag = BER_EVENT_DOWNLOAD_TAG; michael@0: command.eventList = command.event.eventType; michael@0: switch (command.eventList) { michael@0: case STK_EVENT_TYPE_LOCATION_STATUS: michael@0: command.deviceId = { michael@0: sourceId :STK_DEVICE_ID_ME, michael@0: destinationId: STK_DEVICE_ID_SIM michael@0: }; michael@0: command.locationStatus = command.event.locationStatus; michael@0: // Location info should only be provided when locationStatus is normal. michael@0: if (command.locationStatus == STK_SERVICE_STATE_NORMAL) { michael@0: command.locationInfo = command.event.locationInfo; michael@0: } michael@0: break; michael@0: case STK_EVENT_TYPE_MT_CALL: michael@0: command.deviceId = { michael@0: sourceId: STK_DEVICE_ID_NETWORK, michael@0: destinationId: STK_DEVICE_ID_SIM michael@0: }; michael@0: command.transactionId = 0; michael@0: command.address = command.event.number; michael@0: break; michael@0: case STK_EVENT_TYPE_CALL_DISCONNECTED: michael@0: command.cause = command.event.error; michael@0: // Fall through. michael@0: case STK_EVENT_TYPE_CALL_CONNECTED: michael@0: command.deviceId = { michael@0: sourceId: (command.event.isIssuedByRemote ? michael@0: STK_DEVICE_ID_NETWORK : STK_DEVICE_ID_ME), michael@0: destinationId: STK_DEVICE_ID_SIM michael@0: }; michael@0: command.transactionId = 0; michael@0: break; michael@0: case STK_EVENT_TYPE_USER_ACTIVITY: michael@0: command.deviceId = { michael@0: sourceId: STK_DEVICE_ID_ME, michael@0: destinationId: STK_DEVICE_ID_SIM michael@0: }; michael@0: break; michael@0: case STK_EVENT_TYPE_IDLE_SCREEN_AVAILABLE: michael@0: command.deviceId = { michael@0: sourceId: STK_DEVICE_ID_DISPLAY, michael@0: destinationId: STK_DEVICE_ID_SIM michael@0: }; michael@0: break; michael@0: case STK_EVENT_TYPE_LANGUAGE_SELECTION: michael@0: command.deviceId = { michael@0: sourceId: STK_DEVICE_ID_ME, michael@0: destinationId: STK_DEVICE_ID_SIM michael@0: }; michael@0: command.language = command.event.language; michael@0: break; michael@0: case STK_EVENT_TYPE_BROWSER_TERMINATION: michael@0: command.deviceId = { michael@0: sourceId: STK_DEVICE_ID_ME, michael@0: destinationId: STK_DEVICE_ID_SIM michael@0: }; michael@0: command.terminationCause = command.event.terminationCause; michael@0: break; michael@0: } michael@0: this.sendICCEnvelopeCommand(command); michael@0: }, michael@0: michael@0: /** michael@0: * Send REQUEST_STK_SEND_ENVELOPE_COMMAND to ICC. michael@0: * michael@0: * @param tag michael@0: * @patam deviceId michael@0: * @param [optioanl] itemIdentifier michael@0: * @param [optional] helpRequested michael@0: * @param [optional] eventList michael@0: * @param [optional] locationStatus michael@0: * @param [optional] locationInfo michael@0: * @param [optional] address michael@0: * @param [optional] transactionId michael@0: * @param [optional] cause michael@0: * @param [optional] timerId michael@0: * @param [optional] timerValue michael@0: * @param [optional] terminationCause michael@0: */ michael@0: sendICCEnvelopeCommand: function(options) { michael@0: if (DEBUG) { michael@0: this.context.debug("Stk Envelope " + JSON.stringify(options)); michael@0: } michael@0: michael@0: let Buf = this.context.Buf; michael@0: let ComprehensionTlvHelper = this.context.ComprehensionTlvHelper; michael@0: let GsmPDUHelper = this.context.GsmPDUHelper; michael@0: michael@0: Buf.newParcel(REQUEST_STK_SEND_ENVELOPE_COMMAND); michael@0: michael@0: // 1st mark for Parcel size michael@0: Buf.startCalOutgoingSize(function(size) { michael@0: // Parcel size is in string length, which costs 2 uint8 per char. michael@0: Buf.writeInt32(size / 2); michael@0: }); michael@0: michael@0: // Write a BER-TLV michael@0: GsmPDUHelper.writeHexOctet(options.tag); michael@0: // 2nd mark for BER length michael@0: Buf.startCalOutgoingSize(function(size) { michael@0: // BER length is in number of hexOctets, which costs 4 uint8 per hexOctet. michael@0: GsmPDUHelper.writeHexOctet(size / 4); michael@0: }); michael@0: michael@0: // Device Identifies michael@0: GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_DEVICE_ID | michael@0: COMPREHENSIONTLV_FLAG_CR); michael@0: GsmPDUHelper.writeHexOctet(2); michael@0: GsmPDUHelper.writeHexOctet(options.deviceId.sourceId); michael@0: GsmPDUHelper.writeHexOctet(options.deviceId.destinationId); michael@0: michael@0: // Item Identifier michael@0: if (options.itemIdentifier != null) { michael@0: GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_ITEM_ID | michael@0: COMPREHENSIONTLV_FLAG_CR); michael@0: GsmPDUHelper.writeHexOctet(1); michael@0: GsmPDUHelper.writeHexOctet(options.itemIdentifier); michael@0: } michael@0: michael@0: // Help Request michael@0: if (options.helpRequested) { michael@0: GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_HELP_REQUEST | michael@0: COMPREHENSIONTLV_FLAG_CR); michael@0: GsmPDUHelper.writeHexOctet(0); michael@0: // Help Request doesn't have value michael@0: } michael@0: michael@0: // Event List michael@0: if (options.eventList != null) { michael@0: GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_EVENT_LIST | michael@0: COMPREHENSIONTLV_FLAG_CR); michael@0: GsmPDUHelper.writeHexOctet(1); michael@0: GsmPDUHelper.writeHexOctet(options.eventList); michael@0: } michael@0: michael@0: // Location Status michael@0: if (options.locationStatus != null) { michael@0: let len = options.locationStatus.length; michael@0: GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_LOCATION_STATUS | michael@0: COMPREHENSIONTLV_FLAG_CR); michael@0: GsmPDUHelper.writeHexOctet(1); michael@0: GsmPDUHelper.writeHexOctet(options.locationStatus); michael@0: } michael@0: michael@0: // Location Info michael@0: if (options.locationInfo) { michael@0: ComprehensionTlvHelper.writeLocationInfoTlv(options.locationInfo); michael@0: } michael@0: michael@0: // Transaction Id michael@0: if (options.transactionId != null) { michael@0: GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_TRANSACTION_ID | michael@0: COMPREHENSIONTLV_FLAG_CR); michael@0: GsmPDUHelper.writeHexOctet(1); michael@0: GsmPDUHelper.writeHexOctet(options.transactionId); michael@0: } michael@0: michael@0: // Address michael@0: if (options.address) { michael@0: GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_ADDRESS | michael@0: COMPREHENSIONTLV_FLAG_CR); michael@0: ComprehensionTlvHelper.writeLength( michael@0: Math.ceil(options.address.length/2) + 1 // address BCD + TON michael@0: ); michael@0: this.context.ICCPDUHelper.writeDiallingNumber(options.address); michael@0: } michael@0: michael@0: // Cause of disconnection. michael@0: if (options.cause != null) { michael@0: ComprehensionTlvHelper.writeCauseTlv(options.cause); michael@0: } michael@0: michael@0: // Timer Identifier michael@0: if (options.timerId != null) { michael@0: GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_TIMER_IDENTIFIER | michael@0: COMPREHENSIONTLV_FLAG_CR); michael@0: GsmPDUHelper.writeHexOctet(1); michael@0: GsmPDUHelper.writeHexOctet(options.timerId); michael@0: } michael@0: michael@0: // Timer Value michael@0: if (options.timerValue != null) { michael@0: ComprehensionTlvHelper.writeTimerValueTlv(options.timerValue, true); michael@0: } michael@0: michael@0: // Language michael@0: if (options.language) { michael@0: ComprehensionTlvHelper.writeLanguageTlv(options.language); michael@0: } michael@0: michael@0: // Browser Termination michael@0: if (options.terminationCause != null) { michael@0: GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_BROWSER_TERMINATION_CAUSE | michael@0: COMPREHENSIONTLV_FLAG_CR); michael@0: GsmPDUHelper.writeHexOctet(1); michael@0: GsmPDUHelper.writeHexOctet(options.terminationCause); michael@0: } michael@0: michael@0: // Calculate and write BER length to 2nd mark michael@0: Buf.stopCalOutgoingSize(); michael@0: michael@0: // Calculate and write Parcel size to 1st mark michael@0: Buf.stopCalOutgoingSize(); michael@0: michael@0: Buf.writeInt32(0); michael@0: Buf.sendParcel(); michael@0: }, michael@0: michael@0: /** michael@0: * Check a given number against the list of emergency numbers provided by the RIL. michael@0: * michael@0: * @param number michael@0: * The number to look up. michael@0: */ michael@0: _isEmergencyNumber: function(number) { michael@0: // Check ril provided numbers first. michael@0: let numbers = RIL_EMERGENCY_NUMBERS; michael@0: michael@0: if (numbers) { michael@0: numbers = numbers.split(","); michael@0: } else { michael@0: // No ecclist system property, so use our own list. michael@0: numbers = DEFAULT_EMERGENCY_NUMBERS; michael@0: } michael@0: michael@0: return numbers.indexOf(number) != -1; michael@0: }, michael@0: michael@0: /** michael@0: * Checks whether to temporarily suppress caller id for the call. michael@0: * michael@0: * @param mmi michael@0: * MMI full object. michael@0: */ michael@0: _isTemporaryModeCLIR: function(mmi) { michael@0: return (mmi && michael@0: mmi.serviceCode == MMI_SC_CLIR && michael@0: mmi.dialNumber && michael@0: (mmi.procedure == MMI_PROCEDURE_ACTIVATION || michael@0: mmi.procedure == MMI_PROCEDURE_DEACTIVATION)); michael@0: }, michael@0: michael@0: /** michael@0: * Report STK Service is running. michael@0: */ michael@0: reportStkServiceIsRunning: function() { michael@0: this.context.Buf.simpleRequest(REQUEST_REPORT_STK_SERVICE_IS_RUNNING); michael@0: }, michael@0: michael@0: /** michael@0: * Process ICC status. michael@0: */ michael@0: _processICCStatus: function(iccStatus) { michael@0: // If |_waitingRadioTech| is true, we should not get app information because michael@0: // the |_isCdma| flag is not ready yet. Otherwise we may use wrong index to michael@0: // get app information, especially for the case that icc card has both cdma michael@0: // and gsm subscription. michael@0: if (this._waitingRadioTech) { michael@0: return; michael@0: } michael@0: michael@0: this.iccStatus = iccStatus; michael@0: let newCardState; michael@0: let index = this._isCdma ? iccStatus.cdmaSubscriptionAppIndex : michael@0: iccStatus.gsmUmtsSubscriptionAppIndex; michael@0: let app = iccStatus.apps[index]; michael@0: michael@0: // When |iccStatus.cardState| is not CARD_STATE_PRESENT or have incorrect michael@0: // app information, we can not get iccId. So treat ICC as undetected. michael@0: if (iccStatus.cardState !== CARD_STATE_PRESENT || !app) { michael@0: if (this.cardState !== GECKO_CARDSTATE_UNDETECTED) { michael@0: this.operator = null; michael@0: // We should send |cardstatechange| before |iccinfochange|, otherwise we michael@0: // may lost cardstatechange event when icc card becomes undetected. michael@0: this.cardState = GECKO_CARDSTATE_UNDETECTED; michael@0: this.sendChromeMessage({rilMessageType: "cardstatechange", michael@0: cardState: this.cardState}); michael@0: michael@0: this.iccInfo = {iccType: null}; michael@0: this.context.ICCUtilsHelper.handleICCInfoChange(); michael@0: } michael@0: return; michael@0: } michael@0: michael@0: let ICCRecordHelper = this.context.ICCRecordHelper; michael@0: // fetchICCRecords will need to read aid, so read aid here. michael@0: this.aid = app.aid; michael@0: this.appType = app.app_type; michael@0: this.iccInfo.iccType = GECKO_CARD_TYPE[this.appType]; michael@0: // Try to get iccId only when cardState left GECKO_CARDSTATE_UNDETECTED. michael@0: if (iccStatus.cardState === CARD_STATE_PRESENT && michael@0: (this.cardState === GECKO_CARDSTATE_UNINITIALIZED || michael@0: this.cardState === GECKO_CARDSTATE_UNDETECTED)) { michael@0: ICCRecordHelper.readICCID(); michael@0: } michael@0: michael@0: switch (app.app_state) { michael@0: case CARD_APPSTATE_ILLEGAL: michael@0: newCardState = GECKO_CARDSTATE_ILLEGAL; michael@0: break; michael@0: case CARD_APPSTATE_PIN: michael@0: newCardState = GECKO_CARDSTATE_PIN_REQUIRED; michael@0: break; michael@0: case CARD_APPSTATE_PUK: michael@0: newCardState = GECKO_CARDSTATE_PUK_REQUIRED; michael@0: break; michael@0: case CARD_APPSTATE_SUBSCRIPTION_PERSO: michael@0: newCardState = PERSONSUBSTATE[app.perso_substate]; michael@0: break; michael@0: case CARD_APPSTATE_READY: michael@0: newCardState = GECKO_CARDSTATE_READY; michael@0: break; michael@0: case CARD_APPSTATE_UNKNOWN: michael@0: case CARD_APPSTATE_DETECTED: michael@0: // Fall through. michael@0: default: michael@0: newCardState = GECKO_CARDSTATE_UNKNOWN; michael@0: } michael@0: michael@0: let pin1State = app.pin1_replaced ? iccStatus.universalPINState : michael@0: app.pin1; michael@0: if (pin1State === CARD_PINSTATE_ENABLED_PERM_BLOCKED) { michael@0: newCardState = GECKO_CARDSTATE_PERMANENT_BLOCKED; michael@0: } michael@0: michael@0: if (this.cardState == newCardState) { michael@0: return; michael@0: } michael@0: michael@0: // This was moved down from CARD_APPSTATE_READY michael@0: this.requestNetworkInfo(); michael@0: if (newCardState == GECKO_CARDSTATE_READY) { michael@0: // For type SIM, we need to check EF_phase first. michael@0: // Other types of ICC we can send Terminal_Profile immediately. michael@0: if (this.appType == CARD_APPTYPE_SIM) { michael@0: this.context.SimRecordHelper.readSimPhase(); michael@0: } else if (RILQUIRKS_SEND_STK_PROFILE_DOWNLOAD) { michael@0: this.sendStkTerminalProfile(STK_SUPPORTED_TERMINAL_PROFILE); michael@0: } michael@0: michael@0: ICCRecordHelper.fetchICCRecords(); michael@0: } michael@0: michael@0: this.cardState = newCardState; michael@0: this.sendChromeMessage({rilMessageType: "cardstatechange", michael@0: cardState: this.cardState}); michael@0: }, michael@0: michael@0: /** michael@0: * Helper for processing responses of functions such as enterICC* and changeICC*. michael@0: */ michael@0: _processEnterAndChangeICCResponses: function(length, options) { michael@0: options.success = (options.rilRequestError === 0); michael@0: if (!options.success) { michael@0: options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError]; michael@0: } michael@0: options.retryCount = length ? this.context.Buf.readInt32List()[0] : -1; michael@0: if (options.rilMessageType != "sendMMI") { michael@0: this.sendChromeMessage(options); michael@0: return; michael@0: } michael@0: michael@0: let mmiServiceCode = options.mmiServiceCode; michael@0: michael@0: if (options.success) { michael@0: switch (mmiServiceCode) { michael@0: case MMI_KS_SC_PIN: michael@0: options.statusMessage = MMI_SM_KS_PIN_CHANGED; michael@0: break; michael@0: case MMI_KS_SC_PIN2: michael@0: options.statusMessage = MMI_SM_KS_PIN2_CHANGED; michael@0: break; michael@0: case MMI_KS_SC_PUK: michael@0: options.statusMessage = MMI_SM_KS_PIN_UNBLOCKED; michael@0: break; michael@0: case MMI_KS_SC_PUK2: michael@0: options.statusMessage = MMI_SM_KS_PIN2_UNBLOCKED; michael@0: break; michael@0: } michael@0: } else { michael@0: if (options.retryCount <= 0) { michael@0: if (mmiServiceCode === MMI_KS_SC_PUK) { michael@0: options.errorMsg = MMI_ERROR_KS_SIM_BLOCKED; michael@0: } else if (mmiServiceCode === MMI_KS_SC_PIN) { michael@0: options.errorMsg = MMI_ERROR_KS_NEEDS_PUK; michael@0: } michael@0: } else { michael@0: if (mmiServiceCode === MMI_KS_SC_PIN || michael@0: mmiServiceCode === MMI_KS_SC_PIN2) { michael@0: options.errorMsg = MMI_ERROR_KS_BAD_PIN; michael@0: } else if (mmiServiceCode === MMI_KS_SC_PUK || michael@0: mmiServiceCode === MMI_KS_SC_PUK2) { michael@0: options.errorMsg = MMI_ERROR_KS_BAD_PUK; michael@0: } michael@0: if (options.retryCount !== undefined) { michael@0: options.additionalInformation = options.retryCount; michael@0: } michael@0: } michael@0: } michael@0: michael@0: this.sendChromeMessage(options); michael@0: }, michael@0: michael@0: // We combine all of the NETWORK_INFO_MESSAGE_TYPES into one "networkinfochange" michael@0: // message to the RadioInterfaceLayer, so we can avoid sending multiple michael@0: // VoiceInfoChanged events for both operator / voice_data_registration michael@0: // michael@0: // State management here is a little tricky. We need to know both: michael@0: // 1. Whether or not a response was received for each of the michael@0: // NETWORK_INFO_MESSAGE_TYPES michael@0: // 2. The outbound message that corresponds with that response -- but this michael@0: // only happens when internal state changes (i.e. it isn't guaranteed) michael@0: // michael@0: // To collect this state, each message response function first calls michael@0: // _receivedNetworkInfo, to mark the response as received. When the michael@0: // final response is received, a call to _sendPendingNetworkInfo is placed michael@0: // on the next tick of the worker thread. michael@0: // michael@0: // Since the original call to _receivedNetworkInfo happens at the top michael@0: // of the response handler, this gives the final handler a chance to michael@0: // queue up it's "changed" message by calling _sendNetworkInfoMessage if/when michael@0: // the internal state has actually changed. michael@0: _sendNetworkInfoMessage: function(type, message) { michael@0: if (!this._processingNetworkInfo) { michael@0: // We only combine these messages in the case of the combined request michael@0: // in requestNetworkInfo() michael@0: this.sendChromeMessage(message); michael@0: return; michael@0: } michael@0: michael@0: if (DEBUG) { michael@0: this.context.debug("Queuing " + type + " network info message: " + michael@0: JSON.stringify(message)); michael@0: } michael@0: this._pendingNetworkInfo[type] = message; michael@0: }, michael@0: michael@0: _receivedNetworkInfo: function(type) { michael@0: if (DEBUG) this.context.debug("Received " + type + " network info."); michael@0: if (!this._processingNetworkInfo) { michael@0: return; michael@0: } michael@0: michael@0: let pending = this._pendingNetworkInfo; michael@0: michael@0: // We still need to track states for events that aren't fired. michael@0: if (!(type in pending)) { michael@0: pending[type] = this.pendingNetworkType; michael@0: } michael@0: michael@0: // Pending network info is ready to be sent when no more messages michael@0: // are waiting for responses, but the combined payload hasn't been sent. michael@0: for (let i = 0; i < NETWORK_INFO_MESSAGE_TYPES.length; i++) { michael@0: let msgType = NETWORK_INFO_MESSAGE_TYPES[i]; michael@0: if (!(msgType in pending)) { michael@0: if (DEBUG) { michael@0: this.context.debug("Still missing some more network info, not " + michael@0: "notifying main thread."); michael@0: } michael@0: return; michael@0: } michael@0: } michael@0: michael@0: // Do a pass to clean up the processed messages that didn't create michael@0: // a response message, so we don't have unused keys in the outbound michael@0: // networkinfochanged message. michael@0: for (let key in pending) { michael@0: if (pending[key] == this.pendingNetworkType) { michael@0: delete pending[key]; michael@0: } michael@0: } michael@0: michael@0: if (DEBUG) { michael@0: this.context.debug("All pending network info has been received: " + michael@0: JSON.stringify(pending)); michael@0: } michael@0: michael@0: // Send the message on the next tick of the worker's loop, so we give the michael@0: // last message a chance to call _sendNetworkInfoMessage first. michael@0: setTimeout(this._sendPendingNetworkInfo.bind(this), 0); michael@0: }, michael@0: michael@0: _sendPendingNetworkInfo: function() { michael@0: this.sendChromeMessage(this._pendingNetworkInfo); michael@0: michael@0: this._processingNetworkInfo = false; michael@0: for (let i = 0; i < NETWORK_INFO_MESSAGE_TYPES.length; i++) { michael@0: delete this._pendingNetworkInfo[NETWORK_INFO_MESSAGE_TYPES[i]]; michael@0: } michael@0: michael@0: if (this._needRepollNetworkInfo) { michael@0: this._needRepollNetworkInfo = false; michael@0: this.requestNetworkInfo(); michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Normalize the signal strength in dBm to the signal level from 0 to 100. michael@0: * michael@0: * @param signal michael@0: * The signal strength in dBm to normalize. michael@0: * @param min michael@0: * The signal strength in dBm maps to level 0. michael@0: * @param max michael@0: * The signal strength in dBm maps to level 100. michael@0: * michael@0: * @return level michael@0: * The signal level from 0 to 100. michael@0: */ michael@0: _processSignalLevel: function(signal, min, max) { michael@0: if (signal <= min) { michael@0: return 0; michael@0: } michael@0: michael@0: if (signal >= max) { michael@0: return 100; michael@0: } michael@0: michael@0: return Math.floor((signal - min) * 100 / (max - min)); michael@0: }, michael@0: michael@0: /** michael@0: * Process LTE signal strength to the signal info object. michael@0: * michael@0: * @param signal michael@0: * The signal object reported from RIL/modem. michael@0: * michael@0: * @return The object of signal strength info. michael@0: * Or null if invalid signal input. michael@0: */ michael@0: _processLteSignal: function(signal) { michael@0: // Valid values are 0-63 as defined in TS 27.007 clause 8.69. michael@0: if (signal.lteSignalStrength === undefined || michael@0: signal.lteSignalStrength < 0 || michael@0: signal.lteSignalStrength > 63) { michael@0: return null; michael@0: } michael@0: michael@0: let info = { michael@0: voice: { michael@0: signalStrength: null, michael@0: relSignalStrength: null michael@0: }, michael@0: data: { michael@0: signalStrength: null, michael@0: relSignalStrength: null michael@0: } michael@0: }; michael@0: michael@0: // TODO: Bug 982013: reconsider signalStrength/relSignalStrength APIs for michael@0: // GSM/CDMA/LTE, and take rsrp/rssnr into account for LTE case then. michael@0: let signalStrength = -111 + signal.lteSignalStrength; michael@0: info.voice.signalStrength = info.data.signalStrength = signalStrength; michael@0: // 0 and 12 are referred to AOSP's implementation. These values are not michael@0: // constants and can be customized based on different requirements. michael@0: let signalLevel = this._processSignalLevel(signal.lteSignalStrength, 0, 12); michael@0: info.voice.relSignalStrength = info.data.relSignalStrength = signalLevel; michael@0: michael@0: return info; michael@0: }, michael@0: michael@0: _processSignalStrength: function(signal) { michael@0: let info = { michael@0: voice: { michael@0: signalStrength: null, michael@0: relSignalStrength: null michael@0: }, michael@0: data: { michael@0: signalStrength: null, michael@0: relSignalStrength: null michael@0: } michael@0: }; michael@0: michael@0: // During startup, |radioTech| is not yet defined, so we need to michael@0: // check it separately. michael@0: if (("radioTech" in this.voiceRegistrationState) && michael@0: !this._isGsmTechGroup(this.voiceRegistrationState.radioTech)) { michael@0: // CDMA RSSI. michael@0: // Valid values are positive integers. This value is the actual RSSI value michael@0: // multiplied by -1. Example: If the actual RSSI is -75, then this michael@0: // response value will be 75. michael@0: if (signal.cdmaDBM && signal.cdmaDBM > 0) { michael@0: let signalStrength = -1 * signal.cdmaDBM; michael@0: info.voice.signalStrength = signalStrength; michael@0: michael@0: // -105 and -70 are referred to AOSP's implementation. These values are michael@0: // not constants and can be customized based on different requirement. michael@0: let signalLevel = this._processSignalLevel(signalStrength, -105, -70); michael@0: info.voice.relSignalStrength = signalLevel; michael@0: } michael@0: michael@0: // EVDO RSSI. michael@0: // Valid values are positive integers. This value is the actual RSSI value michael@0: // multiplied by -1. Example: If the actual RSSI is -75, then this michael@0: // response value will be 75. michael@0: if (signal.evdoDBM && signal.evdoDBM > 0) { michael@0: let signalStrength = -1 * signal.evdoDBM; michael@0: info.data.signalStrength = signalStrength; michael@0: michael@0: // -105 and -70 are referred to AOSP's implementation. These values are michael@0: // not constants and can be customized based on different requirement. michael@0: let signalLevel = this._processSignalLevel(signalStrength, -105, -70); michael@0: info.data.relSignalStrength = signalLevel; michael@0: } michael@0: } else { michael@0: // Check LTE level first, and check GSM/UMTS level next if LTE one is not michael@0: // valid. michael@0: let lteInfo = this._processLteSignal(signal); michael@0: if (lteInfo) { michael@0: info = lteInfo; michael@0: } else { michael@0: // GSM signal strength. michael@0: // Valid values are 0-31 as defined in TS 27.007 8.5. michael@0: // 0 : -113 dBm or less michael@0: // 1 : -111 dBm michael@0: // 2...30: -109...-53 dBm michael@0: // 31 : -51 dBm michael@0: if (signal.gsmSignalStrength && michael@0: signal.gsmSignalStrength >= 0 && michael@0: signal.gsmSignalStrength <= 31) { michael@0: let signalStrength = -113 + 2 * signal.gsmSignalStrength; michael@0: info.voice.signalStrength = info.data.signalStrength = signalStrength; michael@0: michael@0: // -115 and -85 are referred to AOSP's implementation. These values are michael@0: // not constants and can be customized based on different requirement. michael@0: let signalLevel = this._processSignalLevel(signalStrength, -110, -85); michael@0: info.voice.relSignalStrength = info.data.relSignalStrength = signalLevel; michael@0: } michael@0: } michael@0: } michael@0: michael@0: info.rilMessageType = "signalstrengthchange"; michael@0: this._sendNetworkInfoMessage(NETWORK_INFO_SIGNAL, info); michael@0: michael@0: if (this.cachedDialRequest && info.voice.signalStrength) { michael@0: // Radio is ready for making the cached emergency call. michael@0: this.cachedDialRequest.callback(); michael@0: this.cachedDialRequest = null; michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Process the network registration flags. michael@0: * michael@0: * @return true if the state changed, false otherwise. michael@0: */ michael@0: _processCREG: function(curState, newState) { michael@0: let changed = false; michael@0: michael@0: let regState = this.parseInt(newState[0], NETWORK_CREG_STATE_UNKNOWN); michael@0: if (curState.regState === undefined || curState.regState !== regState) { michael@0: changed = true; michael@0: curState.regState = regState; michael@0: michael@0: curState.state = NETWORK_CREG_TO_GECKO_MOBILE_CONNECTION_STATE[regState]; michael@0: curState.connected = regState == NETWORK_CREG_STATE_REGISTERED_HOME || michael@0: regState == NETWORK_CREG_STATE_REGISTERED_ROAMING; michael@0: curState.roaming = regState == NETWORK_CREG_STATE_REGISTERED_ROAMING; michael@0: curState.emergencyCallsOnly = !curState.connected; michael@0: } michael@0: michael@0: if (!curState.cell) { michael@0: curState.cell = {}; michael@0: } michael@0: michael@0: // From TS 23.003, 0000 and 0xfffe are indicated that no valid LAI exists michael@0: // in MS. So we still need to report the '0000' as well. michael@0: let lac = this.parseInt(newState[1], -1, 16); michael@0: if (curState.cell.gsmLocationAreaCode === undefined || michael@0: curState.cell.gsmLocationAreaCode !== lac) { michael@0: curState.cell.gsmLocationAreaCode = lac; michael@0: changed = true; michael@0: } michael@0: michael@0: let cid = this.parseInt(newState[2], -1, 16); michael@0: if (curState.cell.gsmCellId === undefined || michael@0: curState.cell.gsmCellId !== cid) { michael@0: curState.cell.gsmCellId = cid; michael@0: changed = true; michael@0: } michael@0: michael@0: let radioTech = (newState[3] === undefined ? michael@0: NETWORK_CREG_TECH_UNKNOWN : michael@0: this.parseInt(newState[3], NETWORK_CREG_TECH_UNKNOWN)); michael@0: if (curState.radioTech === undefined || curState.radioTech !== radioTech) { michael@0: changed = true; michael@0: curState.radioTech = radioTech; michael@0: curState.type = GECKO_RADIO_TECH[radioTech] || null; michael@0: } michael@0: return changed; michael@0: }, michael@0: michael@0: _processVoiceRegistrationState: function(state) { michael@0: let rs = this.voiceRegistrationState; michael@0: let stateChanged = this._processCREG(rs, state); michael@0: if (stateChanged && rs.connected) { michael@0: this.getSmscAddress(); michael@0: } michael@0: michael@0: let cell = rs.cell; michael@0: if (this._isCdma) { michael@0: // Some variables below are not used. Comment them instead of removing to michael@0: // keep the information about state[x]. michael@0: let cdmaBaseStationId = this.parseInt(state[4], -1); michael@0: let cdmaBaseStationLatitude = this.parseInt(state[5], -2147483648); michael@0: let cdmaBaseStationLongitude = this.parseInt(state[6], -2147483648); michael@0: // let cssIndicator = this.parseInt(state[7]); michael@0: let cdmaSystemId = this.parseInt(state[8], -1); michael@0: let cdmaNetworkId = this.parseInt(state[9], -1); michael@0: // let roamingIndicator = this.parseInt(state[10]); michael@0: // let systemIsInPRL = this.parseInt(state[11]); michael@0: // let defaultRoamingIndicator = this.parseInt(state[12]); michael@0: // let reasonForDenial = this.parseInt(state[13]); michael@0: michael@0: if (cell.cdmaBaseStationId !== cdmaBaseStationId || michael@0: cell.cdmaBaseStationLatitude !== cdmaBaseStationLatitude || michael@0: cell.cdmaBaseStationLongitude !== cdmaBaseStationLongitude || michael@0: cell.cdmaSystemId !== cdmaSystemId || michael@0: cell.cdmaNetworkId !== cdmaNetworkId) { michael@0: stateChanged = true; michael@0: cell.cdmaBaseStationId = cdmaBaseStationId; michael@0: cell.cdmaBaseStationLatitude = cdmaBaseStationLatitude; michael@0: cell.cdmaBaseStationLongitude = cdmaBaseStationLongitude; michael@0: cell.cdmaSystemId = cdmaSystemId; michael@0: cell.cdmaNetworkId = cdmaNetworkId; michael@0: } michael@0: } michael@0: michael@0: if (stateChanged) { michael@0: rs.rilMessageType = "voiceregistrationstatechange"; michael@0: this._sendNetworkInfoMessage(NETWORK_INFO_VOICE_REGISTRATION_STATE, rs); michael@0: } michael@0: }, michael@0: michael@0: _processDataRegistrationState: function(state) { michael@0: let rs = this.dataRegistrationState; michael@0: let stateChanged = this._processCREG(rs, state); michael@0: if (stateChanged) { michael@0: rs.rilMessageType = "dataregistrationstatechange"; michael@0: this._sendNetworkInfoMessage(NETWORK_INFO_DATA_REGISTRATION_STATE, rs); michael@0: } michael@0: }, michael@0: michael@0: _processOperator: function(operatorData) { michael@0: if (operatorData.length < 3) { michael@0: if (DEBUG) { michael@0: this.context.debug("Expected at least 3 strings for operator."); michael@0: } michael@0: } michael@0: michael@0: if (!this.operator) { michael@0: this.operator = { michael@0: rilMessageType: "operatorchange", michael@0: longName: null, michael@0: shortName: null michael@0: }; michael@0: } michael@0: michael@0: let [longName, shortName, networkTuple] = operatorData; michael@0: let thisTuple = (this.operator.mcc || "") + (this.operator.mnc || ""); michael@0: michael@0: if (this.operator.longName !== longName || michael@0: this.operator.shortName !== shortName || michael@0: thisTuple !== networkTuple) { michael@0: michael@0: this.operator.mcc = null; michael@0: this.operator.mnc = null; michael@0: michael@0: if (networkTuple) { michael@0: try { michael@0: this._processNetworkTuple(networkTuple, this.operator); michael@0: } catch (e) { michael@0: if (DEBUG) this.context.debug("Error processing operator tuple: " + e); michael@0: } michael@0: } else { michael@0: // According to ril.h, the operator fields will be NULL when the operator michael@0: // is not currently registered. We can avoid trying to parse the numeric michael@0: // tuple in that case. michael@0: if (DEBUG) { michael@0: this.context.debug("Operator is currently unregistered"); michael@0: } michael@0: } michael@0: michael@0: let ICCUtilsHelper = this.context.ICCUtilsHelper; michael@0: let networkName; michael@0: // We won't get network name using PNN and OPL if voice registration isn't ready michael@0: if (this.voiceRegistrationState.cell && michael@0: this.voiceRegistrationState.cell.gsmLocationAreaCode != -1) { michael@0: networkName = ICCUtilsHelper.getNetworkNameFromICC( michael@0: this.operator.mcc, michael@0: this.operator.mnc, michael@0: this.voiceRegistrationState.cell.gsmLocationAreaCode); michael@0: } michael@0: michael@0: if (networkName) { michael@0: if (DEBUG) { michael@0: this.context.debug("Operator names will be overriden: " + michael@0: "longName = " + networkName.fullName + ", " + michael@0: "shortName = " + networkName.shortName); michael@0: } michael@0: michael@0: this.operator.longName = networkName.fullName; michael@0: this.operator.shortName = networkName.shortName; michael@0: } else { michael@0: this.operator.longName = longName; michael@0: this.operator.shortName = shortName; michael@0: } michael@0: michael@0: if (ICCUtilsHelper.updateDisplayCondition()) { michael@0: ICCUtilsHelper.handleICCInfoChange(); michael@0: } michael@0: this._sendNetworkInfoMessage(NETWORK_INFO_OPERATOR, this.operator); michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Helpers for processing call state and handle the active call. michael@0: */ michael@0: _processCalls: function(newCalls) { michael@0: let conferenceChanged = false; michael@0: let clearConferenceRequest = false; michael@0: let pendingOutgoingCall = null; michael@0: michael@0: // Go through the calls we currently have on file and see if any of them michael@0: // changed state. Remove them from the newCalls map as we deal with them michael@0: // so that only new calls remain in the map after we're done. michael@0: for each (let currentCall in this.currentCalls) { michael@0: if (currentCall.callIndex == OUTGOING_PLACEHOLDER_CALL_INDEX) { michael@0: pendingOutgoingCall = currentCall; michael@0: continue; michael@0: } michael@0: michael@0: let newCall; michael@0: if (newCalls) { michael@0: newCall = newCalls[currentCall.callIndex]; michael@0: delete newCalls[currentCall.callIndex]; michael@0: } michael@0: michael@0: // Call is no longer reported by the radio. Remove from our map and send michael@0: // disconnected state change. michael@0: if (!newCall) { michael@0: if (this.currentConference.participants[currentCall.callIndex]) { michael@0: conferenceChanged = true; michael@0: } michael@0: this._removeVoiceCall(currentCall, michael@0: currentCall.hangUpLocal ? michael@0: GECKO_CALL_ERROR_NORMAL_CALL_CLEARING : null); michael@0: continue; michael@0: } michael@0: michael@0: // Call is still valid. michael@0: if (newCall.state == currentCall.state && michael@0: newCall.isMpty == currentCall.isMpty) { michael@0: continue; michael@0: } michael@0: michael@0: // State has changed. michael@0: if (newCall.state == CALL_STATE_INCOMING && michael@0: currentCall.state == CALL_STATE_WAITING) { michael@0: // Update the call internally but we don't notify chrome since these two michael@0: // states are viewed as the same one there. michael@0: currentCall.state = newCall.state; michael@0: continue; michael@0: } michael@0: michael@0: if (!currentCall.started && newCall.state == CALL_STATE_ACTIVE) { michael@0: currentCall.started = new Date().getTime(); michael@0: } michael@0: michael@0: if (currentCall.isMpty == newCall.isMpty && michael@0: newCall.state != currentCall.state) { michael@0: currentCall.state = newCall.state; michael@0: if (currentCall.isConference) { michael@0: conferenceChanged = true; michael@0: } michael@0: this._handleChangedCallState(currentCall); michael@0: continue; michael@0: } michael@0: michael@0: // '.isMpty' becomes false when the conference call is put on hold. michael@0: // We need to introduce additional 'isConference' to correctly record the michael@0: // real conference status michael@0: michael@0: // Update a possible conference participant when .isMpty changes. michael@0: if (!currentCall.isMpty && newCall.isMpty) { michael@0: if (this._hasConferenceRequest) { michael@0: conferenceChanged = true; michael@0: clearConferenceRequest = true; michael@0: currentCall.state = newCall.state; michael@0: currentCall.isMpty = newCall.isMpty; michael@0: currentCall.isConference = true; michael@0: this.currentConference.participants[currentCall.callIndex] = currentCall; michael@0: this._handleChangedCallState(currentCall); michael@0: } else if (currentCall.isConference) { michael@0: // The case happens when resuming a held conference call. michael@0: conferenceChanged = true; michael@0: currentCall.state = newCall.state; michael@0: currentCall.isMpty = newCall.isMpty; michael@0: this.currentConference.participants[currentCall.callIndex] = currentCall; michael@0: this._handleChangedCallState(currentCall); michael@0: } else { michael@0: // Weird. This sometimes happens when we switch two calls, but it is michael@0: // not a conference call. michael@0: currentCall.state = newCall.state; michael@0: this._handleChangedCallState(currentCall); michael@0: } michael@0: } else if (currentCall.isMpty && !newCall.isMpty) { michael@0: if (!this.currentConference.participants[newCall.callIndex]) { michael@0: continue; michael@0: } michael@0: michael@0: // '.isMpty' of a conference participant is set to false by rild when michael@0: // the conference call is put on hold. We don't actually know if the call michael@0: // still attends the conference until updating all calls finishes. We michael@0: // cache it for further determination. michael@0: if (newCall.state != CALL_STATE_HOLDING) { michael@0: delete this.currentConference.participants[newCall.callIndex]; michael@0: currentCall.state = newCall.state; michael@0: currentCall.isMpty = newCall.isMpty; michael@0: currentCall.isConference = false; michael@0: conferenceChanged = true; michael@0: this._handleChangedCallState(currentCall); michael@0: continue; michael@0: } michael@0: michael@0: if (!this.currentConference.cache) { michael@0: this.currentConference.cache = {}; michael@0: } michael@0: this.currentConference.cache[currentCall.callIndex] = newCall; michael@0: currentCall.state = newCall.state; michael@0: currentCall.isMpty = newCall.isMpty; michael@0: conferenceChanged = true; michael@0: } michael@0: } michael@0: michael@0: if (pendingOutgoingCall) { michael@0: if (!newCalls || Object.keys(newCalls).length === 0) { michael@0: // We don't get a successful call for pendingOutgoingCall. michael@0: this._removePendingOutgoingCall(GECKO_CALL_ERROR_UNSPECIFIED); michael@0: } else { michael@0: // Only remove it from currentCalls map. Will use the new call to michael@0: // replace the placeholder. michael@0: delete this.currentCalls[OUTGOING_PLACEHOLDER_CALL_INDEX]; michael@0: } michael@0: } michael@0: michael@0: // Go through any remaining calls that are new to us. michael@0: for each (let newCall in newCalls) { michael@0: if (newCall.isVoice) { michael@0: if (newCall.isMpty) { michael@0: conferenceChanged = true; michael@0: } michael@0: if (!pendingOutgoingCall && michael@0: (newCall.state === CALL_STATE_DIALING || michael@0: newCall.state === CALL_STATE_ALERTING)) { michael@0: // Receive a new outgoing call which is already hung up by user. michael@0: if (DEBUG) this.context.debug("Pending outgoing call is hung up by user."); michael@0: this.sendHangUpRequest(newCall.callIndex); michael@0: } else { michael@0: this._addNewVoiceCall(newCall); michael@0: } michael@0: } michael@0: } michael@0: michael@0: if (clearConferenceRequest) { michael@0: this._hasConferenceRequest = false; michael@0: } michael@0: if (conferenceChanged) { michael@0: this._ensureConference(); michael@0: } michael@0: }, michael@0: michael@0: _addNewVoiceCall: function(newCall) { michael@0: // Format international numbers appropriately. michael@0: if (newCall.number && newCall.toa == TOA_INTERNATIONAL && michael@0: newCall.number[0] != "+") { michael@0: newCall.number = "+" + newCall.number; michael@0: } michael@0: michael@0: if (newCall.state == CALL_STATE_INCOMING) { michael@0: newCall.isOutgoing = false; michael@0: } else if (newCall.state == CALL_STATE_DIALING) { michael@0: newCall.isOutgoing = true; michael@0: } michael@0: michael@0: // Set flag for outgoing emergency call. michael@0: newCall.isEmergency = newCall.isOutgoing && michael@0: this._isEmergencyNumber(newCall.number); michael@0: michael@0: // Set flag for conference. michael@0: newCall.isConference = newCall.isMpty ? true : false; michael@0: michael@0: // Add to our map. michael@0: if (newCall.isMpty) { michael@0: this.currentConference.participants[newCall.callIndex] = newCall; michael@0: } michael@0: this._handleChangedCallState(newCall); michael@0: this.currentCalls[newCall.callIndex] = newCall; michael@0: }, michael@0: michael@0: _removeVoiceCall: function(removedCall, failCause) { michael@0: if (this.currentConference.participants[removedCall.callIndex]) { michael@0: removedCall.isConference = false; michael@0: delete this.currentConference.participants[removedCall.callIndex]; michael@0: delete this.currentCalls[removedCall.callIndex]; michael@0: // We don't query the fail cause here as it triggers another asynchrouns michael@0: // request that leads to a problem of updating all conferece participants michael@0: // in one task. michael@0: this._handleDisconnectedCall(removedCall); michael@0: } else { michael@0: delete this.currentCalls[removedCall.callIndex]; michael@0: if (failCause) { michael@0: removedCall.failCause = failCause; michael@0: this._handleDisconnectedCall(removedCall); michael@0: } else { michael@0: this.getFailCauseCode((function(call, failCause) { michael@0: call.failCause = failCause; michael@0: this._handleDisconnectedCall(call); michael@0: }).bind(this, removedCall)); michael@0: } michael@0: } michael@0: }, michael@0: michael@0: _createPendingOutgoingCall: function(options) { michael@0: if (DEBUG) this.context.debug("Create a pending outgoing call."); michael@0: this._addNewVoiceCall({ michael@0: number: options.number, michael@0: state: CALL_STATE_DIALING, michael@0: callIndex: OUTGOING_PLACEHOLDER_CALL_INDEX michael@0: }); michael@0: }, michael@0: michael@0: _removePendingOutgoingCall: function(failCause) { michael@0: let call = this.currentCalls[OUTGOING_PLACEHOLDER_CALL_INDEX]; michael@0: if (!call) { michael@0: return; michael@0: } michael@0: michael@0: if (DEBUG) this.context.debug("Remove pending outgoing call."); michael@0: this._removeVoiceCall(pendingOutgoingCall, failCause); michael@0: }, michael@0: michael@0: _ensureConference: function() { michael@0: let oldState = this.currentConference.state; michael@0: let remaining = Object.keys(this.currentConference.participants); michael@0: michael@0: if (remaining.length == 1) { michael@0: // Remove that if only does one remain in a conference call. michael@0: let call = this.currentCalls[remaining[0]]; michael@0: call.isConference = false; michael@0: this._handleChangedCallState(call); michael@0: delete this.currentConference.participants[call.callIndex]; michael@0: } else if (remaining.length > 1) { michael@0: for each (let call in this.currentConference.cache) { michael@0: call.isConference = true; michael@0: this.currentConference.participants[call.callIndex] = call; michael@0: this.currentCalls[call.callIndex] = call; michael@0: this._handleChangedCallState(call); michael@0: } michael@0: } michael@0: delete this.currentConference.cache; michael@0: michael@0: // Update the conference call's state. michael@0: let state = CALL_STATE_UNKNOWN; michael@0: for each (let call in this.currentConference.participants) { michael@0: if (state != CALL_STATE_UNKNOWN && state != call.state) { michael@0: // Each participant should have the same state, otherwise something michael@0: // wrong happens. michael@0: state = CALL_STATE_UNKNOWN; michael@0: break; michael@0: } michael@0: state = call.state; michael@0: } michael@0: if (oldState != state) { michael@0: this.currentConference.state = state; michael@0: let message = {rilMessageType: "conferenceCallStateChanged", michael@0: state: state}; michael@0: this.sendChromeMessage(message); michael@0: } michael@0: }, michael@0: michael@0: _handleChangedCallState: function(changedCall) { michael@0: let message = {rilMessageType: "callStateChange", michael@0: call: changedCall}; michael@0: this.sendChromeMessage(message); michael@0: }, michael@0: michael@0: _handleDisconnectedCall: function(disconnectedCall) { michael@0: let message = {rilMessageType: "callDisconnected", michael@0: call: disconnectedCall}; michael@0: this.sendChromeMessage(message); michael@0: }, michael@0: michael@0: _sendDataCallError: function(message, errorCode) { michael@0: // Should not include token for unsolicited response. michael@0: delete message.rilMessageToken; michael@0: message.rilMessageType = "datacallerror"; michael@0: if (errorCode == ERROR_GENERIC_FAILURE) { michael@0: message.errorMsg = RIL_ERROR_TO_GECKO_ERROR[errorCode]; michael@0: } else { michael@0: message.errorMsg = RIL_DATACALL_FAILCAUSE_TO_GECKO_DATACALL_ERROR[errorCode]; michael@0: } michael@0: this.sendChromeMessage(message); michael@0: }, michael@0: michael@0: /** michael@0: * @return "deactivate" if changes or one of the currentDataCall michael@0: * addresses is missing in updatedDataCall, or "identical" if no michael@0: * changes found, or "changed" otherwise. michael@0: */ michael@0: _compareDataCallLink: function(updatedDataCall, currentDataCall) { michael@0: // If network interface is changed, report as "deactivate". michael@0: if (updatedDataCall.ifname != currentDataCall.ifname) { michael@0: return "deactivate"; michael@0: } michael@0: michael@0: // If any existing address is missing, report as "deactivate". michael@0: for (let i = 0; i < currentDataCall.addresses.length; i++) { michael@0: let address = currentDataCall.addresses[i]; michael@0: if (updatedDataCall.addresses.indexOf(address) < 0) { michael@0: return "deactivate"; michael@0: } michael@0: } michael@0: michael@0: if (currentDataCall.addresses.length != updatedDataCall.addresses.length) { michael@0: // Since now all |currentDataCall.addresses| are found in michael@0: // |updatedDataCall.addresses|, this means one or more new addresses are michael@0: // reported. michael@0: return "changed"; michael@0: } michael@0: michael@0: let fields = ["gateways", "dnses"]; michael@0: for (let i = 0; i < fields.length; i++) { michael@0: // Compare .. michael@0: let field = fields[i]; michael@0: let lhs = updatedDataCall[field], rhs = currentDataCall[field]; michael@0: if (lhs.length != rhs.length) { michael@0: return "changed"; michael@0: } michael@0: for (let i = 0; i < lhs.length; i++) { michael@0: if (lhs[i] != rhs[i]) { michael@0: return "changed"; michael@0: } michael@0: } michael@0: } michael@0: michael@0: return "identical"; michael@0: }, michael@0: michael@0: _processDataCallList: function(datacalls, newDataCallOptions) { michael@0: // Check for possible PDP errors: We check earlier because the datacall michael@0: // can be removed if is the same as the current one. michael@0: for each (let newDataCall in datacalls) { michael@0: if (newDataCall.status != DATACALL_FAIL_NONE) { michael@0: if (newDataCallOptions) { michael@0: newDataCall.apn = newDataCallOptions.apn; michael@0: } michael@0: this._sendDataCallError(newDataCall, newDataCall.status); michael@0: } michael@0: } michael@0: michael@0: for each (let currentDataCall in this.currentDataCalls) { michael@0: let updatedDataCall; michael@0: if (datacalls) { michael@0: updatedDataCall = datacalls[currentDataCall.cid]; michael@0: delete datacalls[currentDataCall.cid]; michael@0: } michael@0: michael@0: if (!updatedDataCall) { michael@0: // If datacalls list is coming from REQUEST_SETUP_DATA_CALL response, michael@0: // we do not change state for any currentDataCalls not in datacalls list. michael@0: if (!newDataCallOptions) { michael@0: delete this.currentDataCalls[currentDataCall.cid]; michael@0: currentDataCall.state = GECKO_NETWORK_STATE_DISCONNECTED; michael@0: currentDataCall.rilMessageType = "datacallstatechange"; michael@0: this.sendChromeMessage(currentDataCall); michael@0: } michael@0: continue; michael@0: } michael@0: michael@0: if (updatedDataCall && !updatedDataCall.ifname) { michael@0: delete this.currentDataCalls[currentDataCall.cid]; michael@0: currentDataCall.state = GECKO_NETWORK_STATE_UNKNOWN; michael@0: currentDataCall.rilMessageType = "datacallstatechange"; michael@0: this.sendChromeMessage(currentDataCall); michael@0: continue; michael@0: } michael@0: michael@0: this._setDataCallGeckoState(updatedDataCall); michael@0: if (updatedDataCall.state != currentDataCall.state) { michael@0: if (updatedDataCall.state == GECKO_NETWORK_STATE_DISCONNECTED) { michael@0: delete this.currentDataCalls[currentDataCall.cid]; michael@0: } michael@0: currentDataCall.status = updatedDataCall.status; michael@0: currentDataCall.active = updatedDataCall.active; michael@0: currentDataCall.state = updatedDataCall.state; michael@0: currentDataCall.rilMessageType = "datacallstatechange"; michael@0: this.sendChromeMessage(currentDataCall); michael@0: continue; michael@0: } michael@0: michael@0: // State not changed, now check links. michael@0: let result = michael@0: this._compareDataCallLink(updatedDataCall, currentDataCall); michael@0: if (result == "identical") { michael@0: if (DEBUG) this.context.debug("No changes in data call."); michael@0: continue; michael@0: } michael@0: if (result == "deactivate") { michael@0: if (DEBUG) this.context.debug("Data link changed, cleanup."); michael@0: this.deactivateDataCall(currentDataCall); michael@0: continue; michael@0: } michael@0: // Minor change, just update and notify. michael@0: if (DEBUG) { michael@0: this.context.debug("Data link minor change, just update and notify."); michael@0: } michael@0: currentDataCall.addresses = updatedDataCall.addresses.slice(); michael@0: currentDataCall.dnses = updatedDataCall.dnses.slice(); michael@0: currentDataCall.gateways = updatedDataCall.gateways.slice(); michael@0: currentDataCall.rilMessageType = "datacallstatechange"; michael@0: this.sendChromeMessage(currentDataCall); michael@0: } michael@0: michael@0: for each (let newDataCall in datacalls) { michael@0: if (!newDataCall.ifname) { michael@0: continue; michael@0: } michael@0: michael@0: if (!newDataCallOptions) { michael@0: if (DEBUG) { michael@0: this.context.debug("Unexpected new data call: " + michael@0: JSON.stringify(newDataCall)); michael@0: } michael@0: continue; michael@0: } michael@0: michael@0: this.currentDataCalls[newDataCall.cid] = newDataCall; michael@0: this._setDataCallGeckoState(newDataCall); michael@0: michael@0: newDataCall.radioTech = newDataCallOptions.radioTech; michael@0: newDataCall.apn = newDataCallOptions.apn; michael@0: newDataCall.user = newDataCallOptions.user; michael@0: newDataCall.passwd = newDataCallOptions.passwd; michael@0: newDataCall.chappap = newDataCallOptions.chappap; michael@0: newDataCall.pdptype = newDataCallOptions.pdptype; michael@0: newDataCallOptions = null; michael@0: michael@0: newDataCall.rilMessageType = "datacallstatechange"; michael@0: this.sendChromeMessage(newDataCall); michael@0: } michael@0: }, michael@0: michael@0: _setDataCallGeckoState: function(datacall) { michael@0: switch (datacall.active) { michael@0: case DATACALL_INACTIVE: michael@0: datacall.state = GECKO_NETWORK_STATE_DISCONNECTED; michael@0: break; michael@0: case DATACALL_ACTIVE_DOWN: michael@0: case DATACALL_ACTIVE_UP: michael@0: datacall.state = GECKO_NETWORK_STATE_CONNECTED; michael@0: break; michael@0: } michael@0: }, michael@0: michael@0: _processSuppSvcNotification: function(info) { michael@0: if (DEBUG) { michael@0: this.context.debug("handle supp svc notification: " + JSON.stringify(info)); michael@0: } michael@0: michael@0: if (info.notificationType !== 1) { michael@0: // We haven't supported MO intermediate result code, i.e. michael@0: // info.notificationType === 0, which refers to code1 defined in 3GPP michael@0: // 27.007 7.17. We only support partial MT unsolicited result code, michael@0: // referring to code2, for now. michael@0: return; michael@0: } michael@0: michael@0: let notification = null; michael@0: let callIndex = -1; michael@0: michael@0: switch (info.code) { michael@0: case SUPP_SVC_NOTIFICATION_CODE2_PUT_ON_HOLD: michael@0: case SUPP_SVC_NOTIFICATION_CODE2_RETRIEVED: michael@0: notification = GECKO_SUPP_SVC_NOTIFICATION_FROM_CODE2[info.code]; michael@0: break; michael@0: default: michael@0: // Notification type not supported. michael@0: return; michael@0: } michael@0: michael@0: // Get the target call object for this notification. michael@0: let currentCallIndexes = Object.keys(this.currentCalls); michael@0: if (currentCallIndexes.length === 1) { michael@0: // Only one call exists. This should be the target. michael@0: callIndex = currentCallIndexes[0]; michael@0: } else { michael@0: // Find the call in |currentCalls| by the given number. michael@0: if (info.number) { michael@0: for each (let currentCall in this.currentCalls) { michael@0: if (currentCall.number == info.number) { michael@0: callIndex = currentCall.callIndex; michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: let message = {rilMessageType: "suppSvcNotification", michael@0: notification: notification, michael@0: callIndex: callIndex}; michael@0: this.sendChromeMessage(message); michael@0: }, michael@0: michael@0: _cancelEmergencyCbModeTimeout: function() { michael@0: if (this._exitEmergencyCbModeTimeoutID) { michael@0: clearTimeout(this._exitEmergencyCbModeTimeoutID); michael@0: this._exitEmergencyCbModeTimeoutID = null; michael@0: } michael@0: }, michael@0: michael@0: _handleChangedEmergencyCbMode: function(active) { michael@0: this._isInEmergencyCbMode = active; michael@0: michael@0: // Clear the existed timeout event. michael@0: this._cancelEmergencyCbModeTimeout(); michael@0: michael@0: // Start a new timeout event when entering the mode. michael@0: if (active) { michael@0: this._exitEmergencyCbModeTimeoutID = setTimeout( michael@0: this.exitEmergencyCbMode.bind(this), EMERGENCY_CB_MODE_TIMEOUT_MS); michael@0: } michael@0: michael@0: let message = {rilMessageType: "emergencyCbModeChange", michael@0: active: active, michael@0: timeoutMs: EMERGENCY_CB_MODE_TIMEOUT_MS}; michael@0: this.sendChromeMessage(message); michael@0: }, michael@0: michael@0: _processNetworks: function() { michael@0: let strings = this.context.Buf.readStringList(); michael@0: let networks = []; michael@0: michael@0: for (let i = 0; i < strings.length; i += 4) { michael@0: let network = { michael@0: longName: strings[i], michael@0: shortName: strings[i + 1], michael@0: mcc: null, michael@0: mnc: null, michael@0: state: null michael@0: }; michael@0: michael@0: let networkTuple = strings[i + 2]; michael@0: try { michael@0: this._processNetworkTuple(networkTuple, network); michael@0: } catch (e) { michael@0: if (DEBUG) this.context.debug("Error processing operator tuple: " + e); michael@0: } michael@0: michael@0: let state = strings[i + 3]; michael@0: if (state === NETWORK_STATE_UNKNOWN) { michael@0: // TODO: looks like this might conflict in style with michael@0: // GECKO_NETWORK_STYLE_UNKNOWN / nsINetworkManager michael@0: state = GECKO_QAN_STATE_UNKNOWN; michael@0: } michael@0: michael@0: network.state = state; michael@0: networks.push(network); michael@0: } michael@0: return networks; michael@0: }, michael@0: michael@0: /** michael@0: * The "numeric" portion of the operator info is a tuple michael@0: * containing MCC (country code) and MNC (network code). michael@0: * AFAICT, MCC should always be 3 digits, making the remaining michael@0: * portion the MNC. michael@0: */ michael@0: _processNetworkTuple: function(networkTuple, network) { michael@0: let tupleLen = networkTuple.length; michael@0: michael@0: if (tupleLen == 5 || tupleLen == 6) { michael@0: network.mcc = networkTuple.substr(0, 3); michael@0: network.mnc = networkTuple.substr(3); michael@0: } else { michael@0: network.mcc = null; michael@0: network.mnc = null; michael@0: michael@0: throw new Error("Invalid network tuple (should be 5 or 6 digits): " + networkTuple); michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Check if GSM radio access technology group. michael@0: */ michael@0: _isGsmTechGroup: function(radioTech) { michael@0: if (!radioTech) { michael@0: return true; michael@0: } michael@0: michael@0: switch(radioTech) { michael@0: case NETWORK_CREG_TECH_GPRS: michael@0: case NETWORK_CREG_TECH_EDGE: michael@0: case NETWORK_CREG_TECH_UMTS: michael@0: case NETWORK_CREG_TECH_HSDPA: michael@0: case NETWORK_CREG_TECH_HSUPA: michael@0: case NETWORK_CREG_TECH_HSPA: michael@0: case NETWORK_CREG_TECH_LTE: michael@0: case NETWORK_CREG_TECH_HSPAP: michael@0: case NETWORK_CREG_TECH_GSM: michael@0: return true; michael@0: } michael@0: michael@0: return false; michael@0: }, michael@0: michael@0: /** michael@0: * Process radio technology change. michael@0: */ michael@0: _processRadioTech: function(radioTech) { michael@0: let isCdma = !this._isGsmTechGroup(radioTech); michael@0: this.radioTech = radioTech; michael@0: michael@0: if (DEBUG) { michael@0: this.context.debug("Radio tech is set to: " + GECKO_RADIO_TECH[radioTech] + michael@0: ", it is a " + (isCdma?"cdma":"gsm") + " technology"); michael@0: } michael@0: michael@0: // We should request SIM information when michael@0: // 1. Radio state has been changed, so we are waiting for radioTech or michael@0: // 2. isCdma is different from this._isCdma. michael@0: if (this._waitingRadioTech || isCdma != this._isCdma) { michael@0: this._isCdma = isCdma; michael@0: this._waitingRadioTech = false; michael@0: if (this._isCdma) { michael@0: this.getDeviceIdentity(); michael@0: } else { michael@0: this.getIMEI(); michael@0: this.getIMEISV(); michael@0: } michael@0: this.getICCStatus(); michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Helper for returning the TOA for the given dial string. michael@0: */ michael@0: _toaFromString: function(number) { michael@0: let toa = TOA_UNKNOWN; michael@0: if (number && number.length > 0 && number[0] == '+') { michael@0: toa = TOA_INTERNATIONAL; michael@0: } michael@0: return toa; michael@0: }, michael@0: michael@0: /** michael@0: * Helper for translating basic service group to call forwarding service class michael@0: * parameter. michael@0: */ michael@0: _siToServiceClass: function(si) { michael@0: if (!si) { michael@0: return ICC_SERVICE_CLASS_NONE; michael@0: } michael@0: michael@0: let serviceCode = parseInt(si, 10); michael@0: switch (serviceCode) { michael@0: case 10: michael@0: return ICC_SERVICE_CLASS_SMS + ICC_SERVICE_CLASS_FAX + ICC_SERVICE_CLASS_VOICE; michael@0: case 11: michael@0: return ICC_SERVICE_CLASS_VOICE; michael@0: case 12: michael@0: return ICC_SERVICE_CLASS_SMS + ICC_SERVICE_CLASS_FAX; michael@0: case 13: michael@0: return ICC_SERVICE_CLASS_FAX; michael@0: case 16: michael@0: return ICC_SERVICE_CLASS_SMS; michael@0: case 19: michael@0: return ICC_SERVICE_CLASS_FAX + ICC_SERVICE_CLASS_VOICE; michael@0: case 21: michael@0: return ICC_SERVICE_CLASS_PAD + ICC_SERVICE_CLASS_DATA_ASYNC; michael@0: case 22: michael@0: return ICC_SERVICE_CLASS_PACKET + ICC_SERVICE_CLASS_DATA_SYNC; michael@0: case 25: michael@0: return ICC_SERVICE_CLASS_DATA_ASYNC; michael@0: case 26: michael@0: return ICC_SERVICE_CLASS_DATA_SYNC + SERVICE_CLASS_VOICE; michael@0: case 99: michael@0: return ICC_SERVICE_CLASS_PACKET; michael@0: default: michael@0: return ICC_SERVICE_CLASS_NONE; michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * @param message A decoded SMS-DELIVER message. michael@0: * michael@0: * @see 3GPP TS 31.111 section 7.1.1 michael@0: */ michael@0: dataDownloadViaSMSPP: function(message) { michael@0: let Buf = this.context.Buf; michael@0: let GsmPDUHelper = this.context.GsmPDUHelper; michael@0: michael@0: let options = { michael@0: pid: message.pid, michael@0: dcs: message.dcs, michael@0: encoding: message.encoding, michael@0: }; michael@0: Buf.newParcel(REQUEST_STK_SEND_ENVELOPE_WITH_STATUS, options); michael@0: michael@0: Buf.seekIncoming(-1 * (Buf.getCurrentParcelSize() - Buf.getReadAvailable() michael@0: - 2 * Buf.UINT32_SIZE)); // Skip response_type & request_type. michael@0: let messageStringLength = Buf.readInt32(); // In semi-octets michael@0: let smscLength = GsmPDUHelper.readHexOctet(); // In octets, inclusive of TOA michael@0: let tpduLength = (messageStringLength / 2) - (smscLength + 1); // In octets michael@0: michael@0: // Device identities: 4 bytes michael@0: // Address: 0 or (2 + smscLength) michael@0: // SMS TPDU: (2 or 3) + tpduLength michael@0: let berLen = 4 + michael@0: (smscLength ? (2 + smscLength) : 0) + michael@0: (tpduLength <= 127 ? 2 : 3) + tpduLength; // In octets michael@0: michael@0: let parcelLength = (berLen <= 127 ? 2 : 3) + berLen; // In octets michael@0: Buf.writeInt32(parcelLength * 2); // In semi-octets michael@0: michael@0: // Write a BER-TLV michael@0: GsmPDUHelper.writeHexOctet(BER_SMS_PP_DOWNLOAD_TAG); michael@0: if (berLen > 127) { michael@0: GsmPDUHelper.writeHexOctet(0x81); michael@0: } michael@0: GsmPDUHelper.writeHexOctet(berLen); michael@0: michael@0: // Device Identifies-TLV michael@0: GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_DEVICE_ID | michael@0: COMPREHENSIONTLV_FLAG_CR); michael@0: GsmPDUHelper.writeHexOctet(0x02); michael@0: GsmPDUHelper.writeHexOctet(STK_DEVICE_ID_NETWORK); michael@0: GsmPDUHelper.writeHexOctet(STK_DEVICE_ID_SIM); michael@0: michael@0: // Address-TLV michael@0: if (smscLength) { michael@0: GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_ADDRESS); michael@0: GsmPDUHelper.writeHexOctet(smscLength); michael@0: Buf.copyIncomingToOutgoing(Buf.PDU_HEX_OCTET_SIZE * smscLength); michael@0: } michael@0: michael@0: // SMS TPDU-TLV michael@0: GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_SMS_TPDU | michael@0: COMPREHENSIONTLV_FLAG_CR); michael@0: if (tpduLength > 127) { michael@0: GsmPDUHelper.writeHexOctet(0x81); michael@0: } michael@0: GsmPDUHelper.writeHexOctet(tpduLength); michael@0: Buf.copyIncomingToOutgoing(Buf.PDU_HEX_OCTET_SIZE * tpduLength); michael@0: michael@0: // Write 2 string delimitors for the total string length must be even. michael@0: Buf.writeStringDelimiter(0); michael@0: michael@0: Buf.sendParcel(); michael@0: }, michael@0: michael@0: /** michael@0: * @param success A boolean value indicating the result of previous michael@0: * SMS-DELIVER message handling. michael@0: * @param responsePduLen ICC IO response PDU length in octets. michael@0: * @param options An object that contains four attributes: `pid`, `dcs`, michael@0: * `encoding` and `responsePduLen`. michael@0: * michael@0: * @see 3GPP TS 23.040 section 9.2.2.1a michael@0: */ michael@0: acknowledgeIncomingGsmSmsWithPDU: function(success, responsePduLen, options) { michael@0: let Buf = this.context.Buf; michael@0: let GsmPDUHelper = this.context.GsmPDUHelper; michael@0: michael@0: Buf.newParcel(REQUEST_ACKNOWLEDGE_INCOMING_GSM_SMS_WITH_PDU); michael@0: michael@0: // Two strings. michael@0: Buf.writeInt32(2); michael@0: michael@0: // String 1: Success michael@0: Buf.writeString(success ? "1" : "0"); michael@0: michael@0: // String 2: RP-ACK/RP-ERROR PDU michael@0: Buf.writeInt32(2 * (responsePduLen + (success ? 5 : 6))); // In semi-octet michael@0: // 1. TP-MTI & TP-UDHI michael@0: GsmPDUHelper.writeHexOctet(PDU_MTI_SMS_DELIVER); michael@0: if (!success) { michael@0: // 2. TP-FCS michael@0: GsmPDUHelper.writeHexOctet(PDU_FCS_USIM_DATA_DOWNLOAD_ERROR); michael@0: } michael@0: // 3. TP-PI michael@0: GsmPDUHelper.writeHexOctet(PDU_PI_USER_DATA_LENGTH | michael@0: PDU_PI_DATA_CODING_SCHEME | michael@0: PDU_PI_PROTOCOL_IDENTIFIER); michael@0: // 4. TP-PID michael@0: GsmPDUHelper.writeHexOctet(options.pid); michael@0: // 5. TP-DCS michael@0: GsmPDUHelper.writeHexOctet(options.dcs); michael@0: // 6. TP-UDL michael@0: if (options.encoding == PDU_DCS_MSG_CODING_7BITS_ALPHABET) { michael@0: GsmPDUHelper.writeHexOctet(Math.floor(responsePduLen * 8 / 7)); michael@0: } else { michael@0: GsmPDUHelper.writeHexOctet(responsePduLen); michael@0: } michael@0: // TP-UD michael@0: Buf.copyIncomingToOutgoing(Buf.PDU_HEX_OCTET_SIZE * responsePduLen); michael@0: // Write 2 string delimitors for the total string length must be even. michael@0: Buf.writeStringDelimiter(0); michael@0: michael@0: Buf.sendParcel(); michael@0: }, michael@0: michael@0: /** michael@0: * @param message A decoded SMS-DELIVER message. michael@0: */ michael@0: writeSmsToSIM: function(message) { michael@0: let Buf = this.context.Buf; michael@0: let GsmPDUHelper = this.context.GsmPDUHelper; michael@0: michael@0: Buf.newParcel(REQUEST_WRITE_SMS_TO_SIM); michael@0: michael@0: // Write EFsms Status michael@0: Buf.writeInt32(EFSMS_STATUS_FREE); michael@0: michael@0: Buf.seekIncoming(-1 * (Buf.getCurrentParcelSize() - Buf.getReadAvailable() michael@0: - 2 * Buf.UINT32_SIZE)); // Skip response_type & request_type. michael@0: let messageStringLength = Buf.readInt32(); // In semi-octets michael@0: let smscLength = GsmPDUHelper.readHexOctet(); // In octets, inclusive of TOA michael@0: let pduLength = (messageStringLength / 2) - (smscLength + 1); // In octets michael@0: michael@0: // 1. Write PDU first. michael@0: if (smscLength > 0) { michael@0: Buf.seekIncoming(smscLength * Buf.PDU_HEX_OCTET_SIZE); michael@0: } michael@0: // Write EFsms PDU string length michael@0: Buf.writeInt32(2 * pduLength); // In semi-octets michael@0: if (pduLength) { michael@0: Buf.copyIncomingToOutgoing(Buf.PDU_HEX_OCTET_SIZE * pduLength); michael@0: } michael@0: // Write 2 string delimitors for the total string length must be even. michael@0: Buf.writeStringDelimiter(0); michael@0: michael@0: // 2. Write SMSC michael@0: // Write EFsms SMSC string length michael@0: Buf.writeInt32(2 * (smscLength + 1)); // Plus smscLength itself, in semi-octets michael@0: // Write smscLength michael@0: GsmPDUHelper.writeHexOctet(smscLength); michael@0: // Write TOA & SMSC Address michael@0: if (smscLength) { michael@0: Buf.seekIncoming(-1 * (Buf.getCurrentParcelSize() - Buf.getReadAvailable() michael@0: - 2 * Buf.UINT32_SIZE // Skip response_type, request_type. michael@0: - 2 * Buf.PDU_HEX_OCTET_SIZE)); // Skip messageStringLength & smscLength. michael@0: Buf.copyIncomingToOutgoing(Buf.PDU_HEX_OCTET_SIZE * smscLength); michael@0: } michael@0: // Write 2 string delimitors for the total string length must be even. michael@0: Buf.writeStringDelimiter(0); michael@0: michael@0: Buf.sendParcel(); michael@0: }, michael@0: michael@0: /** michael@0: * Helper to delegate the received sms segment to RadioInterface to process. michael@0: * michael@0: * @param message michael@0: * Received sms message. michael@0: * michael@0: * @return MOZ_FCS_WAIT_FOR_EXPLICIT_ACK michael@0: */ michael@0: _processSmsMultipart: function(message) { michael@0: message.rilMessageType = "sms-received"; michael@0: michael@0: this.sendChromeMessage(message); michael@0: michael@0: return MOZ_FCS_WAIT_FOR_EXPLICIT_ACK; michael@0: }, michael@0: michael@0: /** michael@0: * Helper for processing SMS-STATUS-REPORT PDUs. michael@0: * michael@0: * @param length michael@0: * Length of SMS string in the incoming parcel. michael@0: * michael@0: * @return A failure cause defined in 3GPP 23.040 clause 9.2.3.22. michael@0: */ michael@0: _processSmsStatusReport: function(length) { michael@0: let [message, result] = this.context.GsmPDUHelper.processReceivedSms(length); michael@0: if (!message) { michael@0: if (DEBUG) this.context.debug("invalid SMS-STATUS-REPORT"); michael@0: return PDU_FCS_UNSPECIFIED; michael@0: } michael@0: michael@0: let options = this._pendingSentSmsMap[message.messageRef]; michael@0: if (!options) { michael@0: if (DEBUG) this.context.debug("no pending SMS-SUBMIT message"); michael@0: return PDU_FCS_OK; michael@0: } michael@0: michael@0: let status = message.status; michael@0: michael@0: // 3GPP TS 23.040 9.2.3.15 `The MS shall interpret any reserved values as michael@0: // "Service Rejected"(01100011) but shall store them exactly as received.` michael@0: if ((status >= 0x80) michael@0: || ((status >= PDU_ST_0_RESERVED_BEGIN) michael@0: && (status < PDU_ST_0_SC_SPECIFIC_BEGIN)) michael@0: || ((status >= PDU_ST_1_RESERVED_BEGIN) michael@0: && (status < PDU_ST_1_SC_SPECIFIC_BEGIN)) michael@0: || ((status >= PDU_ST_2_RESERVED_BEGIN) michael@0: && (status < PDU_ST_2_SC_SPECIFIC_BEGIN)) michael@0: || ((status >= PDU_ST_3_RESERVED_BEGIN) michael@0: && (status < PDU_ST_3_SC_SPECIFIC_BEGIN)) michael@0: ) { michael@0: status = PDU_ST_3_SERVICE_REJECTED; michael@0: } michael@0: michael@0: // Pending. Waiting for next status report. michael@0: if ((status >>> 5) == 0x01) { michael@0: if (DEBUG) this.context.debug("SMS-STATUS-REPORT: delivery still pending"); michael@0: return PDU_FCS_OK; michael@0: } michael@0: michael@0: delete this._pendingSentSmsMap[message.messageRef]; michael@0: michael@0: let deliveryStatus = ((status >>> 5) === 0x00) michael@0: ? GECKO_SMS_DELIVERY_STATUS_SUCCESS michael@0: : GECKO_SMS_DELIVERY_STATUS_ERROR; michael@0: this.sendChromeMessage({ michael@0: rilMessageType: options.rilMessageType, michael@0: rilMessageToken: options.rilMessageToken, michael@0: deliveryStatus: deliveryStatus michael@0: }); michael@0: michael@0: return PDU_FCS_OK; michael@0: }, michael@0: michael@0: /** michael@0: * Helper for processing CDMA SMS Delivery Acknowledgment Message michael@0: * michael@0: * @param message michael@0: * decoded SMS Delivery ACK message from CdmaPDUHelper. michael@0: * michael@0: * @return A failure cause defined in 3GPP 23.040 clause 9.2.3.22. michael@0: */ michael@0: _processCdmaSmsStatusReport: function(message) { michael@0: let options = this._pendingSentSmsMap[message.msgId]; michael@0: if (!options) { michael@0: if (DEBUG) this.context.debug("no pending SMS-SUBMIT message"); michael@0: return PDU_FCS_OK; michael@0: } michael@0: michael@0: if (message.errorClass === 2) { michael@0: if (DEBUG) { michael@0: this.context.debug("SMS-STATUS-REPORT: delivery still pending, " + michael@0: "msgStatus: " + message.msgStatus); michael@0: } michael@0: return PDU_FCS_OK; michael@0: } michael@0: michael@0: delete this._pendingSentSmsMap[message.msgId]; michael@0: michael@0: if (message.errorClass === -1 && message.body) { michael@0: // Process as normal incoming SMS, if errorClass is invalid michael@0: // but message body is available. michael@0: return this._processSmsMultipart(message); michael@0: } michael@0: michael@0: let deliveryStatus = (message.errorClass === 0) michael@0: ? GECKO_SMS_DELIVERY_STATUS_SUCCESS michael@0: : GECKO_SMS_DELIVERY_STATUS_ERROR; michael@0: this.sendChromeMessage({ michael@0: rilMessageType: options.rilMessageType, michael@0: rilMessageToken: options.rilMessageToken, michael@0: deliveryStatus: deliveryStatus michael@0: }); michael@0: michael@0: return PDU_FCS_OK; michael@0: }, michael@0: michael@0: /** michael@0: * Helper for processing CDMA SMS WAP Push Message michael@0: * michael@0: * @param message michael@0: * decoded WAP message from CdmaPDUHelper. michael@0: * michael@0: * @return A failure cause defined in 3GPP 23.040 clause 9.2.3.22. michael@0: */ michael@0: _processCdmaSmsWapPush: function(message) { michael@0: if (!message.data) { michael@0: if (DEBUG) this.context.debug("no data inside WAP Push message."); michael@0: return PDU_FCS_OK; michael@0: } michael@0: michael@0: // See 6.5. MAPPING OF WDP TO CDMA SMS in WAP-295-WDP. michael@0: // michael@0: // Field | Length (bits) michael@0: // ----------------------------------------- michael@0: // MSG_TYPE | 8 michael@0: // TOTAL_SEGMENTS | 8 michael@0: // SEGMENT_NUMBER | 8 michael@0: // DATAGRAM | (NUM_FIELDS – 3) * 8 michael@0: let index = 0; michael@0: if (message.data[index++] !== 0) { michael@0: if (DEBUG) this.context.debug("Ignore a WAP Message which is not WDP."); michael@0: return PDU_FCS_OK; michael@0: } michael@0: michael@0: // 1. Originator Address in SMS-TL + Message_Id in SMS-TS are used to identify a unique WDP datagram. michael@0: // 2. TOTAL_SEGMENTS, SEGMENT_NUMBER are used to verify that a complete michael@0: // datagram has been received and is ready to be passed to a higher layer. michael@0: message.header = { michael@0: segmentRef: message.msgId, michael@0: segmentMaxSeq: message.data[index++], michael@0: segmentSeq: message.data[index++] + 1 // It's zero-based in CDMA WAP Push. michael@0: }; michael@0: michael@0: if (message.header.segmentSeq > message.header.segmentMaxSeq) { michael@0: if (DEBUG) this.context.debug("Wrong WDP segment info."); michael@0: return PDU_FCS_OK; michael@0: } michael@0: michael@0: // Ports are only specified in 1st segment. michael@0: if (message.header.segmentSeq == 1) { michael@0: message.header.originatorPort = message.data[index++] << 8; michael@0: message.header.originatorPort |= message.data[index++]; michael@0: message.header.destinationPort = message.data[index++] << 8; michael@0: message.header.destinationPort |= message.data[index++]; michael@0: } michael@0: michael@0: message.data = message.data.subarray(index); michael@0: michael@0: return this._processSmsMultipart(message); michael@0: }, michael@0: michael@0: /** michael@0: * Helper for processing sent multipart SMS. michael@0: */ michael@0: _processSentSmsSegment: function(options) { michael@0: // Setup attributes for sending next segment michael@0: let next = options.segmentSeq; michael@0: options.body = options.segments[next].body; michael@0: options.encodedBodyLength = options.segments[next].encodedBodyLength; michael@0: options.segmentSeq = next + 1; michael@0: michael@0: this.sendSMS(options); michael@0: }, michael@0: michael@0: /** michael@0: * Helper for processing result of send SMS. michael@0: * michael@0: * @param length michael@0: * Length of SMS string in the incoming parcel. michael@0: * @param options michael@0: * Sms information. michael@0: */ michael@0: _processSmsSendResult: function(length, options) { michael@0: if (options.rilRequestError) { michael@0: if (DEBUG) { michael@0: this.context.debug("_processSmsSendResult: rilRequestError = " + michael@0: options.rilRequestError); michael@0: } michael@0: switch (options.rilRequestError) { michael@0: case ERROR_SMS_SEND_FAIL_RETRY: michael@0: if (options.retryCount < SMS_RETRY_MAX) { michael@0: options.retryCount++; michael@0: // TODO: bug 736702 TP-MR, retry interval, retry timeout michael@0: this.sendSMS(options); michael@0: break; michael@0: } michael@0: // Fallback to default error handling if it meets max retry count. michael@0: // Fall through. michael@0: default: michael@0: this.sendChromeMessage({ michael@0: rilMessageType: options.rilMessageType, michael@0: rilMessageToken: options.rilMessageToken, michael@0: errorMsg: options.rilRequestError, michael@0: }); michael@0: break; michael@0: } michael@0: return; michael@0: } michael@0: michael@0: let Buf = this.context.Buf; michael@0: options.messageRef = Buf.readInt32(); michael@0: options.ackPDU = Buf.readString(); michael@0: options.errorCode = Buf.readInt32(); michael@0: michael@0: if ((options.segmentMaxSeq > 1) michael@0: && (options.segmentSeq < options.segmentMaxSeq)) { michael@0: // Not last segment michael@0: this._processSentSmsSegment(options); michael@0: } else { michael@0: // Last segment sent with success. michael@0: if (options.requestStatusReport) { michael@0: if (DEBUG) { michael@0: this.context.debug("waiting SMS-STATUS-REPORT for messageRef " + michael@0: options.messageRef); michael@0: } michael@0: this._pendingSentSmsMap[options.messageRef] = options; michael@0: } michael@0: michael@0: this.sendChromeMessage({ michael@0: rilMessageType: options.rilMessageType, michael@0: rilMessageToken: options.rilMessageToken, michael@0: }); michael@0: } michael@0: }, michael@0: michael@0: _processReceivedSmsCbPage: function(original) { michael@0: if (original.numPages <= 1) { michael@0: if (original.body) { michael@0: original.fullBody = original.body; michael@0: delete original.body; michael@0: } else if (original.data) { michael@0: original.fullData = original.data; michael@0: delete original.data; michael@0: } michael@0: return original; michael@0: } michael@0: michael@0: // Hash = :::: michael@0: let hash = original.serial + ":" + this.iccInfo.mcc + ":" michael@0: + this.iccInfo.mnc + ":"; michael@0: switch (original.geographicalScope) { michael@0: case CB_GSM_GEOGRAPHICAL_SCOPE_CELL_WIDE_IMMEDIATE: michael@0: case CB_GSM_GEOGRAPHICAL_SCOPE_CELL_WIDE: michael@0: hash += this.voiceRegistrationState.cell.gsmLocationAreaCode + ":" michael@0: + this.voiceRegistrationState.cell.gsmCellId; michael@0: break; michael@0: case CB_GSM_GEOGRAPHICAL_SCOPE_LOCATION_AREA_WIDE: michael@0: hash += this.voiceRegistrationState.cell.gsmLocationAreaCode + ":"; michael@0: break; michael@0: default: michael@0: hash += ":"; michael@0: break; michael@0: } michael@0: michael@0: let index = original.pageIndex; michael@0: michael@0: let options = this._receivedSmsCbPagesMap[hash]; michael@0: if (!options) { michael@0: options = original; michael@0: this._receivedSmsCbPagesMap[hash] = options; michael@0: michael@0: options.receivedPages = 0; michael@0: options.pages = []; michael@0: } else if (options.pages[index]) { michael@0: // Duplicated page? michael@0: if (DEBUG) { michael@0: this.context.debug("Got duplicated page no." + index + michael@0: " of a multipage SMSCB: " + JSON.stringify(original)); michael@0: } michael@0: return null; michael@0: } michael@0: michael@0: if (options.encoding == PDU_DCS_MSG_CODING_8BITS_ALPHABET) { michael@0: options.pages[index] = original.data; michael@0: delete original.data; michael@0: } else { michael@0: options.pages[index] = original.body; michael@0: delete original.body; michael@0: } michael@0: options.receivedPages++; michael@0: if (options.receivedPages < options.numPages) { michael@0: if (DEBUG) { michael@0: this.context.debug("Got page no." + index + " of a multipage SMSCB: " + michael@0: JSON.stringify(options)); michael@0: } michael@0: return null; michael@0: } michael@0: michael@0: // Remove from map michael@0: delete this._receivedSmsCbPagesMap[hash]; michael@0: michael@0: // Rebuild full body michael@0: if (options.encoding == PDU_DCS_MSG_CODING_8BITS_ALPHABET) { michael@0: // Uint8Array doesn't have `concat`, so we have to merge all pages by hand. michael@0: let fullDataLen = 0; michael@0: for (let i = 1; i <= options.numPages; i++) { michael@0: fullDataLen += options.pages[i].length; michael@0: } michael@0: michael@0: options.fullData = new Uint8Array(fullDataLen); michael@0: for (let d= 0, i = 1; i <= options.numPages; i++) { michael@0: let data = options.pages[i]; michael@0: for (let j = 0; j < data.length; j++) { michael@0: options.fullData[d++] = data[j]; michael@0: } michael@0: } michael@0: } else { michael@0: options.fullBody = options.pages.join(""); michael@0: } michael@0: michael@0: if (DEBUG) { michael@0: this.context.debug("Got full multipage SMSCB: " + JSON.stringify(options)); michael@0: } michael@0: michael@0: return options; michael@0: }, michael@0: michael@0: _mergeCellBroadcastConfigs: function(list, from, to) { michael@0: if (!list) { michael@0: return [from, to]; michael@0: } michael@0: michael@0: for (let i = 0, f1, t1; i < list.length;) { michael@0: f1 = list[i++]; michael@0: t1 = list[i++]; michael@0: if (to == f1) { michael@0: // ...[from]...[to|f1]...(t1) michael@0: list[i - 2] = from; michael@0: return list; michael@0: } michael@0: michael@0: if (to < f1) { michael@0: // ...[from]...(to)...[f1] or ...[from]...(to)[f1] michael@0: if (i > 2) { michael@0: // Not the first range pair, merge three arrays. michael@0: return list.slice(0, i - 2).concat([from, to]).concat(list.slice(i - 2)); michael@0: } else { michael@0: return [from, to].concat(list); michael@0: } michael@0: } michael@0: michael@0: if (from > t1) { michael@0: // ...[f1]...(t1)[from] or ...[f1]...(t1)...[from] michael@0: continue; michael@0: } michael@0: michael@0: // Have overlap or merge-able adjacency with [f1]...(t1). Replace it michael@0: // with [min(from, f1)]...(max(to, t1)). michael@0: michael@0: let changed = false; michael@0: if (from < f1) { michael@0: // [from]...[f1]...(t1) or [from][f1]...(t1) michael@0: // Save minimum from value. michael@0: list[i - 2] = from; michael@0: changed = true; michael@0: } michael@0: michael@0: if (to <= t1) { michael@0: // [from]...[to](t1) or [from]...(to|t1) michael@0: // Can't have further merge-able adjacency. Return. michael@0: return list; michael@0: } michael@0: michael@0: // Try merging possible next adjacent range. michael@0: let j = i; michael@0: for (let f2, t2; j < list.length;) { michael@0: f2 = list[j++]; michael@0: t2 = list[j++]; michael@0: if (to > t2) { michael@0: // [from]...[f2]...[t2]...(to) or [from]...[f2]...[t2](to) michael@0: // Merge next adjacent range again. michael@0: continue; michael@0: } michael@0: michael@0: if (to < t2) { michael@0: if (to < f2) { michael@0: // [from]...(to)[f2] or [from]...(to)...[f2] michael@0: // Roll back and give up. michael@0: j -= 2; michael@0: } else if (to < t2) { michael@0: // [from]...[to|f2]...(t2), or [from]...[f2]...[to](t2) michael@0: // Merge to [from]...(t2) and give up. michael@0: to = t2; michael@0: } michael@0: } michael@0: michael@0: break; michael@0: } michael@0: michael@0: // Save maximum to value. michael@0: list[i - 1] = to; michael@0: michael@0: if (j != i) { michael@0: // Remove merged adjacent ranges. michael@0: let ret = list.slice(0, i); michael@0: if (j < list.length) { michael@0: ret = ret.concat(list.slice(j)); michael@0: } michael@0: return ret; michael@0: } michael@0: michael@0: return list; michael@0: } michael@0: michael@0: // Append to the end. michael@0: list.push(from); michael@0: list.push(to); michael@0: michael@0: return list; michael@0: }, michael@0: michael@0: _isCellBroadcastConfigReady: function() { michael@0: if (!("MMI" in this.cellBroadcastConfigs)) { michael@0: return false; michael@0: } michael@0: michael@0: // CBMI should be ready in GSM. michael@0: if (!this._isCdma && michael@0: (!("CBMI" in this.cellBroadcastConfigs) || michael@0: !("CBMID" in this.cellBroadcastConfigs) || michael@0: !("CBMIR" in this.cellBroadcastConfigs))) { michael@0: return false; michael@0: } michael@0: michael@0: return true; michael@0: }, michael@0: michael@0: /** michael@0: * Merge all members of cellBroadcastConfigs into mergedCellBroadcastConfig. michael@0: */ michael@0: _mergeAllCellBroadcastConfigs: function() { michael@0: if (!this._isCellBroadcastConfigReady()) { michael@0: if (DEBUG) { michael@0: this.context.debug("cell broadcast configs not ready, waiting ..."); michael@0: } michael@0: return; michael@0: } michael@0: michael@0: // Prepare cell broadcast config. CBMI* are only used in GSM. michael@0: let usedCellBroadcastConfigs = {MMI: this.cellBroadcastConfigs.MMI}; michael@0: if (!this._isCdma) { michael@0: usedCellBroadcastConfigs.CBMI = this.cellBroadcastConfigs.CBMI; michael@0: usedCellBroadcastConfigs.CBMID = this.cellBroadcastConfigs.CBMID; michael@0: usedCellBroadcastConfigs.CBMIR = this.cellBroadcastConfigs.CBMIR; michael@0: } michael@0: michael@0: if (DEBUG) { michael@0: this.context.debug("Cell Broadcast search lists: " + michael@0: JSON.stringify(usedCellBroadcastConfigs)); michael@0: } michael@0: michael@0: let list = null; michael@0: for each (let ll in usedCellBroadcastConfigs) { michael@0: if (ll == null) { michael@0: continue; michael@0: } michael@0: michael@0: for (let i = 0; i < ll.length; i += 2) { michael@0: list = this._mergeCellBroadcastConfigs(list, ll[i], ll[i + 1]); michael@0: } michael@0: } michael@0: michael@0: if (DEBUG) { michael@0: this.context.debug("Cell Broadcast search lists(merged): " + michael@0: JSON.stringify(list)); michael@0: } michael@0: this.mergedCellBroadcastConfig = list; michael@0: this.updateCellBroadcastConfig(); michael@0: }, michael@0: michael@0: /** michael@0: * Check whether search list from settings is settable by MMI, that is, michael@0: * whether the range is bounded in any entries of CB_NON_MMI_SETTABLE_RANGES. michael@0: */ michael@0: _checkCellBroadcastMMISettable: function(from, to) { michael@0: if ((to <= from) || (from >= 65536) || (from < 0)) { michael@0: return false; michael@0: } michael@0: michael@0: if (!this._isCdma) { michael@0: // GSM not settable ranges. michael@0: for (let i = 0, f, t; i < CB_NON_MMI_SETTABLE_RANGES.length;) { michael@0: f = CB_NON_MMI_SETTABLE_RANGES[i++]; michael@0: t = CB_NON_MMI_SETTABLE_RANGES[i++]; michael@0: if ((from < t) && (to > f)) { michael@0: // Have overlap. michael@0: return false; michael@0: } michael@0: } michael@0: } michael@0: michael@0: return true; michael@0: }, michael@0: michael@0: /** michael@0: * Convert Cell Broadcast settings string into search list. michael@0: */ michael@0: _convertCellBroadcastSearchList: function(searchListStr) { michael@0: let parts = searchListStr && searchListStr.split(","); michael@0: if (!parts) { michael@0: return null; michael@0: } michael@0: michael@0: let list = null; michael@0: let result, from, to; michael@0: for (let range of parts) { michael@0: // Match "12" or "12-34". The result will be ["12", "12", null] or michael@0: // ["12-34", "12", "34"]. michael@0: result = range.match(/^(\d+)(?:-(\d+))?$/); michael@0: if (!result) { michael@0: throw "Invalid format"; michael@0: } michael@0: michael@0: from = parseInt(result[1], 10); michael@0: to = (result[2]) ? parseInt(result[2], 10) + 1 : from + 1; michael@0: if (!this._checkCellBroadcastMMISettable(from, to)) { michael@0: throw "Invalid range"; michael@0: } michael@0: michael@0: if (list == null) { michael@0: list = []; michael@0: } michael@0: list.push(from); michael@0: list.push(to); michael@0: } michael@0: michael@0: return list; michael@0: }, michael@0: michael@0: /** michael@0: * Handle incoming messages from the main UI thread. michael@0: * michael@0: * @param message michael@0: * Object containing the message. Messages are supposed michael@0: */ michael@0: handleChromeMessage: function(message) { michael@0: if (DEBUG) { michael@0: this.context.debug("Received chrome message " + JSON.stringify(message)); michael@0: } michael@0: let method = this[message.rilMessageType]; michael@0: if (typeof method != "function") { michael@0: if (DEBUG) { michael@0: this.context.debug("Don't know what to do with message " + michael@0: JSON.stringify(message)); michael@0: } michael@0: return; michael@0: } michael@0: method.call(this, message); michael@0: }, michael@0: michael@0: /** michael@0: * Get a list of current voice calls. michael@0: */ michael@0: enumerateCalls: function(options) { michael@0: if (DEBUG) this.context.debug("Sending all current calls"); michael@0: let calls = []; michael@0: for each (let call in this.currentCalls) { michael@0: calls.push(call); michael@0: } michael@0: options.calls = calls; michael@0: this.sendChromeMessage(options); michael@0: }, michael@0: michael@0: /** michael@0: * Process STK Proactive Command. michael@0: */ michael@0: processStkProactiveCommand: function() { michael@0: let Buf = this.context.Buf; michael@0: let length = Buf.readInt32(); michael@0: let berTlv; michael@0: try { michael@0: berTlv = this.context.BerTlvHelper.decode(length / 2); michael@0: } catch (e) { michael@0: if (DEBUG) this.context.debug("processStkProactiveCommand : " + e); michael@0: this.sendStkTerminalResponse({ michael@0: resultCode: STK_RESULT_CMD_DATA_NOT_UNDERSTOOD}); michael@0: return; michael@0: } michael@0: michael@0: Buf.readStringDelimiter(length); michael@0: michael@0: let ctlvs = berTlv.value; michael@0: let ctlv = this.context.StkProactiveCmdHelper.searchForTag( michael@0: COMPREHENSIONTLV_TAG_COMMAND_DETAILS, ctlvs); michael@0: if (!ctlv) { michael@0: this.sendStkTerminalResponse({ michael@0: resultCode: STK_RESULT_CMD_DATA_NOT_UNDERSTOOD}); michael@0: throw new Error("Can't find COMMAND_DETAILS ComprehensionTlv"); michael@0: } michael@0: michael@0: let cmdDetails = ctlv.value; michael@0: if (DEBUG) { michael@0: this.context.debug("commandNumber = " + cmdDetails.commandNumber + michael@0: " typeOfCommand = " + cmdDetails.typeOfCommand.toString(16) + michael@0: " commandQualifier = " + cmdDetails.commandQualifier); michael@0: } michael@0: michael@0: // STK_CMD_MORE_TIME need not to propagate event to chrome. michael@0: if (cmdDetails.typeOfCommand == STK_CMD_MORE_TIME) { michael@0: this.sendStkTerminalResponse({ michael@0: command: cmdDetails, michael@0: resultCode: STK_RESULT_OK}); michael@0: return; michael@0: } michael@0: michael@0: cmdDetails.rilMessageType = "stkcommand"; michael@0: cmdDetails.options = michael@0: this.context.StkCommandParamsFactory.createParam(cmdDetails, ctlvs); michael@0: this.sendChromeMessage(cmdDetails); michael@0: }, michael@0: michael@0: /** michael@0: * Send messages to the main thread. michael@0: */ michael@0: sendChromeMessage: function(message) { michael@0: message.rilMessageClientId = this.context.clientId; michael@0: postMessage(message); michael@0: }, michael@0: michael@0: /** michael@0: * Handle incoming requests from the RIL. We find the method that michael@0: * corresponds to the request type. Incidentally, the request type michael@0: * _is_ the method name, so that's easy. michael@0: */ michael@0: michael@0: handleParcel: function(request_type, length, options) { michael@0: let method = this[request_type]; michael@0: if (typeof method == "function") { michael@0: if (DEBUG) this.context.debug("Handling parcel as " + method.name); michael@0: method.call(this, length, options); michael@0: } michael@0: } michael@0: }; michael@0: michael@0: RilObject.prototype[REQUEST_GET_SIM_STATUS] = function REQUEST_GET_SIM_STATUS(length, options) { michael@0: if (options.rilRequestError) { michael@0: return; michael@0: } michael@0: michael@0: let iccStatus = {}; michael@0: let Buf = this.context.Buf; michael@0: iccStatus.cardState = Buf.readInt32(); // CARD_STATE_* michael@0: iccStatus.universalPINState = Buf.readInt32(); // CARD_PINSTATE_* michael@0: iccStatus.gsmUmtsSubscriptionAppIndex = Buf.readInt32(); michael@0: iccStatus.cdmaSubscriptionAppIndex = Buf.readInt32(); michael@0: if (!this.v5Legacy) { michael@0: iccStatus.imsSubscriptionAppIndex = Buf.readInt32(); michael@0: } michael@0: michael@0: let apps_length = Buf.readInt32(); michael@0: if (apps_length > CARD_MAX_APPS) { michael@0: apps_length = CARD_MAX_APPS; michael@0: } michael@0: michael@0: iccStatus.apps = []; michael@0: for (let i = 0 ; i < apps_length ; i++) { michael@0: iccStatus.apps.push({ michael@0: app_type: Buf.readInt32(), // CARD_APPTYPE_* michael@0: app_state: Buf.readInt32(), // CARD_APPSTATE_* michael@0: perso_substate: Buf.readInt32(), // CARD_PERSOSUBSTATE_* michael@0: aid: Buf.readString(), michael@0: app_label: Buf.readString(), michael@0: pin1_replaced: Buf.readInt32(), michael@0: pin1: Buf.readInt32(), michael@0: pin2: Buf.readInt32() michael@0: }); michael@0: if (RILQUIRKS_SIM_APP_STATE_EXTRA_FIELDS) { michael@0: Buf.readInt32(); michael@0: Buf.readInt32(); michael@0: Buf.readInt32(); michael@0: Buf.readInt32(); michael@0: } michael@0: } michael@0: michael@0: if (DEBUG) this.context.debug("iccStatus: " + JSON.stringify(iccStatus)); michael@0: this._processICCStatus(iccStatus); michael@0: }; michael@0: RilObject.prototype[REQUEST_ENTER_SIM_PIN] = function REQUEST_ENTER_SIM_PIN(length, options) { michael@0: this._processEnterAndChangeICCResponses(length, options); michael@0: }; michael@0: RilObject.prototype[REQUEST_ENTER_SIM_PUK] = function REQUEST_ENTER_SIM_PUK(length, options) { michael@0: this._processEnterAndChangeICCResponses(length, options); michael@0: }; michael@0: RilObject.prototype[REQUEST_ENTER_SIM_PIN2] = function REQUEST_ENTER_SIM_PIN2(length, options) { michael@0: this._processEnterAndChangeICCResponses(length, options); michael@0: }; michael@0: RilObject.prototype[REQUEST_ENTER_SIM_PUK2] = function REQUEST_ENTER_SIM_PUK(length, options) { michael@0: this._processEnterAndChangeICCResponses(length, options); michael@0: }; michael@0: RilObject.prototype[REQUEST_CHANGE_SIM_PIN] = function REQUEST_CHANGE_SIM_PIN(length, options) { michael@0: this._processEnterAndChangeICCResponses(length, options); michael@0: }; michael@0: RilObject.prototype[REQUEST_CHANGE_SIM_PIN2] = function REQUEST_CHANGE_SIM_PIN2(length, options) { michael@0: this._processEnterAndChangeICCResponses(length, options); michael@0: }; michael@0: RilObject.prototype[REQUEST_ENTER_NETWORK_DEPERSONALIZATION_CODE] = michael@0: function REQUEST_ENTER_NETWORK_DEPERSONALIZATION_CODE(length, options) { michael@0: this._processEnterAndChangeICCResponses(length, options); michael@0: }; michael@0: RilObject.prototype[REQUEST_GET_CURRENT_CALLS] = function REQUEST_GET_CURRENT_CALLS(length, options) { michael@0: if (options.rilRequestError) { michael@0: return; michael@0: } michael@0: michael@0: let Buf = this.context.Buf; michael@0: let calls_length = 0; michael@0: // The RIL won't even send us the length integer if there are no active calls. michael@0: // So only read this integer if the parcel actually has it. michael@0: if (length) { michael@0: calls_length = Buf.readInt32(); michael@0: } michael@0: if (!calls_length) { michael@0: this._processCalls(null); michael@0: return; michael@0: } michael@0: michael@0: let calls = {}; michael@0: for (let i = 0; i < calls_length; i++) { michael@0: let call = {}; michael@0: michael@0: // Extra uint32 field to get correct callIndex and rest of call data for michael@0: // call waiting feature. michael@0: if (RILQUIRKS_EXTRA_UINT32_2ND_CALL && i > 0) { michael@0: Buf.readInt32(); michael@0: } michael@0: michael@0: call.state = Buf.readInt32(); // CALL_STATE_* michael@0: call.callIndex = Buf.readInt32(); // GSM index (1-based) michael@0: call.toa = Buf.readInt32(); michael@0: call.isMpty = Boolean(Buf.readInt32()); michael@0: call.isMT = Boolean(Buf.readInt32()); michael@0: call.als = Buf.readInt32(); michael@0: call.isVoice = Boolean(Buf.readInt32()); michael@0: call.isVoicePrivacy = Boolean(Buf.readInt32()); michael@0: if (RILQUIRKS_CALLSTATE_EXTRA_UINT32) { michael@0: Buf.readInt32(); michael@0: } michael@0: call.number = Buf.readString(); //TODO munge with TOA michael@0: call.numberPresentation = Buf.readInt32(); // CALL_PRESENTATION_* michael@0: call.name = Buf.readString(); michael@0: call.namePresentation = Buf.readInt32(); michael@0: michael@0: call.uusInfo = null; michael@0: let uusInfoPresent = Buf.readInt32(); michael@0: if (uusInfoPresent == 1) { michael@0: call.uusInfo = { michael@0: type: Buf.readInt32(), michael@0: dcs: Buf.readInt32(), michael@0: userData: null //XXX TODO byte array?!? michael@0: }; michael@0: } michael@0: michael@0: calls[call.callIndex] = call; michael@0: } michael@0: this._processCalls(calls); michael@0: }; michael@0: RilObject.prototype[REQUEST_DIAL] = function REQUEST_DIAL(length, options) { michael@0: // We already return a successful response before. Don't respond it again! michael@0: if (options.rilRequestError) { michael@0: this.getFailCauseCode((function(failCause) { michael@0: this._removePendingOutgoingCall(failCause); michael@0: }).bind(this)); michael@0: } michael@0: }; michael@0: RilObject.prototype[REQUEST_DIAL_EMERGENCY_CALL] = RilObject.prototype[REQUEST_DIAL]; michael@0: RilObject.prototype[REQUEST_GET_IMSI] = function REQUEST_GET_IMSI(length, options) { michael@0: if (options.rilRequestError) { michael@0: return; michael@0: } michael@0: michael@0: this.iccInfoPrivate.imsi = this.context.Buf.readString(); michael@0: if (DEBUG) { michael@0: this.context.debug("IMSI: " + this.iccInfoPrivate.imsi); michael@0: } michael@0: michael@0: options.rilMessageType = "iccimsi"; michael@0: options.imsi = this.iccInfoPrivate.imsi; michael@0: this.sendChromeMessage(options); michael@0: }; michael@0: RilObject.prototype[REQUEST_HANGUP] = function REQUEST_HANGUP(length, options) { michael@0: if (options.rilRequestError) { michael@0: return; michael@0: } michael@0: michael@0: this.getCurrentCalls(); michael@0: }; michael@0: RilObject.prototype[REQUEST_HANGUP_WAITING_OR_BACKGROUND] = function REQUEST_HANGUP_WAITING_OR_BACKGROUND(length, options) { michael@0: if (options.rilRequestError) { michael@0: return; michael@0: } michael@0: michael@0: this.getCurrentCalls(); michael@0: }; michael@0: RilObject.prototype[REQUEST_HANGUP_FOREGROUND_RESUME_BACKGROUND] = function REQUEST_HANGUP_FOREGROUND_RESUME_BACKGROUND(length, options) { michael@0: if (options.rilRequestError) { michael@0: return; michael@0: } michael@0: michael@0: this.getCurrentCalls(); michael@0: }; michael@0: RilObject.prototype[REQUEST_SWITCH_WAITING_OR_HOLDING_AND_ACTIVE] = function REQUEST_SWITCH_WAITING_OR_HOLDING_AND_ACTIVE(length, options) { michael@0: options.success = (options.rilRequestError === 0); michael@0: if (!options.success) { michael@0: options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError]; michael@0: this.sendChromeMessage(options); michael@0: return; michael@0: } michael@0: michael@0: this.sendChromeMessage(options); michael@0: this.getCurrentCalls(); michael@0: }; michael@0: RilObject.prototype[REQUEST_CONFERENCE] = function REQUEST_CONFERENCE(length, options) { michael@0: options.success = (options.rilRequestError === 0); michael@0: if (!options.success) { michael@0: this._hasConferenceRequest = false; michael@0: options.errorName = "addError"; michael@0: options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError]; michael@0: this.sendChromeMessage(options); michael@0: return; michael@0: } michael@0: michael@0: this.sendChromeMessage(options); michael@0: }; michael@0: RilObject.prototype[REQUEST_UDUB] = null; michael@0: RilObject.prototype[REQUEST_LAST_CALL_FAIL_CAUSE] = function REQUEST_LAST_CALL_FAIL_CAUSE(length, options) { michael@0: let Buf = this.context.Buf; michael@0: let num = length ? Buf.readInt32() : 0; michael@0: let failCause = num ? RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[Buf.readInt32()] : null; michael@0: if (options.callback) { michael@0: options.callback(failCause); michael@0: } michael@0: }; michael@0: RilObject.prototype[REQUEST_SIGNAL_STRENGTH] = function REQUEST_SIGNAL_STRENGTH(length, options) { michael@0: this._receivedNetworkInfo(NETWORK_INFO_SIGNAL); michael@0: michael@0: if (options.rilRequestError) { michael@0: return; michael@0: } michael@0: michael@0: let Buf = this.context.Buf; michael@0: let signal = { michael@0: gsmSignalStrength: Buf.readInt32(), michael@0: gsmBitErrorRate: Buf.readInt32(), michael@0: cdmaDBM: Buf.readInt32(), michael@0: cdmaECIO: Buf.readInt32(), michael@0: evdoDBM: Buf.readInt32(), michael@0: evdoECIO: Buf.readInt32(), michael@0: evdoSNR: Buf.readInt32() michael@0: }; michael@0: michael@0: if (!this.v5Legacy) { michael@0: signal.lteSignalStrength = Buf.readInt32(); michael@0: signal.lteRSRP = Buf.readInt32(); michael@0: signal.lteRSRQ = Buf.readInt32(); michael@0: signal.lteRSSNR = Buf.readInt32(); michael@0: signal.lteCQI = Buf.readInt32(); michael@0: } michael@0: michael@0: if (DEBUG) this.context.debug("signal strength: " + JSON.stringify(signal)); michael@0: michael@0: this._processSignalStrength(signal); michael@0: }; michael@0: RilObject.prototype[REQUEST_VOICE_REGISTRATION_STATE] = function REQUEST_VOICE_REGISTRATION_STATE(length, options) { michael@0: this._receivedNetworkInfo(NETWORK_INFO_VOICE_REGISTRATION_STATE); michael@0: michael@0: if (options.rilRequestError) { michael@0: return; michael@0: } michael@0: michael@0: let state = this.context.Buf.readStringList(); michael@0: if (DEBUG) this.context.debug("voice registration state: " + state); michael@0: michael@0: this._processVoiceRegistrationState(state); michael@0: michael@0: if (this.cachedDialRequest && michael@0: (this.voiceRegistrationState.emergencyCallsOnly || michael@0: this.voiceRegistrationState.connected) && michael@0: this.voiceRegistrationState.radioTech != NETWORK_CREG_TECH_UNKNOWN) { michael@0: // Radio is ready for making the cached emergency call. michael@0: this.cachedDialRequest.callback(); michael@0: this.cachedDialRequest = null; michael@0: } michael@0: }; michael@0: RilObject.prototype[REQUEST_DATA_REGISTRATION_STATE] = function REQUEST_DATA_REGISTRATION_STATE(length, options) { michael@0: this._receivedNetworkInfo(NETWORK_INFO_DATA_REGISTRATION_STATE); michael@0: michael@0: if (options.rilRequestError) { michael@0: return; michael@0: } michael@0: michael@0: let state = this.context.Buf.readStringList(); michael@0: this._processDataRegistrationState(state); michael@0: }; michael@0: RilObject.prototype[REQUEST_OPERATOR] = function REQUEST_OPERATOR(length, options) { michael@0: this._receivedNetworkInfo(NETWORK_INFO_OPERATOR); michael@0: michael@0: if (options.rilRequestError) { michael@0: return; michael@0: } michael@0: michael@0: let operatorData = this.context.Buf.readStringList(); michael@0: if (DEBUG) this.context.debug("Operator: " + operatorData); michael@0: this._processOperator(operatorData); michael@0: }; michael@0: RilObject.prototype[REQUEST_RADIO_POWER] = function REQUEST_RADIO_POWER(length, options) { michael@0: if (options.rilMessageType == null) { michael@0: // The request was made by ril_worker itself. michael@0: if (options.rilRequestError) { michael@0: if (this.cachedDialRequest && options.enabled) { michael@0: // Turning on radio fails. Notify the error of making an emergency call. michael@0: this.cachedDialRequest.onerror(GECKO_ERROR_RADIO_NOT_AVAILABLE); michael@0: this.cachedDialRequest = null; michael@0: } michael@0: } michael@0: return; michael@0: } michael@0: michael@0: options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError]; michael@0: this.sendChromeMessage(options); michael@0: }; michael@0: RilObject.prototype[REQUEST_DTMF] = null; michael@0: RilObject.prototype[REQUEST_SEND_SMS] = function REQUEST_SEND_SMS(length, options) { michael@0: this._processSmsSendResult(length, options); michael@0: }; michael@0: RilObject.prototype[REQUEST_SEND_SMS_EXPECT_MORE] = null; michael@0: michael@0: RilObject.prototype.readSetupDataCall_v5 = function readSetupDataCall_v5(options) { michael@0: if (!options) { michael@0: options = {}; michael@0: } michael@0: let [cid, ifname, addresses, dnses, gateways] = this.context.Buf.readStringList(); michael@0: options.cid = cid; michael@0: options.ifname = ifname; michael@0: options.addresses = addresses ? [addresses] : []; michael@0: options.dnses = dnses ? [dnses] : []; michael@0: options.gateways = gateways ? [gateways] : []; michael@0: options.active = DATACALL_ACTIVE_UNKNOWN; michael@0: options.state = GECKO_NETWORK_STATE_CONNECTING; michael@0: return options; michael@0: }; michael@0: michael@0: RilObject.prototype[REQUEST_SETUP_DATA_CALL] = function REQUEST_SETUP_DATA_CALL(length, options) { michael@0: if (options.rilRequestError) { michael@0: // On Data Call generic errors, we shall notify caller michael@0: this._sendDataCallError(options, options.rilRequestError); michael@0: return; michael@0: } michael@0: michael@0: if (this.v5Legacy) { michael@0: // Populate the `options` object with the data call information. That way michael@0: // we retain the APN and other info about how the data call was set up. michael@0: this.readSetupDataCall_v5(options); michael@0: this.currentDataCalls[options.cid] = options; michael@0: options.rilMessageType = "datacallstatechange"; michael@0: this.sendChromeMessage(options); michael@0: // Let's get the list of data calls to ensure we know whether it's active michael@0: // or not. michael@0: this.getDataCallList(); michael@0: return; michael@0: } michael@0: // Pass `options` along. That way we retain the APN and other info about michael@0: // how the data call was set up. michael@0: this[REQUEST_DATA_CALL_LIST](length, options); michael@0: }; michael@0: RilObject.prototype[REQUEST_SIM_IO] = function REQUEST_SIM_IO(length, options) { michael@0: let ICCIOHelper = this.context.ICCIOHelper; michael@0: if (!length) { michael@0: ICCIOHelper.processICCIOError(options); michael@0: return; michael@0: } michael@0: michael@0: // Don't need to read rilRequestError since we can know error status from michael@0: // sw1 and sw2. michael@0: let Buf = this.context.Buf; michael@0: options.sw1 = Buf.readInt32(); michael@0: options.sw2 = Buf.readInt32(); michael@0: if (options.sw1 != ICC_STATUS_NORMAL_ENDING) { michael@0: ICCIOHelper.processICCIOError(options); michael@0: return; michael@0: } michael@0: ICCIOHelper.processICCIO(options); michael@0: }; michael@0: RilObject.prototype[REQUEST_SEND_USSD] = function REQUEST_SEND_USSD(length, options) { michael@0: if (DEBUG) { michael@0: this.context.debug("REQUEST_SEND_USSD " + JSON.stringify(options)); michael@0: } michael@0: options.success = (this._ussdSession = options.rilRequestError === 0); michael@0: options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError]; michael@0: this.sendChromeMessage(options); michael@0: }; michael@0: RilObject.prototype[REQUEST_CANCEL_USSD] = function REQUEST_CANCEL_USSD(length, options) { michael@0: if (DEBUG) { michael@0: this.context.debug("REQUEST_CANCEL_USSD" + JSON.stringify(options)); michael@0: } michael@0: options.success = (options.rilRequestError === 0); michael@0: this._ussdSession = !options.success; michael@0: options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError]; michael@0: this.sendChromeMessage(options); michael@0: }; michael@0: RilObject.prototype[REQUEST_GET_CLIR] = function REQUEST_GET_CLIR(length, options) { michael@0: options.success = (options.rilRequestError === 0); michael@0: if (!options.success) { michael@0: options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError]; michael@0: this.sendChromeMessage(options); michael@0: return; michael@0: } michael@0: michael@0: let Buf = this.context.Buf; michael@0: let bufLength = Buf.readInt32(); michael@0: if (!bufLength || bufLength < 2) { michael@0: options.success = false; michael@0: options.errorMsg = GECKO_ERROR_GENERIC_FAILURE; michael@0: this.sendChromeMessage(options); michael@0: return; michael@0: } michael@0: michael@0: options.n = Buf.readInt32(); // Will be TS 27.007 +CLIR parameter 'n'. michael@0: options.m = Buf.readInt32(); // Will be TS 27.007 +CLIR parameter 'm'. michael@0: michael@0: if (options.rilMessageType === "sendMMI") { michael@0: // TS 27.007 +CLIR parameter 'm'. michael@0: switch (options.m) { michael@0: // CLIR not provisioned. michael@0: case 0: michael@0: options.statusMessage = MMI_SM_KS_SERVICE_NOT_PROVISIONED; michael@0: break; michael@0: // CLIR provisioned in permanent mode. michael@0: case 1: michael@0: options.statusMessage = MMI_SM_KS_CLIR_PERMANENT; michael@0: break; michael@0: // Unknown (e.g. no network, etc.). michael@0: case 2: michael@0: options.success = false; michael@0: options.errorMsg = MMI_ERROR_KS_ERROR; michael@0: break; michael@0: // CLIR temporary mode presentation restricted. michael@0: case 3: michael@0: // TS 27.007 +CLIR parameter 'n'. michael@0: switch (options.n) { michael@0: // Default. michael@0: case 0: michael@0: // CLIR invocation. michael@0: case 1: michael@0: options.statusMessage = MMI_SM_KS_CLIR_DEFAULT_ON_NEXT_CALL_ON; michael@0: break; michael@0: // CLIR suppression. michael@0: case 2: michael@0: options.statusMessage = MMI_SM_KS_CLIR_DEFAULT_ON_NEXT_CALL_OFF; michael@0: break; michael@0: default: michael@0: options.success = false; michael@0: options.errorMsg = GECKO_ERROR_GENERIC_FAILURE; michael@0: break; michael@0: } michael@0: break; michael@0: // CLIR temporary mode presentation allowed. michael@0: case 4: michael@0: // TS 27.007 +CLIR parameter 'n'. michael@0: switch (options.n) { michael@0: // Default. michael@0: case 0: michael@0: // CLIR suppression. michael@0: case 2: michael@0: options.statusMessage = MMI_SM_KS_CLIR_DEFAULT_OFF_NEXT_CALL_OFF; michael@0: break; michael@0: // CLIR invocation. michael@0: case 1: michael@0: options.statusMessage = MMI_SM_KS_CLIR_DEFAULT_OFF_NEXT_CALL_ON; michael@0: break; michael@0: default: michael@0: options.success = false; michael@0: options.errorMsg = GECKO_ERROR_GENERIC_FAILURE; michael@0: break; michael@0: } michael@0: break; michael@0: default: michael@0: options.success = false; michael@0: options.errorMsg = GECKO_ERROR_GENERIC_FAILURE; michael@0: break; michael@0: } michael@0: } michael@0: michael@0: this.sendChromeMessage(options); michael@0: }; michael@0: RilObject.prototype[REQUEST_SET_CLIR] = function REQUEST_SET_CLIR(length, options) { michael@0: if (options.rilMessageType == null) { michael@0: // The request was made by ril_worker itself automatically. Don't report. michael@0: return; michael@0: } michael@0: options.success = (options.rilRequestError === 0); michael@0: if (!options.success) { michael@0: options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError]; michael@0: } else if (options.rilMessageType === "sendMMI") { michael@0: switch (options.procedure) { michael@0: case MMI_PROCEDURE_ACTIVATION: michael@0: options.statusMessage = MMI_SM_KS_SERVICE_ENABLED; michael@0: break; michael@0: case MMI_PROCEDURE_DEACTIVATION: michael@0: options.statusMessage = MMI_SM_KS_SERVICE_DISABLED; michael@0: break; michael@0: } michael@0: } michael@0: this.sendChromeMessage(options); michael@0: }; michael@0: michael@0: RilObject.prototype[REQUEST_QUERY_CALL_FORWARD_STATUS] = michael@0: function REQUEST_QUERY_CALL_FORWARD_STATUS(length, options) { michael@0: options.success = (options.rilRequestError === 0); michael@0: if (!options.success) { michael@0: options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError]; michael@0: this.sendChromeMessage(options); michael@0: return; michael@0: } michael@0: michael@0: let Buf = this.context.Buf; michael@0: let rulesLength = 0; michael@0: if (length) { michael@0: rulesLength = Buf.readInt32(); michael@0: } michael@0: if (!rulesLength) { michael@0: options.success = false; michael@0: options.errorMsg = GECKO_ERROR_GENERIC_FAILURE; michael@0: this.sendChromeMessage(options); michael@0: return; michael@0: } michael@0: let rules = new Array(rulesLength); michael@0: for (let i = 0; i < rulesLength; i++) { michael@0: let rule = {}; michael@0: rule.active = Buf.readInt32() == 1; // CALL_FORWARD_STATUS_* michael@0: rule.reason = Buf.readInt32(); // CALL_FORWARD_REASON_* michael@0: rule.serviceClass = Buf.readInt32(); michael@0: rule.toa = Buf.readInt32(); michael@0: rule.number = Buf.readString(); michael@0: rule.timeSeconds = Buf.readInt32(); michael@0: rules[i] = rule; michael@0: } michael@0: options.rules = rules; michael@0: if (options.rilMessageType === "sendMMI") { michael@0: options.statusMessage = MMI_SM_KS_SERVICE_INTERROGATED; michael@0: // MMI query call forwarding options request returns a set of rules that michael@0: // will be exposed in the form of an array of nsIDOMMozMobileCFInfo michael@0: // instances. michael@0: options.additionalInformation = rules; michael@0: } michael@0: this.sendChromeMessage(options); michael@0: }; michael@0: RilObject.prototype[REQUEST_SET_CALL_FORWARD] = michael@0: function REQUEST_SET_CALL_FORWARD(length, options) { michael@0: options.success = (options.rilRequestError === 0); michael@0: if (!options.success) { michael@0: options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError]; michael@0: } else if (options.rilMessageType === "sendMMI") { michael@0: switch (options.action) { michael@0: case CALL_FORWARD_ACTION_ENABLE: michael@0: options.statusMessage = MMI_SM_KS_SERVICE_ENABLED; michael@0: break; michael@0: case CALL_FORWARD_ACTION_DISABLE: michael@0: options.statusMessage = MMI_SM_KS_SERVICE_DISABLED; michael@0: break; michael@0: case CALL_FORWARD_ACTION_REGISTRATION: michael@0: options.statusMessage = MMI_SM_KS_SERVICE_REGISTERED; michael@0: break; michael@0: case CALL_FORWARD_ACTION_ERASURE: michael@0: options.statusMessage = MMI_SM_KS_SERVICE_ERASED; michael@0: break; michael@0: } michael@0: } michael@0: this.sendChromeMessage(options); michael@0: }; michael@0: RilObject.prototype[REQUEST_QUERY_CALL_WAITING] = michael@0: function REQUEST_QUERY_CALL_WAITING(length, options) { michael@0: options.success = (options.rilRequestError === 0); michael@0: if (!options.success) { michael@0: options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError]; michael@0: this.sendChromeMessage(options); michael@0: return; michael@0: } michael@0: michael@0: if (options.callback) { michael@0: options.callback.call(this, options); michael@0: return; michael@0: } michael@0: michael@0: let Buf = this.context.Buf; michael@0: options.length = Buf.readInt32(); michael@0: options.enabled = ((Buf.readInt32() == 1) && michael@0: ((Buf.readInt32() & ICC_SERVICE_CLASS_VOICE) == 0x01)); michael@0: this.sendChromeMessage(options); michael@0: }; michael@0: michael@0: RilObject.prototype[REQUEST_SET_CALL_WAITING] = function REQUEST_SET_CALL_WAITING(length, options) { michael@0: options.success = (options.rilRequestError === 0); michael@0: if (!options.success) { michael@0: options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError]; michael@0: this.sendChromeMessage(options); michael@0: return; michael@0: } michael@0: michael@0: if (options.callback) { michael@0: options.callback.call(this, options); michael@0: return; michael@0: } michael@0: michael@0: this.sendChromeMessage(options); michael@0: }; michael@0: RilObject.prototype[REQUEST_SMS_ACKNOWLEDGE] = null; michael@0: RilObject.prototype[REQUEST_GET_IMEI] = function REQUEST_GET_IMEI(length, options) { michael@0: this.IMEI = this.context.Buf.readString(); michael@0: let rilMessageType = options.rilMessageType; michael@0: // So far we only send the IMEI back to chrome if it was requested via MMI. michael@0: if (rilMessageType !== "sendMMI") { michael@0: return; michael@0: } michael@0: michael@0: options.success = (options.rilRequestError === 0); michael@0: options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError]; michael@0: if ((!options.success || this.IMEI == null) && !options.errorMsg) { michael@0: options.errorMsg = GECKO_ERROR_GENERIC_FAILURE; michael@0: } michael@0: options.statusMessage = this.IMEI; michael@0: this.sendChromeMessage(options); michael@0: }; michael@0: RilObject.prototype[REQUEST_GET_IMEISV] = function REQUEST_GET_IMEISV(length, options) { michael@0: if (options.rilRequestError) { michael@0: return; michael@0: } michael@0: michael@0: this.IMEISV = this.context.Buf.readString(); michael@0: }; michael@0: RilObject.prototype[REQUEST_ANSWER] = null; michael@0: RilObject.prototype[REQUEST_DEACTIVATE_DATA_CALL] = function REQUEST_DEACTIVATE_DATA_CALL(length, options) { michael@0: if (options.rilRequestError) { michael@0: return; michael@0: } michael@0: michael@0: let datacall = this.currentDataCalls[options.cid]; michael@0: delete this.currentDataCalls[options.cid]; michael@0: datacall.state = GECKO_NETWORK_STATE_UNKNOWN; michael@0: datacall.rilMessageType = "datacallstatechange"; michael@0: this.sendChromeMessage(datacall); michael@0: }; michael@0: RilObject.prototype[REQUEST_QUERY_FACILITY_LOCK] = function REQUEST_QUERY_FACILITY_LOCK(length, options) { michael@0: options.success = (options.rilRequestError === 0); michael@0: if (!options.success) { michael@0: options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError]; michael@0: } michael@0: michael@0: let services; michael@0: if (length) { michael@0: // Buf.readInt32List()[0] for Call Barring is a bit vector of services. michael@0: services = this.context.Buf.readInt32List()[0]; michael@0: } else { michael@0: options.success = false; michael@0: options.errorMsg = GECKO_ERROR_GENERIC_FAILURE; michael@0: this.sendChromeMessage(options); michael@0: return; michael@0: } michael@0: michael@0: options.enabled = services === 0 ? false : true; michael@0: michael@0: if (options.success && (options.rilMessageType === "sendMMI")) { michael@0: if (!options.enabled) { michael@0: options.statusMessage = MMI_SM_KS_SERVICE_DISABLED; michael@0: } else { michael@0: options.statusMessage = MMI_SM_KS_SERVICE_ENABLED_FOR; michael@0: let serviceClass = []; michael@0: for (let serviceClassMask = 1; michael@0: serviceClassMask <= ICC_SERVICE_CLASS_MAX; michael@0: serviceClassMask <<= 1) { michael@0: if ((serviceClassMask & services) !== 0) { michael@0: serviceClass.push(MMI_KS_SERVICE_CLASS_MAPPING[serviceClassMask]); michael@0: } michael@0: } michael@0: michael@0: options.additionalInformation = serviceClass; michael@0: } michael@0: } michael@0: this.sendChromeMessage(options); michael@0: }; michael@0: RilObject.prototype[REQUEST_SET_FACILITY_LOCK] = function REQUEST_SET_FACILITY_LOCK(length, options) { michael@0: options.success = (options.rilRequestError === 0); michael@0: if (!options.success) { michael@0: options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError]; michael@0: } michael@0: michael@0: options.retryCount = length ? this.context.Buf.readInt32List()[0] : -1; michael@0: michael@0: if (options.success && (options.rilMessageType === "sendMMI")) { michael@0: switch (options.procedure) { michael@0: case MMI_PROCEDURE_ACTIVATION: michael@0: options.statusMessage = MMI_SM_KS_SERVICE_ENABLED; michael@0: break; michael@0: case MMI_PROCEDURE_DEACTIVATION: michael@0: options.statusMessage = MMI_SM_KS_SERVICE_DISABLED; michael@0: break; michael@0: } michael@0: } michael@0: this.sendChromeMessage(options); michael@0: }; michael@0: RilObject.prototype[REQUEST_CHANGE_BARRING_PASSWORD] = michael@0: function REQUEST_CHANGE_BARRING_PASSWORD(length, options) { michael@0: if (options.rilRequestError) { michael@0: options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError]; michael@0: } michael@0: this.sendChromeMessage(options); michael@0: }; michael@0: RilObject.prototype[REQUEST_SIM_OPEN_CHANNEL] = function REQUEST_SIM_OPEN_CHANNEL(length, options) { michael@0: if (options.rilRequestError) { michael@0: options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError]; michael@0: this.sendChromeMessage(options); michael@0: return; michael@0: } michael@0: michael@0: options.channel = this.context.Buf.readInt32(); michael@0: if (DEBUG) { michael@0: this.context.debug("Setting channel number in options: " + options.channel); michael@0: } michael@0: this.sendChromeMessage(options); michael@0: }; michael@0: RilObject.prototype[REQUEST_SIM_CLOSE_CHANNEL] = function REQUEST_SIM_CLOSE_CHANNEL(length, options) { michael@0: if (options.rilRequestError) { michael@0: options.error = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError]; michael@0: this.sendChromeMessage(options); michael@0: return; michael@0: } michael@0: michael@0: // No return value michael@0: this.sendChromeMessage(options); michael@0: }; michael@0: RilObject.prototype[REQUEST_SIM_ACCESS_CHANNEL] = function REQUEST_SIM_ACCESS_CHANNEL(length, options) { michael@0: if (options.rilRequestError) { michael@0: options.error = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError]; michael@0: this.sendChromeMessage(options); michael@0: } michael@0: michael@0: let Buf = this.context.Buf; michael@0: options.sw1 = Buf.readInt32(); michael@0: options.sw2 = Buf.readInt32(); michael@0: options.simResponse = Buf.readString(); michael@0: if (DEBUG) { michael@0: this.context.debug("Setting return values for RIL[REQUEST_SIM_ACCESS_CHANNEL]: [" + michael@0: options.sw1 + "," + michael@0: options.sw2 + ", " + michael@0: options.simResponse + "]"); michael@0: } michael@0: this.sendChromeMessage(options); michael@0: }; michael@0: RilObject.prototype[REQUEST_QUERY_NETWORK_SELECTION_MODE] = function REQUEST_QUERY_NETWORK_SELECTION_MODE(length, options) { michael@0: this._receivedNetworkInfo(NETWORK_INFO_NETWORK_SELECTION_MODE); michael@0: michael@0: if (options.rilRequestError) { michael@0: return; michael@0: } michael@0: michael@0: let mode = this.context.Buf.readInt32List(); michael@0: let selectionMode; michael@0: michael@0: switch (mode[0]) { michael@0: case NETWORK_SELECTION_MODE_AUTOMATIC: michael@0: selectionMode = GECKO_NETWORK_SELECTION_AUTOMATIC; michael@0: break; michael@0: case NETWORK_SELECTION_MODE_MANUAL: michael@0: selectionMode = GECKO_NETWORK_SELECTION_MANUAL; michael@0: break; michael@0: default: michael@0: selectionMode = GECKO_NETWORK_SELECTION_UNKNOWN; michael@0: break; michael@0: } michael@0: michael@0: if (this.networkSelectionMode != selectionMode) { michael@0: this.networkSelectionMode = options.mode = selectionMode; michael@0: options.rilMessageType = "networkselectionmodechange"; michael@0: this._sendNetworkInfoMessage(NETWORK_INFO_NETWORK_SELECTION_MODE, options); michael@0: } michael@0: }; michael@0: RilObject.prototype[REQUEST_SET_NETWORK_SELECTION_AUTOMATIC] = function REQUEST_SET_NETWORK_SELECTION_AUTOMATIC(length, options) { michael@0: if (options.rilRequestError) { michael@0: options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError]; michael@0: } michael@0: michael@0: this.sendChromeMessage(options); michael@0: }; michael@0: RilObject.prototype[REQUEST_SET_NETWORK_SELECTION_MANUAL] = function REQUEST_SET_NETWORK_SELECTION_MANUAL(length, options) { michael@0: if (options.rilRequestError) { michael@0: options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError]; michael@0: } michael@0: michael@0: this.sendChromeMessage(options); michael@0: }; michael@0: RilObject.prototype[REQUEST_QUERY_AVAILABLE_NETWORKS] = function REQUEST_QUERY_AVAILABLE_NETWORKS(length, options) { michael@0: if (options.rilRequestError) { michael@0: options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError]; michael@0: } else { michael@0: options.networks = this._processNetworks(); michael@0: } michael@0: this.sendChromeMessage(options); michael@0: }; michael@0: RilObject.prototype[REQUEST_DTMF_START] = null; michael@0: RilObject.prototype[REQUEST_DTMF_STOP] = null; michael@0: RilObject.prototype[REQUEST_BASEBAND_VERSION] = function REQUEST_BASEBAND_VERSION(length, options) { michael@0: if (options.rilRequestError) { michael@0: return; michael@0: } michael@0: michael@0: this.basebandVersion = this.context.Buf.readString(); michael@0: if (DEBUG) this.context.debug("Baseband version: " + this.basebandVersion); michael@0: }; michael@0: RilObject.prototype[REQUEST_SEPARATE_CONNECTION] = function REQUEST_SEPARATE_CONNECTION(length, options) { michael@0: options.success = (options.rilRequestError === 0); michael@0: if (!options.success) { michael@0: options.errorName = "removeError"; michael@0: options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError]; michael@0: this.sendChromeMessage(options); michael@0: return; michael@0: } michael@0: michael@0: this.sendChromeMessage(options); michael@0: }; michael@0: RilObject.prototype[REQUEST_SET_MUTE] = null; michael@0: RilObject.prototype[REQUEST_GET_MUTE] = null; michael@0: RilObject.prototype[REQUEST_QUERY_CLIP] = function REQUEST_QUERY_CLIP(length, options) { michael@0: options.success = (options.rilRequestError === 0); michael@0: if (!options.success) { michael@0: options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError]; michael@0: this.sendChromeMessage(options); michael@0: return; michael@0: } michael@0: michael@0: let Buf = this.context.Buf; michael@0: let bufLength = Buf.readInt32(); michael@0: if (!bufLength) { michael@0: options.success = false; michael@0: options.errorMsg = GECKO_ERROR_GENERIC_FAILURE; michael@0: this.sendChromeMessage(options); michael@0: return; michael@0: } michael@0: michael@0: // options.provisioned informs about the called party receives the calling michael@0: // party's address information: michael@0: // 0 for CLIP not provisioned michael@0: // 1 for CLIP provisioned michael@0: // 2 for unknown michael@0: options.provisioned = Buf.readInt32(); michael@0: if (options.rilMessageType === "sendMMI") { michael@0: switch (options.provisioned) { michael@0: case 0: michael@0: options.statusMessage = MMI_SM_KS_SERVICE_DISABLED; michael@0: break; michael@0: case 1: michael@0: options.statusMessage = MMI_SM_KS_SERVICE_ENABLED; michael@0: break; michael@0: default: michael@0: options.success = false; michael@0: options.errorMsg = MMI_ERROR_KS_ERROR; michael@0: break; michael@0: } michael@0: } michael@0: this.sendChromeMessage(options); michael@0: }; michael@0: RilObject.prototype[REQUEST_LAST_DATA_CALL_FAIL_CAUSE] = null; michael@0: michael@0: /** michael@0: * V3: michael@0: * # address - A space-delimited list of addresses. michael@0: * michael@0: * V4: michael@0: * # address - An address. michael@0: * michael@0: * V5: michael@0: * # addresses - A space-delimited list of addresses. michael@0: * # dnses - A space-delimited list of DNS server addresses. michael@0: * michael@0: * V6: michael@0: * # addresses - A space-delimited list of addresses with optional "/" prefix michael@0: * length. michael@0: * # dnses - A space-delimited list of DNS server addresses. michael@0: * # gateways - A space-delimited list of default gateway addresses. michael@0: */ michael@0: RilObject.prototype.readDataCall_v5 = function(options) { michael@0: if (!options) { michael@0: options = {}; michael@0: } michael@0: let Buf = this.context.Buf; michael@0: options.cid = Buf.readInt32().toString(); michael@0: options.active = Buf.readInt32(); // DATACALL_ACTIVE_* michael@0: options.type = Buf.readString(); michael@0: options.apn = Buf.readString(); michael@0: let addresses = Buf.readString(); michael@0: let dnses = Buf.readString(); michael@0: options.addresses = addresses ? addresses.split(" ") : []; michael@0: options.dnses = dnses ? dnses.split(" ") : []; michael@0: options.gateways = []; michael@0: return options; michael@0: }; michael@0: michael@0: RilObject.prototype.readDataCall_v6 = function(options) { michael@0: if (!options) { michael@0: options = {}; michael@0: } michael@0: let Buf = this.context.Buf; michael@0: options.status = Buf.readInt32(); // DATACALL_FAIL_* michael@0: options.suggestedRetryTime = Buf.readInt32(); michael@0: options.cid = Buf.readInt32().toString(); michael@0: options.active = Buf.readInt32(); // DATACALL_ACTIVE_* michael@0: options.type = Buf.readString(); michael@0: options.ifname = Buf.readString(); michael@0: let addresses = Buf.readString(); michael@0: let dnses = Buf.readString(); michael@0: let gateways = Buf.readString(); michael@0: options.addresses = addresses ? addresses.split(" ") : []; michael@0: options.dnses = dnses ? dnses.split(" ") : []; michael@0: options.gateways = gateways ? gateways.split(" ") : []; michael@0: return options; michael@0: }; michael@0: michael@0: RilObject.prototype[REQUEST_DATA_CALL_LIST] = function REQUEST_DATA_CALL_LIST(length, options) { michael@0: if (options.rilRequestError) { michael@0: return; michael@0: } michael@0: michael@0: if (!length) { michael@0: this._processDataCallList(null); michael@0: return; michael@0: } michael@0: michael@0: let Buf = this.context.Buf; michael@0: let version = 0; michael@0: if (!this.v5Legacy) { michael@0: version = Buf.readInt32(); michael@0: } michael@0: let num = Buf.readInt32(); michael@0: let datacalls = {}; michael@0: for (let i = 0; i < num; i++) { michael@0: let datacall; michael@0: if (version < 6) { michael@0: datacall = this.readDataCall_v5(); michael@0: } else { michael@0: datacall = this.readDataCall_v6(); michael@0: } michael@0: datacalls[datacall.cid] = datacall; michael@0: } michael@0: michael@0: let newDataCallOptions = null; michael@0: if (options.rilRequestType == REQUEST_SETUP_DATA_CALL) { michael@0: newDataCallOptions = options; michael@0: } michael@0: this._processDataCallList(datacalls, newDataCallOptions); michael@0: }; michael@0: RilObject.prototype[REQUEST_RESET_RADIO] = null; michael@0: RilObject.prototype[REQUEST_OEM_HOOK_RAW] = null; michael@0: RilObject.prototype[REQUEST_OEM_HOOK_STRINGS] = null; michael@0: RilObject.prototype[REQUEST_SCREEN_STATE] = null; michael@0: RilObject.prototype[REQUEST_SET_SUPP_SVC_NOTIFICATION] = null; michael@0: RilObject.prototype[REQUEST_WRITE_SMS_TO_SIM] = function REQUEST_WRITE_SMS_TO_SIM(length, options) { michael@0: if (options.rilRequestError) { michael@0: // `The MS shall return a "protocol error, unspecified" error message if michael@0: // the short message cannot be stored in the (U)SIM, and there is other michael@0: // message storage available at the MS` ~ 3GPP TS 23.038 section 4. Here michael@0: // we assume we always have indexed db as another storage. michael@0: this.acknowledgeGsmSms(false, PDU_FCS_PROTOCOL_ERROR); michael@0: } else { michael@0: this.acknowledgeGsmSms(true, PDU_FCS_OK); michael@0: } michael@0: }; michael@0: RilObject.prototype[REQUEST_DELETE_SMS_ON_SIM] = null; michael@0: RilObject.prototype[REQUEST_SET_BAND_MODE] = null; michael@0: RilObject.prototype[REQUEST_QUERY_AVAILABLE_BAND_MODE] = null; michael@0: RilObject.prototype[REQUEST_STK_GET_PROFILE] = null; michael@0: RilObject.prototype[REQUEST_STK_SET_PROFILE] = null; michael@0: RilObject.prototype[REQUEST_STK_SEND_ENVELOPE_COMMAND] = null; michael@0: RilObject.prototype[REQUEST_STK_SEND_TERMINAL_RESPONSE] = null; michael@0: RilObject.prototype[REQUEST_STK_HANDLE_CALL_SETUP_REQUESTED_FROM_SIM] = null; michael@0: RilObject.prototype[REQUEST_EXPLICIT_CALL_TRANSFER] = null; michael@0: RilObject.prototype[REQUEST_SET_PREFERRED_NETWORK_TYPE] = function REQUEST_SET_PREFERRED_NETWORK_TYPE(length, options) { michael@0: if (options.networkType == null) { michael@0: // The request was made by ril_worker itself automatically. Don't report. michael@0: return; michael@0: } michael@0: michael@0: if (options.rilRequestError) { michael@0: options.success = false; michael@0: options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError]; michael@0: } else { michael@0: options.success = true; michael@0: } michael@0: this.sendChromeMessage(options); michael@0: }; michael@0: RilObject.prototype[REQUEST_GET_PREFERRED_NETWORK_TYPE] = function REQUEST_GET_PREFERRED_NETWORK_TYPE(length, options) { michael@0: if (options.rilRequestError) { michael@0: options.success = false; michael@0: options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError]; michael@0: this.sendChromeMessage(options); michael@0: return; michael@0: } michael@0: michael@0: let results = this.context.Buf.readInt32List(); michael@0: options.networkType = this.preferredNetworkType = results[0]; michael@0: options.success = true; michael@0: michael@0: this.sendChromeMessage(options); michael@0: }; michael@0: RilObject.prototype[REQUEST_GET_NEIGHBORING_CELL_IDS] = null; michael@0: RilObject.prototype[REQUEST_SET_LOCATION_UPDATES] = null; michael@0: RilObject.prototype[REQUEST_CDMA_SET_SUBSCRIPTION_SOURCE] = null; michael@0: RilObject.prototype[REQUEST_CDMA_SET_ROAMING_PREFERENCE] = function REQUEST_CDMA_SET_ROAMING_PREFERENCE(length, options) { michael@0: if (options.rilRequestError) { michael@0: options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError]; michael@0: } michael@0: this.sendChromeMessage(options); michael@0: }; michael@0: RilObject.prototype[REQUEST_CDMA_QUERY_ROAMING_PREFERENCE] = function REQUEST_CDMA_QUERY_ROAMING_PREFERENCE(length, options) { michael@0: if (options.rilRequestError) { michael@0: options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError]; michael@0: } else { michael@0: let mode = this.context.Buf.readInt32List(); michael@0: options.mode = CDMA_ROAMING_PREFERENCE_TO_GECKO[mode[0]]; michael@0: } michael@0: this.sendChromeMessage(options); michael@0: }; michael@0: RilObject.prototype[REQUEST_SET_TTY_MODE] = null; michael@0: RilObject.prototype[REQUEST_QUERY_TTY_MODE] = null; michael@0: RilObject.prototype[REQUEST_CDMA_SET_PREFERRED_VOICE_PRIVACY_MODE] = function REQUEST_CDMA_SET_PREFERRED_VOICE_PRIVACY_MODE(length, options) { michael@0: if (options.rilRequestError) { michael@0: options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError]; michael@0: this.sendChromeMessage(options); michael@0: return; michael@0: } michael@0: michael@0: this.sendChromeMessage(options); michael@0: }; michael@0: RilObject.prototype[REQUEST_CDMA_QUERY_PREFERRED_VOICE_PRIVACY_MODE] = function REQUEST_CDMA_QUERY_PREFERRED_VOICE_PRIVACY_MODE(length, options) { michael@0: if (options.rilRequestError) { michael@0: options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError]; michael@0: this.sendChromeMessage(options); michael@0: return; michael@0: } michael@0: michael@0: let enabled = this.context.Buf.readInt32List(); michael@0: options.enabled = enabled[0] ? true : false; michael@0: this.sendChromeMessage(options); michael@0: }; michael@0: RilObject.prototype[REQUEST_CDMA_FLASH] = function REQUEST_CDMA_FLASH(length, options) { michael@0: options.success = (options.rilRequestError === 0); michael@0: if (!options.success) { michael@0: if (options.rilMessageType === "conferenceCall") { michael@0: options.errorName = "addError"; michael@0: } else if (options.rilMessageType === "separateCall") { michael@0: options.errorName = "removeError"; michael@0: } michael@0: options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError]; michael@0: } michael@0: michael@0: this.sendChromeMessage(options); michael@0: }; michael@0: RilObject.prototype[REQUEST_CDMA_BURST_DTMF] = null; michael@0: RilObject.prototype[REQUEST_CDMA_VALIDATE_AND_WRITE_AKEY] = null; michael@0: RilObject.prototype[REQUEST_CDMA_SEND_SMS] = function REQUEST_CDMA_SEND_SMS(length, options) { michael@0: this._processSmsSendResult(length, options); michael@0: }; michael@0: RilObject.prototype[REQUEST_CDMA_SMS_ACKNOWLEDGE] = null; michael@0: RilObject.prototype[REQUEST_GSM_GET_BROADCAST_SMS_CONFIG] = null; michael@0: RilObject.prototype[REQUEST_GSM_SET_BROADCAST_SMS_CONFIG] = function REQUEST_GSM_SET_BROADCAST_SMS_CONFIG(length, options) { michael@0: if (options.rilRequestError == ERROR_SUCCESS) { michael@0: this.setSmsBroadcastActivation(true); michael@0: } michael@0: }; michael@0: RilObject.prototype[REQUEST_GSM_SMS_BROADCAST_ACTIVATION] = null; michael@0: RilObject.prototype[REQUEST_CDMA_GET_BROADCAST_SMS_CONFIG] = null; michael@0: RilObject.prototype[REQUEST_CDMA_SET_BROADCAST_SMS_CONFIG] = null; michael@0: RilObject.prototype[REQUEST_CDMA_SMS_BROADCAST_ACTIVATION] = null; michael@0: RilObject.prototype[REQUEST_CDMA_SUBSCRIPTION] = function REQUEST_CDMA_SUBSCRIPTION(length, options) { michael@0: if (options.rilRequestError) { michael@0: return; michael@0: } michael@0: michael@0: let result = this.context.Buf.readStringList(); michael@0: michael@0: this.iccInfo.mdn = result[0]; michael@0: // The result[1] is Home SID. (Already be handled in readCDMAHome()) michael@0: // The result[2] is Home NID. (Already be handled in readCDMAHome()) michael@0: // The result[3] is MIN. michael@0: this.iccInfo.prlVersion = parseInt(result[4], 10); michael@0: michael@0: this.context.ICCUtilsHelper.handleICCInfoChange(); michael@0: }; michael@0: RilObject.prototype[REQUEST_CDMA_WRITE_SMS_TO_RUIM] = null; michael@0: RilObject.prototype[REQUEST_CDMA_DELETE_SMS_ON_RUIM] = null; michael@0: RilObject.prototype[REQUEST_DEVICE_IDENTITY] = function REQUEST_DEVICE_IDENTITY(length, options) { michael@0: if (options.rilRequestError) { michael@0: return; michael@0: } michael@0: michael@0: let result = this.context.Buf.readStringList(); michael@0: michael@0: // The result[0] is for IMEI. (Already be handled in REQUEST_GET_IMEI) michael@0: // The result[1] is for IMEISV. (Already be handled in REQUEST_GET_IMEISV) michael@0: // They are both ignored. michael@0: this.ESN = result[2]; michael@0: this.MEID = result[3]; michael@0: }; michael@0: RilObject.prototype[REQUEST_EXIT_EMERGENCY_CALLBACK_MODE] = function REQUEST_EXIT_EMERGENCY_CALLBACK_MODE(length, options) { michael@0: if (options.internal) { michael@0: return; michael@0: } michael@0: michael@0: options.success = (options.rilRequestError === 0); michael@0: if (!options.success) { michael@0: options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError]; michael@0: } michael@0: this.sendChromeMessage(options); michael@0: }; michael@0: RilObject.prototype[REQUEST_GET_SMSC_ADDRESS] = function REQUEST_GET_SMSC_ADDRESS(length, options) { michael@0: this.SMSC = options.rilRequestError ? null : this.context.Buf.readString(); michael@0: michael@0: if (!options.rilMessageType || options.rilMessageType !== "getSmscAddress") { michael@0: return; michael@0: } michael@0: michael@0: options.smscAddress = this.SMSC; michael@0: options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError]; michael@0: this.sendChromeMessage(options); michael@0: }; michael@0: RilObject.prototype[REQUEST_SET_SMSC_ADDRESS] = null; michael@0: RilObject.prototype[REQUEST_REPORT_SMS_MEMORY_STATUS] = null; michael@0: RilObject.prototype[REQUEST_REPORT_STK_SERVICE_IS_RUNNING] = null; michael@0: RilObject.prototype[REQUEST_ACKNOWLEDGE_INCOMING_GSM_SMS_WITH_PDU] = null; michael@0: RilObject.prototype[REQUEST_STK_SEND_ENVELOPE_WITH_STATUS] = function REQUEST_STK_SEND_ENVELOPE_WITH_STATUS(length, options) { michael@0: if (options.rilRequestError) { michael@0: this.acknowledgeGsmSms(false, PDU_FCS_UNSPECIFIED); michael@0: return; michael@0: } michael@0: michael@0: let Buf = this.context.Buf; michael@0: let sw1 = Buf.readInt32(); michael@0: let sw2 = Buf.readInt32(); michael@0: if ((sw1 == ICC_STATUS_SAT_BUSY) && (sw2 === 0x00)) { michael@0: this.acknowledgeGsmSms(false, PDU_FCS_USAT_BUSY); michael@0: return; michael@0: } michael@0: michael@0: let success = ((sw1 == ICC_STATUS_NORMAL_ENDING) && (sw2 === 0x00)) michael@0: || (sw1 == ICC_STATUS_NORMAL_ENDING_WITH_EXTRA); michael@0: michael@0: let messageStringLength = Buf.readInt32(); // In semi-octets michael@0: let responsePduLen = messageStringLength / 2; // In octets michael@0: if (!responsePduLen) { michael@0: this.acknowledgeGsmSms(success, success ? PDU_FCS_OK michael@0: : PDU_FCS_USIM_DATA_DOWNLOAD_ERROR); michael@0: return; michael@0: } michael@0: michael@0: this.acknowledgeIncomingGsmSmsWithPDU(success, responsePduLen, options); michael@0: }; michael@0: RilObject.prototype[REQUEST_VOICE_RADIO_TECH] = function REQUEST_VOICE_RADIO_TECH(length, options) { michael@0: if (options.rilRequestError) { michael@0: if (DEBUG) { michael@0: this.context.debug("Error when getting voice radio tech: " + michael@0: options.rilRequestError); michael@0: } michael@0: return; michael@0: } michael@0: let radioTech = this.context.Buf.readInt32List(); michael@0: this._processRadioTech(radioTech[0]); michael@0: }; michael@0: RilObject.prototype[REQUEST_GET_UNLOCK_RETRY_COUNT] = function REQUEST_GET_UNLOCK_RETRY_COUNT(length, options) { michael@0: options.success = (options.rilRequestError === 0); michael@0: if (!options.success) { michael@0: options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError]; michael@0: } michael@0: options.retryCount = length ? this.context.Buf.readInt32List()[0] : -1; michael@0: this.sendChromeMessage(options); michael@0: }; michael@0: RilObject.prototype[RIL_REQUEST_GPRS_ATTACH] = null; michael@0: RilObject.prototype[RIL_REQUEST_GPRS_DETACH] = null; michael@0: RilObject.prototype[UNSOLICITED_RESPONSE_RADIO_STATE_CHANGED] = function UNSOLICITED_RESPONSE_RADIO_STATE_CHANGED() { michael@0: let radioState = this.context.Buf.readInt32(); michael@0: let newState; michael@0: if (radioState == RADIO_STATE_UNAVAILABLE) { michael@0: newState = GECKO_RADIOSTATE_UNAVAILABLE; michael@0: } else if (radioState == RADIO_STATE_OFF) { michael@0: newState = GECKO_RADIOSTATE_OFF; michael@0: } else { michael@0: newState = GECKO_RADIOSTATE_READY; michael@0: } michael@0: michael@0: if (DEBUG) { michael@0: this.context.debug("Radio state changed from '" + this.radioState + michael@0: "' to '" + newState + "'"); michael@0: } michael@0: if (this.radioState == newState) { michael@0: return; michael@0: } michael@0: michael@0: switch (radioState) { michael@0: case RADIO_STATE_SIM_READY: michael@0: case RADIO_STATE_SIM_NOT_READY: michael@0: case RADIO_STATE_SIM_LOCKED_OR_ABSENT: michael@0: this._isCdma = false; michael@0: this._waitingRadioTech = false; michael@0: break; michael@0: case RADIO_STATE_RUIM_READY: michael@0: case RADIO_STATE_RUIM_NOT_READY: michael@0: case RADIO_STATE_RUIM_LOCKED_OR_ABSENT: michael@0: case RADIO_STATE_NV_READY: michael@0: case RADIO_STATE_NV_NOT_READY: michael@0: this._isCdma = true; michael@0: this._waitingRadioTech = false; michael@0: break; michael@0: case RADIO_STATE_ON: // RIL v7 michael@0: // This value is defined in RIL v7, we will retrieve radio tech by another michael@0: // request. We leave _isCdma untouched, and it will be set once we get the michael@0: // radio technology. michael@0: this._waitingRadioTech = true; michael@0: this.getVoiceRadioTechnology(); michael@0: break; michael@0: } michael@0: michael@0: if ((this.radioState == GECKO_RADIOSTATE_UNAVAILABLE || michael@0: this.radioState == GECKO_RADIOSTATE_OFF) && michael@0: newState == GECKO_RADIOSTATE_READY) { michael@0: // The radio became available, let's get its info. michael@0: if (!this._waitingRadioTech) { michael@0: if (this._isCdma) { michael@0: this.getDeviceIdentity(); michael@0: } else { michael@0: this.getIMEI(); michael@0: this.getIMEISV(); michael@0: } michael@0: } michael@0: this.getBasebandVersion(); michael@0: this.updateCellBroadcastConfig(); michael@0: this.setPreferredNetworkType(); michael@0: this.setCLIR(); michael@0: if (RILQUIRKS_DATA_REGISTRATION_ON_DEMAND && this._attachDataRegistration) { michael@0: this.setDataRegistration({attach: true}); michael@0: } michael@0: } michael@0: michael@0: this.radioState = newState; michael@0: this.sendChromeMessage({ michael@0: rilMessageType: "radiostatechange", michael@0: radioState: newState michael@0: }); michael@0: michael@0: // If the radio is up and on, so let's query the card state. michael@0: // On older RILs only if the card is actually ready, though. michael@0: // If _waitingRadioTech is set, we don't need to get icc status now. michael@0: if (radioState == RADIO_STATE_UNAVAILABLE || michael@0: radioState == RADIO_STATE_OFF || michael@0: this._waitingRadioTech) { michael@0: return; michael@0: } michael@0: this.getICCStatus(); michael@0: }; michael@0: RilObject.prototype[UNSOLICITED_RESPONSE_CALL_STATE_CHANGED] = function UNSOLICITED_RESPONSE_CALL_STATE_CHANGED() { michael@0: this.getCurrentCalls(); michael@0: }; michael@0: RilObject.prototype[UNSOLICITED_RESPONSE_VOICE_NETWORK_STATE_CHANGED] = function UNSOLICITED_RESPONSE_VOICE_NETWORK_STATE_CHANGED() { michael@0: if (DEBUG) { michael@0: this.context.debug("Network state changed, re-requesting phone state and " + michael@0: "ICC status"); michael@0: } michael@0: this.getICCStatus(); michael@0: this.requestNetworkInfo(); michael@0: }; michael@0: RilObject.prototype[UNSOLICITED_RESPONSE_NEW_SMS] = function UNSOLICITED_RESPONSE_NEW_SMS(length) { michael@0: let [message, result] = this.context.GsmPDUHelper.processReceivedSms(length); michael@0: michael@0: if (message) { michael@0: result = this._processSmsMultipart(message); michael@0: } michael@0: michael@0: if (result == PDU_FCS_RESERVED || result == MOZ_FCS_WAIT_FOR_EXPLICIT_ACK) { michael@0: return; michael@0: } michael@0: michael@0: // Not reserved FCS values, send ACK now. michael@0: this.acknowledgeGsmSms(result == PDU_FCS_OK, result); michael@0: }; michael@0: RilObject.prototype[UNSOLICITED_RESPONSE_NEW_SMS_STATUS_REPORT] = function UNSOLICITED_RESPONSE_NEW_SMS_STATUS_REPORT(length) { michael@0: let result = this._processSmsStatusReport(length); michael@0: this.acknowledgeGsmSms(result == PDU_FCS_OK, result); michael@0: }; michael@0: RilObject.prototype[UNSOLICITED_RESPONSE_NEW_SMS_ON_SIM] = function UNSOLICITED_RESPONSE_NEW_SMS_ON_SIM(length) { michael@0: let recordNumber = this.context.Buf.readInt32List()[0]; michael@0: michael@0: this.context.SimRecordHelper.readSMS( michael@0: recordNumber, michael@0: function onsuccess(message) { michael@0: if (message && message.simStatus === 3) { //New Unread SMS michael@0: this._processSmsMultipart(message); michael@0: } michael@0: }.bind(this), michael@0: function onerror(errorMsg) { michael@0: if (DEBUG) { michael@0: this.context.debug("Failed to Read NEW SMS on SIM #" + recordNumber + michael@0: ", errorMsg: " + errorMsg); michael@0: } michael@0: }); michael@0: }; michael@0: RilObject.prototype[UNSOLICITED_ON_USSD] = function UNSOLICITED_ON_USSD() { michael@0: let [typeCode, message] = this.context.Buf.readStringList(); michael@0: if (DEBUG) { michael@0: this.context.debug("On USSD. Type Code: " + typeCode + " Message: " + message); michael@0: } michael@0: michael@0: this._ussdSession = (typeCode != "0" && typeCode != "2"); michael@0: michael@0: this.sendChromeMessage({rilMessageType: "USSDReceived", michael@0: message: message, michael@0: sessionEnded: !this._ussdSession}); michael@0: }; michael@0: RilObject.prototype[UNSOLICITED_NITZ_TIME_RECEIVED] = function UNSOLICITED_NITZ_TIME_RECEIVED() { michael@0: let dateString = this.context.Buf.readString(); michael@0: michael@0: // The data contained in the NITZ message is michael@0: // in the form "yy/mm/dd,hh:mm:ss(+/-)tz,dt" michael@0: // for example: 12/02/16,03:36:08-20,00,310410 michael@0: // See also bug 714352 - Listen for NITZ updates from rild. michael@0: michael@0: if (DEBUG) this.context.debug("DateTimeZone string " + dateString); michael@0: michael@0: let now = Date.now(); michael@0: michael@0: let year = parseInt(dateString.substr(0, 2), 10); michael@0: let month = parseInt(dateString.substr(3, 2), 10); michael@0: let day = parseInt(dateString.substr(6, 2), 10); michael@0: let hours = parseInt(dateString.substr(9, 2), 10); michael@0: let minutes = parseInt(dateString.substr(12, 2), 10); michael@0: let seconds = parseInt(dateString.substr(15, 2), 10); michael@0: // Note that |tz| is in 15-min units. michael@0: let tz = parseInt(dateString.substr(17, 3), 10); michael@0: // Note that |dst| is in 1-hour units and is already applied in |tz|. michael@0: let dst = parseInt(dateString.substr(21, 2), 10); michael@0: michael@0: let timeInMS = Date.UTC(year + PDU_TIMESTAMP_YEAR_OFFSET, month - 1, day, michael@0: hours, minutes, seconds); michael@0: michael@0: if (isNaN(timeInMS)) { michael@0: if (DEBUG) this.context.debug("NITZ failed to convert date"); michael@0: return; michael@0: } michael@0: michael@0: this.sendChromeMessage({rilMessageType: "nitzTime", michael@0: networkTimeInMS: timeInMS, michael@0: networkTimeZoneInMinutes: -(tz * 15), michael@0: networkDSTInMinutes: -(dst * 60), michael@0: receiveTimeInMS: now}); michael@0: }; michael@0: michael@0: RilObject.prototype[UNSOLICITED_SIGNAL_STRENGTH] = function UNSOLICITED_SIGNAL_STRENGTH(length) { michael@0: this[REQUEST_SIGNAL_STRENGTH](length, {rilRequestError: ERROR_SUCCESS}); michael@0: }; michael@0: RilObject.prototype[UNSOLICITED_DATA_CALL_LIST_CHANGED] = function UNSOLICITED_DATA_CALL_LIST_CHANGED(length) { michael@0: if (this.v5Legacy) { michael@0: this.getDataCallList(); michael@0: return; michael@0: } michael@0: this[REQUEST_DATA_CALL_LIST](length, {rilRequestError: ERROR_SUCCESS}); michael@0: }; michael@0: RilObject.prototype[UNSOLICITED_SUPP_SVC_NOTIFICATION] = function UNSOLICITED_SUPP_SVC_NOTIFICATION(length) { michael@0: let Buf = this.context.Buf; michael@0: let info = {}; michael@0: info.notificationType = Buf.readInt32(); michael@0: info.code = Buf.readInt32(); michael@0: info.index = Buf.readInt32(); michael@0: info.type = Buf.readInt32(); michael@0: info.number = Buf.readString(); michael@0: michael@0: this._processSuppSvcNotification(info); michael@0: }; michael@0: michael@0: RilObject.prototype[UNSOLICITED_STK_SESSION_END] = function UNSOLICITED_STK_SESSION_END() { michael@0: this.sendChromeMessage({rilMessageType: "stksessionend"}); michael@0: }; michael@0: RilObject.prototype[UNSOLICITED_STK_PROACTIVE_COMMAND] = function UNSOLICITED_STK_PROACTIVE_COMMAND() { michael@0: this.processStkProactiveCommand(); michael@0: }; michael@0: RilObject.prototype[UNSOLICITED_STK_EVENT_NOTIFY] = function UNSOLICITED_STK_EVENT_NOTIFY() { michael@0: this.processStkProactiveCommand(); michael@0: }; michael@0: RilObject.prototype[UNSOLICITED_STK_CALL_SETUP] = null; michael@0: RilObject.prototype[UNSOLICITED_SIM_SMS_STORAGE_FULL] = null; michael@0: RilObject.prototype[UNSOLICITED_SIM_REFRESH] = null; michael@0: RilObject.prototype[UNSOLICITED_CALL_RING] = function UNSOLICITED_CALL_RING() { michael@0: let Buf = this.context.Buf; michael@0: let info = {rilMessageType: "callRing"}; michael@0: let isCDMA = false; //XXX TODO hard-code this for now michael@0: if (isCDMA) { michael@0: info.isPresent = Buf.readInt32(); michael@0: info.signalType = Buf.readInt32(); michael@0: info.alertPitch = Buf.readInt32(); michael@0: info.signal = Buf.readInt32(); michael@0: } michael@0: // At this point we don't know much other than the fact there's an incoming michael@0: // call, but that's enough to bring up the Phone app already. We'll know michael@0: // details once we get a call state changed notification and can then michael@0: // dispatch DOM events etc. michael@0: this.sendChromeMessage(info); michael@0: }; michael@0: RilObject.prototype[UNSOLICITED_RESPONSE_SIM_STATUS_CHANGED] = function UNSOLICITED_RESPONSE_SIM_STATUS_CHANGED() { michael@0: this.getICCStatus(); michael@0: }; michael@0: RilObject.prototype[UNSOLICITED_RESPONSE_CDMA_NEW_SMS] = function UNSOLICITED_RESPONSE_CDMA_NEW_SMS(length) { michael@0: let [message, result] = this.context.CdmaPDUHelper.processReceivedSms(length); michael@0: michael@0: if (message) { michael@0: if (message.teleservice === PDU_CDMA_MSG_TELESERIVCIE_ID_WAP) { michael@0: result = this._processCdmaSmsWapPush(message); michael@0: } else if (message.subMsgType === PDU_CDMA_MSG_TYPE_DELIVER_ACK) { michael@0: result = this._processCdmaSmsStatusReport(message); michael@0: } else { michael@0: result = this._processSmsMultipart(message); michael@0: } michael@0: } michael@0: michael@0: if (result == PDU_FCS_RESERVED || result == MOZ_FCS_WAIT_FOR_EXPLICIT_ACK) { michael@0: return; michael@0: } michael@0: michael@0: // Not reserved FCS values, send ACK now. michael@0: this.acknowledgeCdmaSms(result == PDU_FCS_OK, result); michael@0: }; michael@0: RilObject.prototype[UNSOLICITED_RESPONSE_NEW_BROADCAST_SMS] = function UNSOLICITED_RESPONSE_NEW_BROADCAST_SMS(length) { michael@0: let message; michael@0: try { michael@0: message = michael@0: this.context.GsmPDUHelper.readCbMessage(this.context.Buf.readInt32()); michael@0: } catch (e) { michael@0: if (DEBUG) { michael@0: this.context.debug("Failed to parse Cell Broadcast message: " + michael@0: JSON.stringify(e)); michael@0: } michael@0: return; michael@0: } michael@0: michael@0: message = this._processReceivedSmsCbPage(message); michael@0: if (!message) { michael@0: return; michael@0: } michael@0: michael@0: message.rilMessageType = "cellbroadcast-received"; michael@0: this.sendChromeMessage(message); michael@0: }; michael@0: RilObject.prototype[UNSOLICITED_CDMA_RUIM_SMS_STORAGE_FULL] = null; michael@0: RilObject.prototype[UNSOLICITED_RESTRICTED_STATE_CHANGED] = null; michael@0: RilObject.prototype[UNSOLICITED_ENTER_EMERGENCY_CALLBACK_MODE] = function UNSOLICITED_ENTER_EMERGENCY_CALLBACK_MODE() { michael@0: this._handleChangedEmergencyCbMode(true); michael@0: }; michael@0: RilObject.prototype[UNSOLICITED_CDMA_CALL_WAITING] = function UNSOLICITED_CDMA_CALL_WAITING(length) { michael@0: let Buf = this.context.Buf; michael@0: let call = {}; michael@0: call.number = Buf.readString(); michael@0: call.numberPresentation = Buf.readInt32(); michael@0: call.name = Buf.readString(); michael@0: call.namePresentation = Buf.readInt32(); michael@0: call.isPresent = Buf.readInt32(); michael@0: call.signalType = Buf.readInt32(); michael@0: call.alertPitch = Buf.readInt32(); michael@0: call.signal = Buf.readInt32(); michael@0: this.sendChromeMessage({rilMessageType: "cdmaCallWaiting", michael@0: number: call.number}); michael@0: }; michael@0: RilObject.prototype[UNSOLICITED_CDMA_OTA_PROVISION_STATUS] = function UNSOLICITED_CDMA_OTA_PROVISION_STATUS() { michael@0: let status = this.context.Buf.readInt32List()[0]; michael@0: this.sendChromeMessage({rilMessageType: "otastatuschange", michael@0: status: status}); michael@0: }; michael@0: RilObject.prototype[UNSOLICITED_CDMA_INFO_REC] = function UNSOLICITED_CDMA_INFO_REC(length) { michael@0: let record = this.context.CdmaPDUHelper.decodeInformationRecord(); michael@0: record.rilMessageType = "cdma-info-rec-received"; michael@0: this.sendChromeMessage(record); michael@0: }; michael@0: RilObject.prototype[UNSOLICITED_OEM_HOOK_RAW] = null; michael@0: RilObject.prototype[UNSOLICITED_RINGBACK_TONE] = null; michael@0: RilObject.prototype[UNSOLICITED_RESEND_INCALL_MUTE] = null; michael@0: RilObject.prototype[UNSOLICITED_CDMA_SUBSCRIPTION_SOURCE_CHANGED] = null; michael@0: RilObject.prototype[UNSOLICITED_CDMA_PRL_CHANGED] = function UNSOLICITED_CDMA_PRL_CHANGED(length) { michael@0: let version = this.context.Buf.readInt32List()[0]; michael@0: if (version !== this.iccInfo.prlVersion) { michael@0: this.iccInfo.prlVersion = version; michael@0: this.context.ICCUtilsHelper.handleICCInfoChange(); michael@0: } michael@0: }; michael@0: RilObject.prototype[UNSOLICITED_EXIT_EMERGENCY_CALLBACK_MODE] = function UNSOLICITED_EXIT_EMERGENCY_CALLBACK_MODE() { michael@0: this._handleChangedEmergencyCbMode(false); michael@0: }; michael@0: RilObject.prototype[UNSOLICITED_RIL_CONNECTED] = function UNSOLICITED_RIL_CONNECTED(length) { michael@0: // Prevent response id collision between UNSOLICITED_RIL_CONNECTED and michael@0: // UNSOLICITED_VOICE_RADIO_TECH_CHANGED for Akami on gingerbread branch. michael@0: if (!length) { michael@0: return; michael@0: } michael@0: michael@0: let version = this.context.Buf.readInt32List()[0]; michael@0: this.v5Legacy = (version < 5); michael@0: if (DEBUG) { michael@0: this.context.debug("Detected RIL version " + version); michael@0: this.context.debug("this.v5Legacy is " + this.v5Legacy); michael@0: } michael@0: michael@0: this.initRILState(); michael@0: // Always ensure that we are not in emergency callback mode when init. michael@0: this.exitEmergencyCbMode(); michael@0: // Reset radio in the case that b2g restart (or crash). michael@0: this.setRadioEnabled({enabled: false}); michael@0: }; michael@0: michael@0: /** michael@0: * This object exposes the functionality to parse and serialize PDU strings michael@0: * michael@0: * A PDU is a string containing a series of hexadecimally encoded octets michael@0: * or nibble-swapped binary-coded decimals (BCDs). It contains not only the michael@0: * message text but information about the sender, the SMS service center, michael@0: * timestamp, etc. michael@0: */ michael@0: function GsmPDUHelperObject(aContext) { michael@0: this.context = aContext; michael@0: } michael@0: GsmPDUHelperObject.prototype = { michael@0: context: null, michael@0: michael@0: /** michael@0: * Read one character (2 bytes) from a RIL string and decode as hex. michael@0: * michael@0: * @return the nibble as a number. michael@0: */ michael@0: readHexNibble: function() { michael@0: let nibble = this.context.Buf.readUint16(); michael@0: if (nibble >= 48 && nibble <= 57) { michael@0: nibble -= 48; // ASCII '0'..'9' michael@0: } else if (nibble >= 65 && nibble <= 70) { michael@0: nibble -= 55; // ASCII 'A'..'F' michael@0: } else if (nibble >= 97 && nibble <= 102) { michael@0: nibble -= 87; // ASCII 'a'..'f' michael@0: } else { michael@0: throw "Found invalid nibble during PDU parsing: " + michael@0: String.fromCharCode(nibble); michael@0: } michael@0: return nibble; michael@0: }, michael@0: michael@0: /** michael@0: * Encode a nibble as one hex character in a RIL string (2 bytes). michael@0: * michael@0: * @param nibble michael@0: * The nibble to encode (represented as a number) michael@0: */ michael@0: writeHexNibble: function(nibble) { michael@0: nibble &= 0x0f; michael@0: if (nibble < 10) { michael@0: nibble += 48; // ASCII '0' michael@0: } else { michael@0: nibble += 55; // ASCII 'A' michael@0: } michael@0: this.context.Buf.writeUint16(nibble); michael@0: }, michael@0: michael@0: /** michael@0: * Read a hex-encoded octet (two nibbles). michael@0: * michael@0: * @return the octet as a number. michael@0: */ michael@0: readHexOctet: function() { michael@0: return (this.readHexNibble() << 4) | this.readHexNibble(); michael@0: }, michael@0: michael@0: /** michael@0: * Write an octet as two hex-encoded nibbles. michael@0: * michael@0: * @param octet michael@0: * The octet (represented as a number) to encode. michael@0: */ michael@0: writeHexOctet: function(octet) { michael@0: this.writeHexNibble(octet >> 4); michael@0: this.writeHexNibble(octet); michael@0: }, michael@0: michael@0: /** michael@0: * Read an array of hex-encoded octets. michael@0: */ michael@0: readHexOctetArray: function(length) { michael@0: let array = new Uint8Array(length); michael@0: for (let i = 0; i < length; i++) { michael@0: array[i] = this.readHexOctet(); michael@0: } michael@0: return array; michael@0: }, michael@0: michael@0: /** michael@0: * Convert an octet (number) to a BCD number. michael@0: * michael@0: * Any nibbles that are not in the BCD range count as 0. michael@0: * michael@0: * @param octet michael@0: * The octet (a number, as returned by getOctet()) michael@0: * michael@0: * @return the corresponding BCD number. michael@0: */ michael@0: octetToBCD: function(octet) { michael@0: return ((octet & 0xf0) <= 0x90) * ((octet >> 4) & 0x0f) + michael@0: ((octet & 0x0f) <= 0x09) * (octet & 0x0f) * 10; michael@0: }, michael@0: michael@0: /** michael@0: * Convert a BCD number to an octet (number) michael@0: * michael@0: * Only take two digits with absolute value. michael@0: * michael@0: * @param bcd michael@0: * michael@0: * @return the corresponding octet. michael@0: */ michael@0: BCDToOctet: function(bcd) { michael@0: bcd = Math.abs(bcd); michael@0: return ((bcd % 10) << 4) + (Math.floor(bcd / 10) % 10); michael@0: }, michael@0: michael@0: /** michael@0: * Convert a semi-octet (number) to a GSM BCD char, or return empty string michael@0: * if invalid semiOctet and supressException is set to true. michael@0: * michael@0: * @param semiOctet michael@0: * Nibble to be converted to. michael@0: * @param [optional] supressException michael@0: * Supress exception if invalid semiOctet and supressException is set michael@0: * to true. michael@0: * michael@0: * @return GSM BCD char, or empty string. michael@0: */ michael@0: bcdChars: "0123456789*#,;", michael@0: semiOctetToBcdChar: function(semiOctet, supressException) { michael@0: if (semiOctet >= 14) { michael@0: if (supressException) { michael@0: return ""; michael@0: } else { michael@0: throw new RangeError(); michael@0: } michael@0: } michael@0: michael@0: return this.bcdChars.charAt(semiOctet); michael@0: }, michael@0: michael@0: /** michael@0: * Read a *swapped nibble* binary coded decimal (BCD) michael@0: * michael@0: * @param pairs michael@0: * Number of nibble *pairs* to read. michael@0: * michael@0: * @return the decimal as a number. michael@0: */ michael@0: readSwappedNibbleBcdNum: function(pairs) { michael@0: let number = 0; michael@0: for (let i = 0; i < pairs; i++) { michael@0: let octet = this.readHexOctet(); michael@0: // Ignore 'ff' octets as they're often used as filler. michael@0: if (octet == 0xff) { michael@0: continue; michael@0: } michael@0: // If the first nibble is an "F" , only the second nibble is to be taken michael@0: // into account. michael@0: if ((octet & 0xf0) == 0xf0) { michael@0: number *= 10; michael@0: number += octet & 0x0f; michael@0: continue; michael@0: } michael@0: number *= 100; michael@0: number += this.octetToBCD(octet); michael@0: } michael@0: return number; michael@0: }, michael@0: michael@0: /** michael@0: * Read a *swapped nibble* binary coded string (BCD) michael@0: * michael@0: * @param pairs michael@0: * Number of nibble *pairs* to read. michael@0: * @param [optional] supressException michael@0: * Supress exception if invalid semiOctet and supressException is set michael@0: * to true. michael@0: * michael@0: * @return The BCD string. michael@0: */ michael@0: readSwappedNibbleBcdString: function(pairs, supressException) { michael@0: let str = ""; michael@0: for (let i = 0; i < pairs; i++) { michael@0: let nibbleH = this.readHexNibble(); michael@0: let nibbleL = this.readHexNibble(); michael@0: if (nibbleL == 0x0F) { michael@0: break; michael@0: } michael@0: michael@0: str += this.semiOctetToBcdChar(nibbleL, supressException); michael@0: if (nibbleH != 0x0F) { michael@0: str += this.semiOctetToBcdChar(nibbleH, supressException); michael@0: } michael@0: } michael@0: michael@0: return str; michael@0: }, michael@0: michael@0: /** michael@0: * Write numerical data as swapped nibble BCD. michael@0: * michael@0: * @param data michael@0: * Data to write (as a string or a number) michael@0: */ michael@0: writeSwappedNibbleBCD: function(data) { michael@0: data = data.toString(); michael@0: if (data.length % 2) { michael@0: data += "F"; michael@0: } michael@0: let Buf = this.context.Buf; michael@0: for (let i = 0; i < data.length; i += 2) { michael@0: Buf.writeUint16(data.charCodeAt(i + 1)); michael@0: Buf.writeUint16(data.charCodeAt(i)); michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Write numerical data as swapped nibble BCD. michael@0: * If the number of digit of data is even, add '0' at the beginning. michael@0: * michael@0: * @param data michael@0: * Data to write (as a string or a number) michael@0: */ michael@0: writeSwappedNibbleBCDNum: function(data) { michael@0: data = data.toString(); michael@0: if (data.length % 2) { michael@0: data = "0" + data; michael@0: } michael@0: let Buf = this.context.Buf; michael@0: for (let i = 0; i < data.length; i += 2) { michael@0: Buf.writeUint16(data.charCodeAt(i + 1)); michael@0: Buf.writeUint16(data.charCodeAt(i)); michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Read user data, convert to septets, look up relevant characters in a michael@0: * 7-bit alphabet, and construct string. michael@0: * michael@0: * @param length michael@0: * Number of septets to read (*not* octets) michael@0: * @param paddingBits michael@0: * Number of padding bits in the first byte of user data. michael@0: * @param langIndex michael@0: * Table index used for normal 7-bit encoded character lookup. michael@0: * @param langShiftIndex michael@0: * Table index used for escaped 7-bit encoded character lookup. michael@0: * michael@0: * @return a string. michael@0: */ michael@0: readSeptetsToString: function(length, paddingBits, langIndex, langShiftIndex) { michael@0: let ret = ""; michael@0: let byteLength = Math.ceil((length * 7 + paddingBits) / 8); michael@0: michael@0: /** michael@0: * |<- last byte in header ->| michael@0: * |<- incompleteBits ->|<- last header septet->| michael@0: * +===7===|===6===|===5===|===4===|===3===|===2===|===1===|===0===| michael@0: * michael@0: * |<- 1st byte in user data ->| michael@0: * |<- data septet 1 ->|<-paddingBits->| michael@0: * +===7===|===6===|===5===|===4===|===3===|===2===|===1===|===0===| michael@0: * michael@0: * |<- 2nd byte in user data ->| michael@0: * |<- data spetet 2 ->|<-ds1->| michael@0: * +===7===|===6===|===5===|===4===|===3===|===2===|===1===|===0===| michael@0: */ michael@0: let data = 0; michael@0: let dataBits = 0; michael@0: if (paddingBits) { michael@0: data = this.readHexOctet() >> paddingBits; michael@0: dataBits = 8 - paddingBits; michael@0: --byteLength; michael@0: } michael@0: michael@0: let escapeFound = false; michael@0: const langTable = PDU_NL_LOCKING_SHIFT_TABLES[langIndex]; michael@0: const langShiftTable = PDU_NL_SINGLE_SHIFT_TABLES[langShiftIndex]; michael@0: do { michael@0: // Read as much as fits in 32bit word michael@0: let bytesToRead = Math.min(byteLength, dataBits ? 3 : 4); michael@0: for (let i = 0; i < bytesToRead; i++) { michael@0: data |= this.readHexOctet() << dataBits; michael@0: dataBits += 8; michael@0: --byteLength; michael@0: } michael@0: michael@0: // Consume available full septets michael@0: for (; dataBits >= 7; dataBits -= 7) { michael@0: let septet = data & 0x7F; michael@0: data >>>= 7; michael@0: michael@0: if (escapeFound) { michael@0: escapeFound = false; michael@0: if (septet == PDU_NL_EXTENDED_ESCAPE) { michael@0: // According to 3GPP TS 23.038, section 6.2.1.1, NOTE 1, "On michael@0: // receipt of this code, a receiving entity shall display a space michael@0: // until another extensiion table is defined." michael@0: ret += " "; michael@0: } else if (septet == PDU_NL_RESERVED_CONTROL) { michael@0: // According to 3GPP TS 23.038 B.2, "This code represents a control michael@0: // character and therefore must not be used for language specific michael@0: // characters." michael@0: ret += " "; michael@0: } else { michael@0: ret += langShiftTable[septet]; michael@0: } michael@0: } else if (septet == PDU_NL_EXTENDED_ESCAPE) { michael@0: escapeFound = true; michael@0: michael@0: // is not an effective character michael@0: --length; michael@0: } else { michael@0: ret += langTable[septet]; michael@0: } michael@0: } michael@0: } while (byteLength); michael@0: michael@0: if (ret.length != length) { michael@0: /** michael@0: * If num of effective characters does not equal to the length of read michael@0: * string, cut the tail off. This happens when the last octet of user michael@0: * data has following layout: michael@0: * michael@0: * |<- penultimate octet in user data ->| michael@0: * |<- data septet N ->|<- dsN-1 ->| michael@0: * +===7===|===6===|===5===|===4===|===3===|===2===|===1===|===0===| michael@0: * michael@0: * |<- last octet in user data ->| michael@0: * |<- fill bits ->|<-dsN->| michael@0: * +===7===|===6===|===5===|===4===|===3===|===2===|===1===|===0===| michael@0: * michael@0: * The fill bits in the last octet may happen to form a full septet and michael@0: * be appended at the end of result string. michael@0: */ michael@0: ret = ret.slice(0, length); michael@0: } michael@0: return ret; michael@0: }, michael@0: michael@0: writeStringAsSeptets: function(message, paddingBits, langIndex, langShiftIndex) { michael@0: const langTable = PDU_NL_LOCKING_SHIFT_TABLES[langIndex]; michael@0: const langShiftTable = PDU_NL_SINGLE_SHIFT_TABLES[langShiftIndex]; michael@0: michael@0: let dataBits = paddingBits; michael@0: let data = 0; michael@0: for (let i = 0; i < message.length; i++) { michael@0: let c = message.charAt(i); michael@0: let septet = langTable.indexOf(c); michael@0: if (septet == PDU_NL_EXTENDED_ESCAPE) { michael@0: continue; michael@0: } michael@0: michael@0: if (septet >= 0) { michael@0: data |= septet << dataBits; michael@0: dataBits += 7; michael@0: } else { michael@0: septet = langShiftTable.indexOf(c); michael@0: if (septet == -1) { michael@0: throw new Error("'" + c + "' is not in 7 bit alphabet " michael@0: + langIndex + ":" + langShiftIndex + "!"); michael@0: } michael@0: michael@0: if (septet == PDU_NL_RESERVED_CONTROL) { michael@0: continue; michael@0: } michael@0: michael@0: data |= PDU_NL_EXTENDED_ESCAPE << dataBits; michael@0: dataBits += 7; michael@0: data |= septet << dataBits; michael@0: dataBits += 7; michael@0: } michael@0: michael@0: for (; dataBits >= 8; dataBits -= 8) { michael@0: this.writeHexOctet(data & 0xFF); michael@0: data >>>= 8; michael@0: } michael@0: } michael@0: michael@0: if (dataBits !== 0) { michael@0: this.writeHexOctet(data & 0xFF); michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Read user data and decode as a UCS2 string. michael@0: * michael@0: * @param numOctets michael@0: * Number of octets to be read as UCS2 string. michael@0: * michael@0: * @return a string. michael@0: */ michael@0: readUCS2String: function(numOctets) { michael@0: let str = ""; michael@0: let length = numOctets / 2; michael@0: for (let i = 0; i < length; ++i) { michael@0: let code = (this.readHexOctet() << 8) | this.readHexOctet(); michael@0: str += String.fromCharCode(code); michael@0: } michael@0: michael@0: if (DEBUG) this.context.debug("Read UCS2 string: " + str); michael@0: michael@0: return str; michael@0: }, michael@0: michael@0: /** michael@0: * Write user data as a UCS2 string. michael@0: * michael@0: * @param message michael@0: * Message string to encode as UCS2 in hex-encoded octets. michael@0: */ michael@0: writeUCS2String: function(message) { michael@0: for (let i = 0; i < message.length; ++i) { michael@0: let code = message.charCodeAt(i); michael@0: this.writeHexOctet((code >> 8) & 0xFF); michael@0: this.writeHexOctet(code & 0xFF); michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Read 1 + UDHL octets and construct user data header. michael@0: * michael@0: * @param msg michael@0: * message object for output. michael@0: * michael@0: * @see 3GPP TS 23.040 9.2.3.24 michael@0: */ michael@0: readUserDataHeader: function(msg) { michael@0: /** michael@0: * A header object with properties contained in received message. michael@0: * The properties set include: michael@0: * michael@0: * length: totoal length of the header, default 0. michael@0: * langIndex: used locking shift table index, default michael@0: * PDU_NL_IDENTIFIER_DEFAULT. michael@0: * langShiftIndex: used locking shift table index, default michael@0: * PDU_NL_IDENTIFIER_DEFAULT. michael@0: * michael@0: */ michael@0: let header = { michael@0: length: 0, michael@0: langIndex: PDU_NL_IDENTIFIER_DEFAULT, michael@0: langShiftIndex: PDU_NL_IDENTIFIER_DEFAULT michael@0: }; michael@0: michael@0: header.length = this.readHexOctet(); michael@0: if (DEBUG) this.context.debug("Read UDH length: " + header.length); michael@0: michael@0: let dataAvailable = header.length; michael@0: while (dataAvailable >= 2) { michael@0: let id = this.readHexOctet(); michael@0: let length = this.readHexOctet(); michael@0: if (DEBUG) this.context.debug("Read UDH id: " + id + ", length: " + length); michael@0: michael@0: dataAvailable -= 2; michael@0: michael@0: switch (id) { michael@0: case PDU_IEI_CONCATENATED_SHORT_MESSAGES_8BIT: { michael@0: let ref = this.readHexOctet(); michael@0: let max = this.readHexOctet(); michael@0: let seq = this.readHexOctet(); michael@0: dataAvailable -= 3; michael@0: if (max && seq && (seq <= max)) { michael@0: header.segmentRef = ref; michael@0: header.segmentMaxSeq = max; michael@0: header.segmentSeq = seq; michael@0: } michael@0: break; michael@0: } michael@0: case PDU_IEI_APPLICATION_PORT_ADDRESSING_SCHEME_8BIT: { michael@0: let dstp = this.readHexOctet(); michael@0: let orip = this.readHexOctet(); michael@0: dataAvailable -= 2; michael@0: if ((dstp < PDU_APA_RESERVED_8BIT_PORTS) michael@0: || (orip < PDU_APA_RESERVED_8BIT_PORTS)) { michael@0: // 3GPP TS 23.040 clause 9.2.3.24.3: "A receiving entity shall michael@0: // ignore any information element where the value of the michael@0: // Information-Element-Data is Reserved or not supported" michael@0: break; michael@0: } michael@0: header.destinationPort = dstp; michael@0: header.originatorPort = orip; michael@0: break; michael@0: } michael@0: case PDU_IEI_APPLICATION_PORT_ADDRESSING_SCHEME_16BIT: { michael@0: let dstp = (this.readHexOctet() << 8) | this.readHexOctet(); michael@0: let orip = (this.readHexOctet() << 8) | this.readHexOctet(); michael@0: dataAvailable -= 4; michael@0: // 3GPP TS 23.040 clause 9.2.3.24.4: "A receiving entity shall michael@0: // ignore any information element where the value of the michael@0: // Information-Element-Data is Reserved or not supported" michael@0: if ((dstp < PDU_APA_VALID_16BIT_PORTS) michael@0: && (orip < PDU_APA_VALID_16BIT_PORTS)) { michael@0: header.destinationPort = dstp; michael@0: header.originatorPort = orip; michael@0: } michael@0: break; michael@0: } michael@0: case PDU_IEI_CONCATENATED_SHORT_MESSAGES_16BIT: { michael@0: let ref = (this.readHexOctet() << 8) | this.readHexOctet(); michael@0: let max = this.readHexOctet(); michael@0: let seq = this.readHexOctet(); michael@0: dataAvailable -= 4; michael@0: if (max && seq && (seq <= max)) { michael@0: header.segmentRef = ref; michael@0: header.segmentMaxSeq = max; michael@0: header.segmentSeq = seq; michael@0: } michael@0: break; michael@0: } michael@0: case PDU_IEI_NATIONAL_LANGUAGE_SINGLE_SHIFT: michael@0: let langShiftIndex = this.readHexOctet(); michael@0: --dataAvailable; michael@0: if (langShiftIndex < PDU_NL_SINGLE_SHIFT_TABLES.length) { michael@0: header.langShiftIndex = langShiftIndex; michael@0: } michael@0: break; michael@0: case PDU_IEI_NATIONAL_LANGUAGE_LOCKING_SHIFT: michael@0: let langIndex = this.readHexOctet(); michael@0: --dataAvailable; michael@0: if (langIndex < PDU_NL_LOCKING_SHIFT_TABLES.length) { michael@0: header.langIndex = langIndex; michael@0: } michael@0: break; michael@0: case PDU_IEI_SPECIAL_SMS_MESSAGE_INDICATION: michael@0: let msgInd = this.readHexOctet() & 0xFF; michael@0: let msgCount = this.readHexOctet(); michael@0: dataAvailable -= 2; michael@0: michael@0: michael@0: /* michael@0: * TS 23.040 V6.8.1 Sec 9.2.3.24.2 michael@0: * bits 1 0 : basic message indication type michael@0: * bits 4 3 2 : extended message indication type michael@0: * bits 6 5 : Profile id michael@0: * bit 7 : storage type michael@0: */ michael@0: let storeType = msgInd & PDU_MWI_STORE_TYPE_BIT; michael@0: let mwi = msg.mwi; michael@0: if (!mwi) { michael@0: mwi = msg.mwi = {}; michael@0: } michael@0: michael@0: if (storeType == PDU_MWI_STORE_TYPE_STORE) { michael@0: // Store message because TP_UDH indicates so, note this may override michael@0: // the setting in DCS, but that is expected michael@0: mwi.discard = false; michael@0: } else if (mwi.discard === undefined) { michael@0: // storeType == PDU_MWI_STORE_TYPE_DISCARD michael@0: // only override mwi.discard here if it hasn't already been set michael@0: mwi.discard = true; michael@0: } michael@0: michael@0: mwi.msgCount = msgCount & 0xFF; michael@0: mwi.active = mwi.msgCount > 0; michael@0: michael@0: if (DEBUG) { michael@0: this.context.debug("MWI in TP_UDH received: " + JSON.stringify(mwi)); michael@0: } michael@0: michael@0: break; michael@0: default: michael@0: if (DEBUG) { michael@0: this.context.debug("readUserDataHeader: unsupported IEI(" + id + michael@0: "), " + length + " bytes."); michael@0: } michael@0: michael@0: // Read out unsupported data michael@0: if (length) { michael@0: let octets; michael@0: if (DEBUG) octets = new Uint8Array(length); michael@0: michael@0: for (let i = 0; i < length; i++) { michael@0: let octet = this.readHexOctet(); michael@0: if (DEBUG) octets[i] = octet; michael@0: } michael@0: dataAvailable -= length; michael@0: michael@0: if (DEBUG) { michael@0: this.context.debug("readUserDataHeader: " + Array.slice(octets)); michael@0: } michael@0: } michael@0: break; michael@0: } michael@0: } michael@0: michael@0: if (dataAvailable !== 0) { michael@0: throw new Error("Illegal user data header found!"); michael@0: } michael@0: michael@0: msg.header = header; michael@0: }, michael@0: michael@0: /** michael@0: * Write out user data header. michael@0: * michael@0: * @param options michael@0: * Options containing information for user data header write-out. The michael@0: * `userDataHeaderLength` property must be correctly pre-calculated. michael@0: */ michael@0: writeUserDataHeader: function(options) { michael@0: this.writeHexOctet(options.userDataHeaderLength); michael@0: michael@0: if (options.segmentMaxSeq > 1) { michael@0: if (options.segmentRef16Bit) { michael@0: this.writeHexOctet(PDU_IEI_CONCATENATED_SHORT_MESSAGES_16BIT); michael@0: this.writeHexOctet(4); michael@0: this.writeHexOctet((options.segmentRef >> 8) & 0xFF); michael@0: } else { michael@0: this.writeHexOctet(PDU_IEI_CONCATENATED_SHORT_MESSAGES_8BIT); michael@0: this.writeHexOctet(3); michael@0: } michael@0: this.writeHexOctet(options.segmentRef & 0xFF); michael@0: this.writeHexOctet(options.segmentMaxSeq & 0xFF); michael@0: this.writeHexOctet(options.segmentSeq & 0xFF); michael@0: } michael@0: michael@0: if (options.dcs == PDU_DCS_MSG_CODING_7BITS_ALPHABET) { michael@0: if (options.langIndex != PDU_NL_IDENTIFIER_DEFAULT) { michael@0: this.writeHexOctet(PDU_IEI_NATIONAL_LANGUAGE_LOCKING_SHIFT); michael@0: this.writeHexOctet(1); michael@0: this.writeHexOctet(options.langIndex); michael@0: } michael@0: michael@0: if (options.langShiftIndex != PDU_NL_IDENTIFIER_DEFAULT) { michael@0: this.writeHexOctet(PDU_IEI_NATIONAL_LANGUAGE_SINGLE_SHIFT); michael@0: this.writeHexOctet(1); michael@0: this.writeHexOctet(options.langShiftIndex); michael@0: } michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Read SM-TL Address. michael@0: * michael@0: * @param len michael@0: * Length of useful semi-octets within the Address-Value field. For michael@0: * example, the lenth of "12345" should be 5, and 4 for "1234". michael@0: * michael@0: * @see 3GPP TS 23.040 9.1.2.5 michael@0: */ michael@0: readAddress: function(len) { michael@0: // Address Length michael@0: if (!len || (len < 0)) { michael@0: if (DEBUG) { michael@0: this.context.debug("PDU error: invalid sender address length: " + len); michael@0: } michael@0: return null; michael@0: } michael@0: if (len % 2 == 1) { michael@0: len += 1; michael@0: } michael@0: if (DEBUG) this.context.debug("PDU: Going to read address: " + len); michael@0: michael@0: // Type-of-Address michael@0: let toa = this.readHexOctet(); michael@0: let addr = ""; michael@0: michael@0: if ((toa & 0xF0) == PDU_TOA_ALPHANUMERIC) { michael@0: addr = this.readSeptetsToString(Math.floor(len * 4 / 7), 0, michael@0: PDU_NL_IDENTIFIER_DEFAULT , PDU_NL_IDENTIFIER_DEFAULT ); michael@0: return addr; michael@0: } michael@0: addr = this.readSwappedNibbleBcdString(len / 2); michael@0: if (addr.length <= 0) { michael@0: if (DEBUG) this.context.debug("PDU error: no number provided"); michael@0: return null; michael@0: } michael@0: if ((toa & 0xF0) == (PDU_TOA_INTERNATIONAL)) { michael@0: addr = '+' + addr; michael@0: } michael@0: michael@0: return addr; michael@0: }, michael@0: michael@0: /** michael@0: * Read TP-Protocol-Indicator(TP-PID). michael@0: * michael@0: * @param msg michael@0: * message object for output. michael@0: * michael@0: * @see 3GPP TS 23.040 9.2.3.9 michael@0: */ michael@0: readProtocolIndicator: function(msg) { michael@0: // `The MS shall interpret reserved, obsolete, or unsupported values as the michael@0: // value 00000000 but shall store them exactly as received.` michael@0: msg.pid = this.readHexOctet(); michael@0: michael@0: msg.epid = msg.pid; michael@0: switch (msg.epid & 0xC0) { michael@0: case 0x40: michael@0: // Bit 7..0 = 01xxxxxx michael@0: switch (msg.epid) { michael@0: case PDU_PID_SHORT_MESSAGE_TYPE_0: michael@0: case PDU_PID_ANSI_136_R_DATA: michael@0: case PDU_PID_USIM_DATA_DOWNLOAD: michael@0: return; michael@0: } michael@0: break; michael@0: } michael@0: michael@0: msg.epid = PDU_PID_DEFAULT; michael@0: }, michael@0: michael@0: /** michael@0: * Read TP-Data-Coding-Scheme(TP-DCS) michael@0: * michael@0: * @param msg michael@0: * message object for output. michael@0: * michael@0: * @see 3GPP TS 23.040 9.2.3.10, 3GPP TS 23.038 4. michael@0: */ michael@0: readDataCodingScheme: function(msg) { michael@0: let dcs = this.readHexOctet(); michael@0: if (DEBUG) this.context.debug("PDU: read SMS dcs: " + dcs); michael@0: michael@0: // No message class by default. michael@0: let messageClass = PDU_DCS_MSG_CLASS_NORMAL; michael@0: // 7 bit is the default fallback encoding. michael@0: let encoding = PDU_DCS_MSG_CODING_7BITS_ALPHABET; michael@0: switch (dcs & PDU_DCS_CODING_GROUP_BITS) { michael@0: case 0x40: // bits 7..4 = 01xx michael@0: case 0x50: michael@0: case 0x60: michael@0: case 0x70: michael@0: // Bit 5..0 are coded exactly the same as Group 00xx michael@0: case 0x00: // bits 7..4 = 00xx michael@0: case 0x10: michael@0: case 0x20: michael@0: case 0x30: michael@0: if (dcs & 0x10) { michael@0: messageClass = dcs & PDU_DCS_MSG_CLASS_BITS; michael@0: } michael@0: switch (dcs & 0x0C) { michael@0: case 0x4: michael@0: encoding = PDU_DCS_MSG_CODING_8BITS_ALPHABET; michael@0: break; michael@0: case 0x8: michael@0: encoding = PDU_DCS_MSG_CODING_16BITS_ALPHABET; michael@0: break; michael@0: } michael@0: break; michael@0: michael@0: case 0xE0: // bits 7..4 = 1110 michael@0: encoding = PDU_DCS_MSG_CODING_16BITS_ALPHABET; michael@0: // Bit 3..0 are coded exactly the same as Message Waiting Indication michael@0: // Group 1101. michael@0: // Fall through. michael@0: case 0xC0: // bits 7..4 = 1100 michael@0: case 0xD0: // bits 7..4 = 1101 michael@0: // Indiciates voicemail indicator set or clear michael@0: let active = (dcs & PDU_DCS_MWI_ACTIVE_BITS) == PDU_DCS_MWI_ACTIVE_VALUE; michael@0: michael@0: // If TP-UDH is present, these values will be overwritten michael@0: switch (dcs & PDU_DCS_MWI_TYPE_BITS) { michael@0: case PDU_DCS_MWI_TYPE_VOICEMAIL: michael@0: let mwi = msg.mwi; michael@0: if (!mwi) { michael@0: mwi = msg.mwi = {}; michael@0: } michael@0: michael@0: mwi.active = active; michael@0: mwi.discard = (dcs & PDU_DCS_CODING_GROUP_BITS) == 0xC0; michael@0: mwi.msgCount = active ? GECKO_VOICEMAIL_MESSAGE_COUNT_UNKNOWN : 0; michael@0: michael@0: if (DEBUG) { michael@0: this.context.debug("MWI in DCS received for voicemail: " + michael@0: JSON.stringify(mwi)); michael@0: } michael@0: break; michael@0: case PDU_DCS_MWI_TYPE_FAX: michael@0: if (DEBUG) this.context.debug("MWI in DCS received for fax"); michael@0: break; michael@0: case PDU_DCS_MWI_TYPE_EMAIL: michael@0: if (DEBUG) this.context.debug("MWI in DCS received for email"); michael@0: break; michael@0: default: michael@0: if (DEBUG) this.context.debug("MWI in DCS received for \"other\""); michael@0: break; michael@0: } michael@0: break; michael@0: michael@0: case 0xF0: // bits 7..4 = 1111 michael@0: if (dcs & 0x04) { michael@0: encoding = PDU_DCS_MSG_CODING_8BITS_ALPHABET; michael@0: } michael@0: messageClass = dcs & PDU_DCS_MSG_CLASS_BITS; michael@0: break; michael@0: michael@0: default: michael@0: // Falling back to default encoding. michael@0: break; michael@0: } michael@0: michael@0: msg.dcs = dcs; michael@0: msg.encoding = encoding; michael@0: msg.messageClass = GECKO_SMS_MESSAGE_CLASSES[messageClass]; michael@0: michael@0: if (DEBUG) this.context.debug("PDU: message encoding is " + encoding + " bit."); michael@0: }, michael@0: michael@0: /** michael@0: * Read GSM TP-Service-Centre-Time-Stamp(TP-SCTS). michael@0: * michael@0: * @see 3GPP TS 23.040 9.2.3.11 michael@0: */ michael@0: readTimestamp: function() { michael@0: let year = this.readSwappedNibbleBcdNum(1) + PDU_TIMESTAMP_YEAR_OFFSET; michael@0: let month = this.readSwappedNibbleBcdNum(1) - 1; michael@0: let day = this.readSwappedNibbleBcdNum(1); michael@0: let hour = this.readSwappedNibbleBcdNum(1); michael@0: let minute = this.readSwappedNibbleBcdNum(1); michael@0: let second = this.readSwappedNibbleBcdNum(1); michael@0: let timestamp = Date.UTC(year, month, day, hour, minute, second); michael@0: michael@0: // If the most significant bit of the least significant nibble is 1, michael@0: // the timezone offset is negative (fourth bit from the right => 0x08): michael@0: // localtime = UTC + tzOffset michael@0: // therefore michael@0: // UTC = localtime - tzOffset michael@0: let tzOctet = this.readHexOctet(); michael@0: let tzOffset = this.octetToBCD(tzOctet & ~0x08) * 15 * 60 * 1000; michael@0: tzOffset = (tzOctet & 0x08) ? -tzOffset : tzOffset; michael@0: timestamp -= tzOffset; michael@0: michael@0: return timestamp; michael@0: }, michael@0: michael@0: /** michael@0: * Write GSM TP-Service-Centre-Time-Stamp(TP-SCTS). michael@0: * michael@0: * @see 3GPP TS 23.040 9.2.3.11 michael@0: */ michael@0: writeTimestamp: function(date) { michael@0: this.writeSwappedNibbleBCDNum(date.getFullYear() - PDU_TIMESTAMP_YEAR_OFFSET); michael@0: michael@0: // The value returned by getMonth() is an integer between 0 and 11. michael@0: // 0 is corresponds to January, 1 to February, and so on. michael@0: this.writeSwappedNibbleBCDNum(date.getMonth() + 1); michael@0: this.writeSwappedNibbleBCDNum(date.getDate()); michael@0: this.writeSwappedNibbleBCDNum(date.getHours()); michael@0: this.writeSwappedNibbleBCDNum(date.getMinutes()); michael@0: this.writeSwappedNibbleBCDNum(date.getSeconds()); michael@0: michael@0: // the value returned by getTimezoneOffset() is the difference, michael@0: // in minutes, between UTC and local time. michael@0: // For example, if your time zone is UTC+10 (Australian Eastern Standard Time), michael@0: // -600 will be returned. michael@0: // In TS 23.040 9.2.3.11, the Time Zone field of TP-SCTS indicates michael@0: // the different between the local time and GMT. michael@0: // And expressed in quarters of an hours. (so need to divid by 15) michael@0: let zone = date.getTimezoneOffset() / 15; michael@0: let octet = this.BCDToOctet(zone); michael@0: michael@0: // the bit3 of the Time Zone field represents the algebraic sign. michael@0: // (0: positive, 1: negative). michael@0: // For example, if the time zone is -0800 GMT, michael@0: // 480 will be returned by getTimezoneOffset(). michael@0: // In this case, need to mark sign bit as 1. => 0x08 michael@0: if (zone > 0) { michael@0: octet = octet | 0x08; michael@0: } michael@0: this.writeHexOctet(octet); michael@0: }, michael@0: michael@0: /** michael@0: * User data can be 7 bit (default alphabet) data, 8 bit data, or 16 bit michael@0: * (UCS2) data. michael@0: * michael@0: * @param msg michael@0: * message object for output. michael@0: * @param length michael@0: * length of user data to read in octets. michael@0: */ michael@0: readUserData: function(msg, length) { michael@0: if (DEBUG) { michael@0: this.context.debug("Reading " + length + " bytes of user data."); michael@0: } michael@0: michael@0: let paddingBits = 0; michael@0: if (msg.udhi) { michael@0: this.readUserDataHeader(msg); michael@0: michael@0: if (msg.encoding == PDU_DCS_MSG_CODING_7BITS_ALPHABET) { michael@0: let headerBits = (msg.header.length + 1) * 8; michael@0: let headerSeptets = Math.ceil(headerBits / 7); michael@0: michael@0: length -= headerSeptets; michael@0: paddingBits = headerSeptets * 7 - headerBits; michael@0: } else { michael@0: length -= (msg.header.length + 1); michael@0: } michael@0: } michael@0: michael@0: if (DEBUG) { michael@0: this.context.debug("After header, " + length + " septets left of user data"); michael@0: } michael@0: michael@0: msg.body = null; michael@0: msg.data = null; michael@0: switch (msg.encoding) { michael@0: case PDU_DCS_MSG_CODING_7BITS_ALPHABET: michael@0: // 7 bit encoding allows 140 octets, which means 160 characters michael@0: // ((140x8) / 7 = 160 chars) michael@0: if (length > PDU_MAX_USER_DATA_7BIT) { michael@0: if (DEBUG) { michael@0: this.context.debug("PDU error: user data is too long: " + length); michael@0: } michael@0: break; michael@0: } michael@0: michael@0: let langIndex = msg.udhi ? msg.header.langIndex : PDU_NL_IDENTIFIER_DEFAULT; michael@0: let langShiftIndex = msg.udhi ? msg.header.langShiftIndex : PDU_NL_IDENTIFIER_DEFAULT; michael@0: msg.body = this.readSeptetsToString(length, paddingBits, langIndex, michael@0: langShiftIndex); michael@0: break; michael@0: case PDU_DCS_MSG_CODING_8BITS_ALPHABET: michael@0: msg.data = this.readHexOctetArray(length); michael@0: break; michael@0: case PDU_DCS_MSG_CODING_16BITS_ALPHABET: michael@0: msg.body = this.readUCS2String(length); michael@0: break; michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Read extra parameters if TP-PI is set. michael@0: * michael@0: * @param msg michael@0: * message object for output. michael@0: */ michael@0: readExtraParams: function(msg) { michael@0: // Because each PDU octet is converted to two UCS2 char2, we should always michael@0: // get even messageStringLength in this#_processReceivedSms(). So, we'll michael@0: // always need two delimitors at the end. michael@0: if (this.context.Buf.getReadAvailable() <= 4) { michael@0: return; michael@0: } michael@0: michael@0: // TP-Parameter-Indicator michael@0: let pi; michael@0: do { michael@0: // `The most significant bit in octet 1 and any other TP-PI octets which michael@0: // may be added later is reserved as an extension bit which when set to a michael@0: // 1 shall indicate that another TP-PI octet follows immediately michael@0: // afterwards.` ~ 3GPP TS 23.040 9.2.3.27 michael@0: pi = this.readHexOctet(); michael@0: } while (pi & PDU_PI_EXTENSION); michael@0: michael@0: // `If the TP-UDL bit is set to "1" but the TP-DCS bit is set to "0" then michael@0: // the receiving entity shall for TP-DCS assume a value of 0x00, i.e. the michael@0: // 7bit default alphabet.` ~ 3GPP 23.040 9.2.3.27 michael@0: msg.dcs = 0; michael@0: msg.encoding = PDU_DCS_MSG_CODING_7BITS_ALPHABET; michael@0: michael@0: // TP-Protocol-Identifier michael@0: if (pi & PDU_PI_PROTOCOL_IDENTIFIER) { michael@0: this.readProtocolIndicator(msg); michael@0: } michael@0: // TP-Data-Coding-Scheme michael@0: if (pi & PDU_PI_DATA_CODING_SCHEME) { michael@0: this.readDataCodingScheme(msg); michael@0: } michael@0: // TP-User-Data-Length michael@0: if (pi & PDU_PI_USER_DATA_LENGTH) { michael@0: let userDataLength = this.readHexOctet(); michael@0: this.readUserData(msg, userDataLength); michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Read and decode a PDU-encoded message from the stream. michael@0: * michael@0: * TODO: add some basic sanity checks like: michael@0: * - do we have the minimum number of chars available michael@0: */ michael@0: readMessage: function() { michael@0: // An empty message object. This gets filled below and then returned. michael@0: let msg = { michael@0: // D:DELIVER, DR:DELIVER-REPORT, S:SUBMIT, SR:SUBMIT-REPORT, michael@0: // ST:STATUS-REPORT, C:COMMAND michael@0: // M:Mandatory, O:Optional, X:Unavailable michael@0: // D DR S SR ST C michael@0: SMSC: null, // M M M M M M michael@0: mti: null, // M M M M M M michael@0: udhi: null, // M M O M M M michael@0: sender: null, // M X X X X X michael@0: recipient: null, // X X M X M M michael@0: pid: null, // M O M O O M michael@0: epid: null, // M O M O O M michael@0: dcs: null, // M O M O O X michael@0: mwi: null, // O O O O O O michael@0: replace: false, // O O O O O O michael@0: header: null, // M M O M M M michael@0: body: null, // M O M O O O michael@0: data: null, // M O M O O O michael@0: sentTimestamp: null, // M X X X X X michael@0: status: null, // X X X X M X michael@0: scts: null, // X X X M M X michael@0: dt: null, // X X X X M X michael@0: }; michael@0: michael@0: // SMSC info michael@0: let smscLength = this.readHexOctet(); michael@0: if (smscLength > 0) { michael@0: let smscTypeOfAddress = this.readHexOctet(); michael@0: // Subtract the type-of-address octet we just read from the length. michael@0: msg.SMSC = this.readSwappedNibbleBcdString(smscLength - 1); michael@0: if ((smscTypeOfAddress >> 4) == (PDU_TOA_INTERNATIONAL >> 4)) { michael@0: msg.SMSC = '+' + msg.SMSC; michael@0: } michael@0: } michael@0: michael@0: // First octet of this SMS-DELIVER or SMS-SUBMIT message michael@0: let firstOctet = this.readHexOctet(); michael@0: // Message Type Indicator michael@0: msg.mti = firstOctet & 0x03; michael@0: // User data header indicator michael@0: msg.udhi = firstOctet & PDU_UDHI; michael@0: michael@0: switch (msg.mti) { michael@0: case PDU_MTI_SMS_RESERVED: michael@0: // `If an MS receives a TPDU with a "Reserved" value in the TP-MTI it michael@0: // shall process the message as if it were an "SMS-DELIVER" but store michael@0: // the message exactly as received.` ~ 3GPP TS 23.040 9.2.3.1 michael@0: case PDU_MTI_SMS_DELIVER: michael@0: return this.readDeliverMessage(msg); michael@0: case PDU_MTI_SMS_STATUS_REPORT: michael@0: return this.readStatusReportMessage(msg); michael@0: default: michael@0: return null; michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Helper for processing received SMS parcel data. michael@0: * michael@0: * @param length michael@0: * Length of SMS string in the incoming parcel. michael@0: * michael@0: * @return Message parsed or null for invalid message. michael@0: */ michael@0: processReceivedSms: function(length) { michael@0: if (!length) { michael@0: if (DEBUG) this.context.debug("Received empty SMS!"); michael@0: return [null, PDU_FCS_UNSPECIFIED]; michael@0: } michael@0: michael@0: let Buf = this.context.Buf; michael@0: michael@0: // An SMS is a string, but we won't read it as such, so let's read the michael@0: // string length and then defer to PDU parsing helper. michael@0: let messageStringLength = Buf.readInt32(); michael@0: if (DEBUG) this.context.debug("Got new SMS, length " + messageStringLength); michael@0: let message = this.readMessage(); michael@0: if (DEBUG) this.context.debug("Got new SMS: " + JSON.stringify(message)); michael@0: michael@0: // Read string delimiters. See Buf.readString(). michael@0: Buf.readStringDelimiter(length); michael@0: michael@0: // Determine result michael@0: if (!message) { michael@0: return [null, PDU_FCS_UNSPECIFIED]; michael@0: } michael@0: michael@0: if (message.epid == PDU_PID_SHORT_MESSAGE_TYPE_0) { michael@0: // `A short message type 0 indicates that the ME must acknowledge receipt michael@0: // of the short message but shall discard its contents.` ~ 3GPP TS 23.040 michael@0: // 9.2.3.9 michael@0: return [null, PDU_FCS_OK]; michael@0: } michael@0: michael@0: if (message.messageClass == GECKO_SMS_MESSAGE_CLASSES[PDU_DCS_MSG_CLASS_2]) { michael@0: let RIL = this.context.RIL; michael@0: switch (message.epid) { michael@0: case PDU_PID_ANSI_136_R_DATA: michael@0: case PDU_PID_USIM_DATA_DOWNLOAD: michael@0: let ICCUtilsHelper = this.context.ICCUtilsHelper; michael@0: if (ICCUtilsHelper.isICCServiceAvailable("DATA_DOWNLOAD_SMS_PP")) { michael@0: // `If the service "data download via SMS Point-to-Point" is michael@0: // allocated and activated in the (U)SIM Service Table, ... then the michael@0: // ME shall pass the message transparently to the UICC using the michael@0: // ENVELOPE (SMS-PP DOWNLOAD).` ~ 3GPP TS 31.111 7.1.1.1 michael@0: RIL.dataDownloadViaSMSPP(message); michael@0: michael@0: // `the ME shall not display the message, or alert the user of a michael@0: // short message waiting.` ~ 3GPP TS 31.111 7.1.1.1 michael@0: return [null, PDU_FCS_RESERVED]; michael@0: } michael@0: michael@0: // If the service "data download via SMS-PP" is not available in the michael@0: // (U)SIM Service Table, ..., then the ME shall store the message in michael@0: // EFsms in accordance with TS 31.102` ~ 3GPP TS 31.111 7.1.1.1 michael@0: michael@0: // Fall through. michael@0: default: michael@0: RIL.writeSmsToSIM(message); michael@0: break; michael@0: } michael@0: } michael@0: michael@0: // TODO: Bug 739143: B2G SMS: Support SMS Storage Full event michael@0: if ((message.messageClass != GECKO_SMS_MESSAGE_CLASSES[PDU_DCS_MSG_CLASS_0]) && !true) { michael@0: // `When a mobile terminated message is class 0..., the MS shall display michael@0: // the message immediately and send a ACK to the SC ..., irrespective of michael@0: // whether there is memory available in the (U)SIM or ME.` ~ 3GPP 23.038 michael@0: // clause 4. michael@0: michael@0: if (message.messageClass == GECKO_SMS_MESSAGE_CLASSES[PDU_DCS_MSG_CLASS_2]) { michael@0: // `If all the short message storage at the MS is already in use, the michael@0: // MS shall return "memory capacity exceeded".` ~ 3GPP 23.038 clause 4. michael@0: return [null, PDU_FCS_MEMORY_CAPACITY_EXCEEDED]; michael@0: } michael@0: michael@0: return [null, PDU_FCS_UNSPECIFIED]; michael@0: } michael@0: michael@0: return [message, PDU_FCS_OK]; michael@0: }, michael@0: michael@0: /** michael@0: * Read and decode a SMS-DELIVER PDU. michael@0: * michael@0: * @param msg michael@0: * message object for output. michael@0: */ michael@0: readDeliverMessage: function(msg) { michael@0: // - Sender Address info - michael@0: let senderAddressLength = this.readHexOctet(); michael@0: msg.sender = this.readAddress(senderAddressLength); michael@0: // - TP-Protocolo-Identifier - michael@0: this.readProtocolIndicator(msg); michael@0: // - TP-Data-Coding-Scheme - michael@0: this.readDataCodingScheme(msg); michael@0: // - TP-Service-Center-Time-Stamp - michael@0: msg.sentTimestamp = this.readTimestamp(); michael@0: // - TP-User-Data-Length - michael@0: let userDataLength = this.readHexOctet(); michael@0: michael@0: // - TP-User-Data - michael@0: if (userDataLength > 0) { michael@0: this.readUserData(msg, userDataLength); michael@0: } michael@0: michael@0: return msg; michael@0: }, michael@0: michael@0: /** michael@0: * Read and decode a SMS-STATUS-REPORT PDU. michael@0: * michael@0: * @param msg michael@0: * message object for output. michael@0: */ michael@0: readStatusReportMessage: function(msg) { michael@0: // TP-Message-Reference michael@0: msg.messageRef = this.readHexOctet(); michael@0: // TP-Recipient-Address michael@0: let recipientAddressLength = this.readHexOctet(); michael@0: msg.recipient = this.readAddress(recipientAddressLength); michael@0: // TP-Service-Centre-Time-Stamp michael@0: msg.scts = this.readTimestamp(); michael@0: // TP-Discharge-Time michael@0: msg.dt = this.readTimestamp(); michael@0: // TP-Status michael@0: msg.status = this.readHexOctet(); michael@0: michael@0: this.readExtraParams(msg); michael@0: michael@0: return msg; michael@0: }, michael@0: michael@0: /** michael@0: * Serialize a SMS-SUBMIT PDU message and write it to the output stream. michael@0: * michael@0: * This method expects that a data coding scheme has been chosen already michael@0: * and that the length of the user data payload in that encoding is known, michael@0: * too. Both go hand in hand together anyway. michael@0: * michael@0: * @param address michael@0: * String containing the address (number) of the SMS receiver michael@0: * @param userData michael@0: * String containing the message to be sent as user data michael@0: * @param dcs michael@0: * Data coding scheme. One of the PDU_DCS_MSG_CODING_*BITS_ALPHABET michael@0: * constants. michael@0: * @param userDataHeaderLength michael@0: * Length of embedded user data header, in bytes. The whole header michael@0: * size will be userDataHeaderLength + 1; 0 for no header. michael@0: * @param encodedBodyLength michael@0: * Length of the user data when encoded with the given DCS. For UCS2, michael@0: * in bytes; for 7-bit, in septets. michael@0: * @param langIndex michael@0: * Table index used for normal 7-bit encoded character lookup. michael@0: * @param langShiftIndex michael@0: * Table index used for escaped 7-bit encoded character lookup. michael@0: * @param requestStatusReport michael@0: * Request status report. michael@0: */ michael@0: writeMessage: function(options) { michael@0: if (DEBUG) { michael@0: this.context.debug("writeMessage: " + JSON.stringify(options)); michael@0: } michael@0: let Buf = this.context.Buf; michael@0: let address = options.number; michael@0: let body = options.body; michael@0: let dcs = options.dcs; michael@0: let userDataHeaderLength = options.userDataHeaderLength; michael@0: let encodedBodyLength = options.encodedBodyLength; michael@0: let langIndex = options.langIndex; michael@0: let langShiftIndex = options.langShiftIndex; michael@0: michael@0: // SMS-SUBMIT Format: michael@0: // michael@0: // PDU Type - 1 octet michael@0: // Message Reference - 1 octet michael@0: // DA - Destination Address - 2 to 12 octets michael@0: // PID - Protocol Identifier - 1 octet michael@0: // DCS - Data Coding Scheme - 1 octet michael@0: // VP - Validity Period - 0, 1 or 7 octets michael@0: // UDL - User Data Length - 1 octet michael@0: // UD - User Data - 140 octets michael@0: michael@0: let addressFormat = PDU_TOA_ISDN; // 81 michael@0: if (address[0] == '+') { michael@0: addressFormat = PDU_TOA_INTERNATIONAL | PDU_TOA_ISDN; // 91 michael@0: address = address.substring(1); michael@0: } michael@0: //TODO validity is unsupported for now michael@0: let validity = 0; michael@0: michael@0: let headerOctets = (userDataHeaderLength ? userDataHeaderLength + 1 : 0); michael@0: let paddingBits; michael@0: let userDataLengthInSeptets; michael@0: let userDataLengthInOctets; michael@0: if (dcs == PDU_DCS_MSG_CODING_7BITS_ALPHABET) { michael@0: let headerSeptets = Math.ceil(headerOctets * 8 / 7); michael@0: userDataLengthInSeptets = headerSeptets + encodedBodyLength; michael@0: userDataLengthInOctets = Math.ceil(userDataLengthInSeptets * 7 / 8); michael@0: paddingBits = headerSeptets * 7 - headerOctets * 8; michael@0: } else { michael@0: userDataLengthInOctets = headerOctets + encodedBodyLength; michael@0: paddingBits = 0; michael@0: } michael@0: michael@0: let pduOctetLength = 4 + // PDU Type, Message Ref, address length + format michael@0: Math.ceil(address.length / 2) + michael@0: 3 + // PID, DCS, UDL michael@0: userDataLengthInOctets; michael@0: if (validity) { michael@0: //TODO: add more to pduOctetLength michael@0: } michael@0: michael@0: // Start the string. Since octets are represented in hex, we will need michael@0: // twice as many characters as octets. michael@0: Buf.writeInt32(pduOctetLength * 2); michael@0: michael@0: // - PDU-TYPE- michael@0: michael@0: // +--------+----------+---------+---------+--------+---------+ michael@0: // | RP (1) | UDHI (1) | SRR (1) | VPF (2) | RD (1) | MTI (2) | michael@0: // +--------+----------+---------+---------+--------+---------+ michael@0: // RP: 0 Reply path parameter is not set michael@0: // 1 Reply path parameter is set michael@0: // UDHI: 0 The UD Field contains only the short message michael@0: // 1 The beginning of the UD field contains a header in addition michael@0: // of the short message michael@0: // SRR: 0 A status report is not requested michael@0: // 1 A status report is requested michael@0: // VPF: bit4 bit3 michael@0: // 0 0 VP field is not present michael@0: // 0 1 Reserved michael@0: // 1 0 VP field present an integer represented (relative) michael@0: // 1 1 VP field present a semi-octet represented (absolute) michael@0: // RD: Instruct the SMSC to accept(0) or reject(1) an SMS-SUBMIT michael@0: // for a short message still held in the SMSC which has the same michael@0: // MR and DA as a previously submitted short message from the michael@0: // same OA michael@0: // MTI: bit1 bit0 Message Type michael@0: // 0 0 SMS-DELIVER (SMSC ==> MS) michael@0: // 0 1 SMS-SUBMIT (MS ==> SMSC) michael@0: michael@0: // PDU type. MTI is set to SMS-SUBMIT michael@0: let firstOctet = PDU_MTI_SMS_SUBMIT; michael@0: michael@0: // Status-Report-Request michael@0: if (options.requestStatusReport) { michael@0: firstOctet |= PDU_SRI_SRR; michael@0: } michael@0: michael@0: // Validity period michael@0: if (validity) { michael@0: //TODO: not supported yet, OR with one of PDU_VPF_* michael@0: } michael@0: // User data header indicator michael@0: if (headerOctets) { michael@0: firstOctet |= PDU_UDHI; michael@0: } michael@0: this.writeHexOctet(firstOctet); michael@0: michael@0: // Message reference 00 michael@0: this.writeHexOctet(0x00); michael@0: michael@0: // - Destination Address - michael@0: this.writeHexOctet(address.length); michael@0: this.writeHexOctet(addressFormat); michael@0: this.writeSwappedNibbleBCD(address); michael@0: michael@0: // - Protocol Identifier - michael@0: this.writeHexOctet(0x00); michael@0: michael@0: // - Data coding scheme - michael@0: // For now it assumes bits 7..4 = 1111 except for the 16 bits use case michael@0: this.writeHexOctet(dcs); michael@0: michael@0: // - Validity Period - michael@0: if (validity) { michael@0: this.writeHexOctet(validity); michael@0: } michael@0: michael@0: // - User Data - michael@0: if (dcs == PDU_DCS_MSG_CODING_7BITS_ALPHABET) { michael@0: this.writeHexOctet(userDataLengthInSeptets); michael@0: } else { michael@0: this.writeHexOctet(userDataLengthInOctets); michael@0: } michael@0: michael@0: if (headerOctets) { michael@0: this.writeUserDataHeader(options); michael@0: } michael@0: michael@0: switch (dcs) { michael@0: case PDU_DCS_MSG_CODING_7BITS_ALPHABET: michael@0: this.writeStringAsSeptets(body, paddingBits, langIndex, langShiftIndex); michael@0: break; michael@0: case PDU_DCS_MSG_CODING_8BITS_ALPHABET: michael@0: // Unsupported. michael@0: break; michael@0: case PDU_DCS_MSG_CODING_16BITS_ALPHABET: michael@0: this.writeUCS2String(body); michael@0: break; michael@0: } michael@0: michael@0: // End of the string. The string length is always even by definition, so michael@0: // we write two \0 delimiters. michael@0: Buf.writeUint16(0); michael@0: Buf.writeUint16(0); michael@0: }, michael@0: michael@0: /** michael@0: * Read GSM CBS message serial number. michael@0: * michael@0: * @param msg michael@0: * message object for output. michael@0: * michael@0: * @see 3GPP TS 23.041 section 9.4.1.2.1 michael@0: */ michael@0: readCbSerialNumber: function(msg) { michael@0: let Buf = this.context.Buf; michael@0: msg.serial = Buf.readUint8() << 8 | Buf.readUint8(); michael@0: msg.geographicalScope = (msg.serial >>> 14) & 0x03; michael@0: msg.messageCode = (msg.serial >>> 4) & 0x03FF; michael@0: msg.updateNumber = msg.serial & 0x0F; michael@0: }, michael@0: michael@0: /** michael@0: * Read GSM CBS message message identifier. michael@0: * michael@0: * @param msg michael@0: * message object for output. michael@0: * michael@0: * @see 3GPP TS 23.041 section 9.4.1.2.2 michael@0: */ michael@0: readCbMessageIdentifier: function(msg) { michael@0: let Buf = this.context.Buf; michael@0: msg.messageId = Buf.readUint8() << 8 | Buf.readUint8(); michael@0: michael@0: if ((msg.format != CB_FORMAT_ETWS) michael@0: && (msg.messageId >= CB_GSM_MESSAGEID_ETWS_BEGIN) michael@0: && (msg.messageId <= CB_GSM_MESSAGEID_ETWS_END)) { michael@0: // `In the case of transmitting CBS message for ETWS, a part of michael@0: // Message Code can be used to command mobile terminals to activate michael@0: // emergency user alert and message popup in order to alert the users.` michael@0: msg.etws = { michael@0: emergencyUserAlert: msg.messageCode & 0x0200 ? true : false, michael@0: popup: msg.messageCode & 0x0100 ? true : false michael@0: }; michael@0: michael@0: let warningType = msg.messageId - CB_GSM_MESSAGEID_ETWS_BEGIN; michael@0: if (warningType < CB_ETWS_WARNING_TYPE_NAMES.length) { michael@0: msg.etws.warningType = warningType; michael@0: } michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Read CBS Data Coding Scheme. michael@0: * michael@0: * @param msg michael@0: * message object for output. michael@0: * michael@0: * @see 3GPP TS 23.038 section 5. michael@0: */ michael@0: readCbDataCodingScheme: function(msg) { michael@0: let dcs = this.context.Buf.readUint8(); michael@0: if (DEBUG) this.context.debug("PDU: read CBS dcs: " + dcs); michael@0: michael@0: let language = null, hasLanguageIndicator = false; michael@0: // `Any reserved codings shall be assumed to be the GSM 7bit default michael@0: // alphabet.` michael@0: let encoding = PDU_DCS_MSG_CODING_7BITS_ALPHABET; michael@0: let messageClass = PDU_DCS_MSG_CLASS_NORMAL; michael@0: michael@0: switch (dcs & PDU_DCS_CODING_GROUP_BITS) { michael@0: case 0x00: // 0000 michael@0: language = CB_DCS_LANG_GROUP_1[dcs & 0x0F]; michael@0: break; michael@0: michael@0: case 0x10: // 0001 michael@0: switch (dcs & 0x0F) { michael@0: case 0x00: michael@0: hasLanguageIndicator = true; michael@0: break; michael@0: case 0x01: michael@0: encoding = PDU_DCS_MSG_CODING_16BITS_ALPHABET; michael@0: hasLanguageIndicator = true; michael@0: break; michael@0: } michael@0: break; michael@0: michael@0: case 0x20: // 0010 michael@0: language = CB_DCS_LANG_GROUP_2[dcs & 0x0F]; michael@0: break; michael@0: michael@0: case 0x40: // 01xx michael@0: case 0x50: michael@0: //case 0x60: Text Compression, not supported michael@0: //case 0x70: Text Compression, not supported michael@0: case 0x90: // 1001 michael@0: encoding = (dcs & 0x0C); michael@0: if (encoding == 0x0C) { michael@0: encoding = PDU_DCS_MSG_CODING_7BITS_ALPHABET; michael@0: } michael@0: messageClass = (dcs & PDU_DCS_MSG_CLASS_BITS); michael@0: break; michael@0: michael@0: case 0xF0: michael@0: encoding = (dcs & 0x04) ? PDU_DCS_MSG_CODING_8BITS_ALPHABET michael@0: : PDU_DCS_MSG_CODING_7BITS_ALPHABET; michael@0: switch(dcs & PDU_DCS_MSG_CLASS_BITS) { michael@0: case 0x01: messageClass = PDU_DCS_MSG_CLASS_USER_1; break; michael@0: case 0x02: messageClass = PDU_DCS_MSG_CLASS_USER_2; break; michael@0: case 0x03: messageClass = PDU_DCS_MSG_CLASS_3; break; michael@0: } michael@0: break; michael@0: michael@0: case 0x30: // 0011 (Reserved) michael@0: case 0x80: // 1000 (Reserved) michael@0: case 0xA0: // 1010..1100 (Reserved) michael@0: case 0xB0: michael@0: case 0xC0: michael@0: break; michael@0: michael@0: default: michael@0: throw new Error("Unsupported CBS data coding scheme: " + dcs); michael@0: } michael@0: michael@0: msg.dcs = dcs; michael@0: msg.encoding = encoding; michael@0: msg.language = language; michael@0: msg.messageClass = GECKO_SMS_MESSAGE_CLASSES[messageClass]; michael@0: msg.hasLanguageIndicator = hasLanguageIndicator; michael@0: }, michael@0: michael@0: /** michael@0: * Read GSM CBS message page parameter. michael@0: * michael@0: * @param msg michael@0: * message object for output. michael@0: * michael@0: * @see 3GPP TS 23.041 section 9.4.1.2.4 michael@0: */ michael@0: readCbPageParameter: function(msg) { michael@0: let octet = this.context.Buf.readUint8(); michael@0: msg.pageIndex = (octet >>> 4) & 0x0F; michael@0: msg.numPages = octet & 0x0F; michael@0: if (!msg.pageIndex || !msg.numPages) { michael@0: // `If a mobile receives the code 0000 in either the first field or the michael@0: // second field then it shall treat the CBS message exactly the same as a michael@0: // CBS message with page parameter 0001 0001 (i.e. a single page message).` michael@0: msg.pageIndex = msg.numPages = 1; michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Read ETWS Primary Notification message warning type. michael@0: * michael@0: * @param msg michael@0: * message object for output. michael@0: * michael@0: * @see 3GPP TS 23.041 section 9.3.24 michael@0: */ michael@0: readCbWarningType: function(msg) { michael@0: let Buf = this.context.Buf; michael@0: let word = Buf.readUint8() << 8 | Buf.readUint8(); michael@0: msg.etws = { michael@0: warningType: (word >>> 9) & 0x7F, michael@0: popup: word & 0x80 ? true : false, michael@0: emergencyUserAlert: word & 0x100 ? true : false michael@0: }; michael@0: }, michael@0: michael@0: /** michael@0: * Read CBS-Message-Information-Page michael@0: * michael@0: * @param msg michael@0: * message object for output. michael@0: * @param length michael@0: * length of cell broadcast data to read in octets. michael@0: * michael@0: * @see 3GPP TS 23.041 section 9.3.19 michael@0: */ michael@0: readGsmCbData: function(msg, length) { michael@0: let Buf = this.context.Buf; michael@0: let bufAdapter = { michael@0: context: this.context, michael@0: readHexOctet: function() { michael@0: return Buf.readUint8(); michael@0: } michael@0: }; michael@0: michael@0: msg.body = null; michael@0: msg.data = null; michael@0: switch (msg.encoding) { michael@0: case PDU_DCS_MSG_CODING_7BITS_ALPHABET: michael@0: msg.body = this.readSeptetsToString.call(bufAdapter, michael@0: (length * 8 / 7), 0, michael@0: PDU_NL_IDENTIFIER_DEFAULT, michael@0: PDU_NL_IDENTIFIER_DEFAULT); michael@0: if (msg.hasLanguageIndicator) { michael@0: msg.language = msg.body.substring(0, 2); michael@0: msg.body = msg.body.substring(3); michael@0: } michael@0: break; michael@0: michael@0: case PDU_DCS_MSG_CODING_8BITS_ALPHABET: michael@0: msg.data = Buf.readUint8Array(length); michael@0: break; michael@0: michael@0: case PDU_DCS_MSG_CODING_16BITS_ALPHABET: michael@0: if (msg.hasLanguageIndicator) { michael@0: msg.language = this.readSeptetsToString.call(bufAdapter, 2, 0, michael@0: PDU_NL_IDENTIFIER_DEFAULT, michael@0: PDU_NL_IDENTIFIER_DEFAULT); michael@0: length -= 2; michael@0: } michael@0: msg.body = this.readUCS2String.call(bufAdapter, length); michael@0: break; michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Read Cell GSM/ETWS/UMTS Broadcast Message. michael@0: * michael@0: * @param pduLength michael@0: * total length of the incoming PDU in octets. michael@0: */ michael@0: readCbMessage: function(pduLength) { michael@0: // Validity GSM ETWS UMTS michael@0: let msg = { michael@0: // Internally used in ril_worker: michael@0: serial: null, // O O O michael@0: updateNumber: null, // O O O michael@0: format: null, // O O O michael@0: dcs: 0x0F, // O X O michael@0: encoding: PDU_DCS_MSG_CODING_7BITS_ALPHABET, // O X O michael@0: hasLanguageIndicator: false, // O X O michael@0: data: null, // O X O michael@0: body: null, // O X O michael@0: pageIndex: 1, // O X X michael@0: numPages: 1, // O X X michael@0: michael@0: // DOM attributes: michael@0: geographicalScope: null, // O O O michael@0: messageCode: null, // O O O michael@0: messageId: null, // O O O michael@0: language: null, // O X O michael@0: fullBody: null, // O X O michael@0: fullData: null, // O X O michael@0: messageClass: GECKO_SMS_MESSAGE_CLASSES[PDU_DCS_MSG_CLASS_NORMAL], // O x O michael@0: etws: null // ? O ? michael@0: /*{ michael@0: warningType: null, // X O X michael@0: popup: false, // X O X michael@0: emergencyUserAlert: false, // X O X michael@0: }*/ michael@0: }; michael@0: michael@0: if (pduLength <= CB_MESSAGE_SIZE_ETWS) { michael@0: msg.format = CB_FORMAT_ETWS; michael@0: return this.readEtwsCbMessage(msg); michael@0: } michael@0: michael@0: if (pduLength <= CB_MESSAGE_SIZE_GSM) { michael@0: msg.format = CB_FORMAT_GSM; michael@0: return this.readGsmCbMessage(msg, pduLength); michael@0: } michael@0: michael@0: return null; michael@0: }, michael@0: michael@0: /** michael@0: * Read GSM Cell Broadcast Message. michael@0: * michael@0: * @param msg michael@0: * message object for output. michael@0: * @param pduLength michael@0: * total length of the incomint PDU in octets. michael@0: * michael@0: * @see 3GPP TS 23.041 clause 9.4.1.2 michael@0: */ michael@0: readGsmCbMessage: function(msg, pduLength) { michael@0: this.readCbSerialNumber(msg); michael@0: this.readCbMessageIdentifier(msg); michael@0: this.readCbDataCodingScheme(msg); michael@0: this.readCbPageParameter(msg); michael@0: michael@0: // GSM CB message header takes 6 octets. michael@0: this.readGsmCbData(msg, pduLength - 6); michael@0: michael@0: return msg; michael@0: }, michael@0: michael@0: /** michael@0: * Read ETWS Primary Notification Message. michael@0: * michael@0: * @param msg michael@0: * message object for output. michael@0: * michael@0: * @see 3GPP TS 23.041 clause 9.4.1.3 michael@0: */ michael@0: readEtwsCbMessage: function(msg) { michael@0: this.readCbSerialNumber(msg); michael@0: this.readCbMessageIdentifier(msg); michael@0: this.readCbWarningType(msg); michael@0: michael@0: // Octet 7..56 is Warning Security Information. However, according to michael@0: // section 9.4.1.3.6, `The UE shall ignore this parameter.` So we just skip michael@0: // processing it here. michael@0: michael@0: return msg; michael@0: }, michael@0: michael@0: /** michael@0: * Read network name. michael@0: * michael@0: * @param len Length of the information element. michael@0: * @return michael@0: * { michael@0: * networkName: network name. michael@0: * shouldIncludeCi: Should Country's initials included in text string. michael@0: * } michael@0: * @see TS 24.008 clause 10.5.3.5a. michael@0: */ michael@0: readNetworkName: function(len) { michael@0: // According to TS 24.008 Sec. 10.5.3.5a, the first octet is: michael@0: // bit 8: must be 1. michael@0: // bit 5-7: Text encoding. michael@0: // 000 - GSM default alphabet. michael@0: // 001 - UCS2 (16 bit). michael@0: // else - reserved. michael@0: // bit 4: MS should add the letters for Country's Initials and a space michael@0: // to the text string if this bit is true. michael@0: // bit 1-3: number of spare bits in last octet. michael@0: michael@0: let codingInfo = this.readHexOctet(); michael@0: if (!(codingInfo & 0x80)) { michael@0: return null; michael@0: } michael@0: michael@0: let textEncoding = (codingInfo & 0x70) >> 4; michael@0: let shouldIncludeCountryInitials = !!(codingInfo & 0x08); michael@0: let spareBits = codingInfo & 0x07; michael@0: let resultString; michael@0: michael@0: switch (textEncoding) { michael@0: case 0: michael@0: // GSM Default alphabet. michael@0: resultString = this.readSeptetsToString( michael@0: ((len - 1) * 8 - spareBits) / 7, 0, michael@0: PDU_NL_IDENTIFIER_DEFAULT, michael@0: PDU_NL_IDENTIFIER_DEFAULT); michael@0: break; michael@0: case 1: michael@0: // UCS2 encoded. michael@0: resultString = this.readUCS2String(len - 1); michael@0: break; michael@0: default: michael@0: // Not an available text coding. michael@0: return null; michael@0: } michael@0: michael@0: // TODO - Bug 820286: According to shouldIncludeCountryInitials, add michael@0: // country initials to the resulting string. michael@0: return resultString; michael@0: } michael@0: }; michael@0: michael@0: /** michael@0: * Provide buffer with bitwise read/write function so make encoding/decoding easier. michael@0: */ michael@0: function BitBufferHelperObject(/* unused */aContext) { michael@0: this.readBuffer = []; michael@0: this.writeBuffer = []; michael@0: } michael@0: BitBufferHelperObject.prototype = { michael@0: readCache: 0, michael@0: readCacheSize: 0, michael@0: readBuffer: null, michael@0: readIndex: 0, michael@0: writeCache: 0, michael@0: writeCacheSize: 0, michael@0: writeBuffer: null, michael@0: michael@0: // Max length is 32 because we use integer as read/write cache. michael@0: // All read/write functions are implemented based on bitwise operation. michael@0: readBits: function(length) { michael@0: if (length <= 0 || length > 32) { michael@0: return null; michael@0: } michael@0: michael@0: if (length > this.readCacheSize) { michael@0: let bytesToRead = Math.ceil((length - this.readCacheSize) / 8); michael@0: for(let i = 0; i < bytesToRead; i++) { michael@0: this.readCache = (this.readCache << 8) | (this.readBuffer[this.readIndex++] & 0xFF); michael@0: this.readCacheSize += 8; michael@0: } michael@0: } michael@0: michael@0: let bitOffset = (this.readCacheSize - length), michael@0: resultMask = (1 << length) - 1, michael@0: result = 0; michael@0: michael@0: result = (this.readCache >> bitOffset) & resultMask; michael@0: this.readCacheSize -= length; michael@0: michael@0: return result; michael@0: }, michael@0: michael@0: backwardReadPilot: function(length) { michael@0: if (length <= 0) { michael@0: return; michael@0: } michael@0: michael@0: // Zero-based position. michael@0: let bitIndexToRead = this.readIndex * 8 - this.readCacheSize - length; michael@0: michael@0: if (bitIndexToRead < 0) { michael@0: return; michael@0: } michael@0: michael@0: // Update readIndex, readCache, readCacheSize accordingly. michael@0: let readBits = bitIndexToRead % 8; michael@0: this.readIndex = Math.floor(bitIndexToRead / 8) + ((readBits) ? 1 : 0); michael@0: this.readCache = (readBits) ? this.readBuffer[this.readIndex - 1] : 0; michael@0: this.readCacheSize = (readBits) ? (8 - readBits) : 0; michael@0: }, michael@0: michael@0: writeBits: function(value, length) { michael@0: if (length <= 0 || length > 32) { michael@0: return; michael@0: } michael@0: michael@0: let totalLength = length + this.writeCacheSize; michael@0: michael@0: // 8-byte cache not full michael@0: if (totalLength < 8) { michael@0: let valueMask = (1 << length) - 1; michael@0: this.writeCache = (this.writeCache << length) | (value & valueMask); michael@0: this.writeCacheSize += length; michael@0: return; michael@0: } michael@0: michael@0: // Deal with unaligned part michael@0: if (this.writeCacheSize) { michael@0: let mergeLength = 8 - this.writeCacheSize, michael@0: valueMask = (1 << mergeLength) - 1; michael@0: michael@0: this.writeCache = (this.writeCache << mergeLength) | ((value >> (length - mergeLength)) & valueMask); michael@0: this.writeBuffer.push(this.writeCache & 0xFF); michael@0: length -= mergeLength; michael@0: } michael@0: michael@0: // Aligned part, just copy michael@0: while (length >= 8) { michael@0: length -= 8; michael@0: this.writeBuffer.push((value >> length) & 0xFF); michael@0: } michael@0: michael@0: // Rest part is saved into cache michael@0: this.writeCacheSize = length; michael@0: this.writeCache = value & ((1 << length) - 1); michael@0: michael@0: return; michael@0: }, michael@0: michael@0: // Drop what still in read cache and goto next 8-byte alignment. michael@0: // There might be a better naming. michael@0: nextOctetAlign: function() { michael@0: this.readCache = 0; michael@0: this.readCacheSize = 0; michael@0: }, michael@0: michael@0: // Flush current write cache to Buf with padding 0s. michael@0: // There might be a better naming. michael@0: flushWithPadding: function() { michael@0: if (this.writeCacheSize) { michael@0: this.writeBuffer.push(this.writeCache << (8 - this.writeCacheSize)); michael@0: } michael@0: this.writeCache = 0; michael@0: this.writeCacheSize = 0; michael@0: }, michael@0: michael@0: startWrite: function(dataBuffer) { michael@0: this.writeBuffer = dataBuffer; michael@0: this.writeCache = 0; michael@0: this.writeCacheSize = 0; michael@0: }, michael@0: michael@0: startRead: function(dataBuffer) { michael@0: this.readBuffer = dataBuffer; michael@0: this.readCache = 0; michael@0: this.readCacheSize = 0; michael@0: this.readIndex = 0; michael@0: }, michael@0: michael@0: getWriteBufferSize: function() { michael@0: return this.writeBuffer.length; michael@0: }, michael@0: michael@0: overwriteWriteBuffer: function(position, data) { michael@0: let writeLength = data.length; michael@0: if (writeLength + position >= this.writeBuffer.length) { michael@0: writeLength = this.writeBuffer.length - position; michael@0: } michael@0: for (let i = 0; i < writeLength; i++) { michael@0: this.writeBuffer[i + position] = data[i]; michael@0: } michael@0: } michael@0: }; michael@0: michael@0: /** michael@0: * Helper for CDMA PDU michael@0: * michael@0: * Currently, some function are shared with GsmPDUHelper, they should be michael@0: * moved from GsmPDUHelper to a common object shared among GsmPDUHelper and michael@0: * CdmaPDUHelper. michael@0: */ michael@0: function CdmaPDUHelperObject(aContext) { michael@0: this.context = aContext; michael@0: } michael@0: CdmaPDUHelperObject.prototype = { michael@0: context: null, michael@0: michael@0: // 1..........C michael@0: // Only "1234567890*#" is defined in C.S0005-D v2.0 michael@0: dtmfChars: ".1234567890*#...", michael@0: michael@0: /** michael@0: * Entry point for SMS encoding, the options object is made compatible michael@0: * with existing writeMessage() of GsmPDUHelper, but less key is used. michael@0: * michael@0: * Current used key in options: michael@0: * @param number michael@0: * String containing the address (number) of the SMS receiver michael@0: * @param body michael@0: * String containing the message to be sent, segmented part michael@0: * @param dcs michael@0: * Data coding scheme. One of the PDU_DCS_MSG_CODING_*BITS_ALPHABET michael@0: * constants. michael@0: * @param encodedBodyLength michael@0: * Length of the user data when encoded with the given DCS. For UCS2, michael@0: * in bytes; for 7-bit, in septets. michael@0: * @param requestStatusReport michael@0: * Request status report. michael@0: * @param segmentRef michael@0: * Reference number of concatenated SMS message michael@0: * @param segmentMaxSeq michael@0: * Total number of concatenated SMS message michael@0: * @param segmentSeq michael@0: * Sequence number of concatenated SMS message michael@0: */ michael@0: writeMessage: function(options) { michael@0: if (DEBUG) { michael@0: this.context.debug("cdma_writeMessage: " + JSON.stringify(options)); michael@0: } michael@0: michael@0: // Get encoding michael@0: options.encoding = this.gsmDcsToCdmaEncoding(options.dcs); michael@0: michael@0: // Common Header michael@0: if (options.segmentMaxSeq > 1) { michael@0: this.writeInt(PDU_CDMA_MSG_TELESERIVCIE_ID_WEMT); michael@0: } else { michael@0: this.writeInt(PDU_CDMA_MSG_TELESERIVCIE_ID_SMS); michael@0: } michael@0: michael@0: this.writeInt(0); michael@0: this.writeInt(PDU_CDMA_MSG_CATEGORY_UNSPEC); michael@0: michael@0: // Just fill out address info in byte, rild will encap them for us michael@0: let addrInfo = this.encodeAddr(options.number); michael@0: this.writeByte(addrInfo.digitMode); michael@0: this.writeByte(addrInfo.numberMode); michael@0: this.writeByte(addrInfo.numberType); michael@0: this.writeByte(addrInfo.numberPlan); michael@0: this.writeByte(addrInfo.address.length); michael@0: for (let i = 0; i < addrInfo.address.length; i++) { michael@0: this.writeByte(addrInfo.address[i]); michael@0: } michael@0: michael@0: // Subaddress, not supported michael@0: this.writeByte(0); // Subaddress : Type michael@0: this.writeByte(0); // Subaddress : Odd michael@0: this.writeByte(0); // Subaddress : length michael@0: michael@0: // User Data michael@0: let encodeResult = this.encodeUserData(options); michael@0: this.writeByte(encodeResult.length); michael@0: for (let i = 0; i < encodeResult.length; i++) { michael@0: this.writeByte(encodeResult[i]); michael@0: } michael@0: michael@0: encodeResult = null; michael@0: }, michael@0: michael@0: /** michael@0: * Data writters michael@0: */ michael@0: writeInt: function(value) { michael@0: this.context.Buf.writeInt32(value); michael@0: }, michael@0: michael@0: writeByte: function(value) { michael@0: this.context.Buf.writeInt32(value & 0xFF); michael@0: }, michael@0: michael@0: /** michael@0: * Transform GSM DCS to CDMA encoding. michael@0: */ michael@0: gsmDcsToCdmaEncoding: function(encoding) { michael@0: switch (encoding) { michael@0: case PDU_DCS_MSG_CODING_7BITS_ALPHABET: michael@0: return PDU_CDMA_MSG_CODING_7BITS_ASCII; michael@0: case PDU_DCS_MSG_CODING_8BITS_ALPHABET: michael@0: return PDU_CDMA_MSG_CODING_OCTET; michael@0: case PDU_DCS_MSG_CODING_16BITS_ALPHABET: michael@0: return PDU_CDMA_MSG_CODING_UNICODE; michael@0: } michael@0: throw new Error("gsmDcsToCdmaEncoding(): Invalid GSM SMS DCS value: " + encoding); michael@0: }, michael@0: michael@0: /** michael@0: * Encode address into CDMA address format, as a byte array. michael@0: * michael@0: * @see 3GGP2 C.S0015-B 2.0, 3.4.3.3 Address Parameters michael@0: * michael@0: * @param address michael@0: * String of address to be encoded michael@0: */ michael@0: encodeAddr: function(address) { michael@0: let result = {}; michael@0: michael@0: result.numberType = PDU_CDMA_MSG_ADDR_NUMBER_TYPE_UNKNOWN; michael@0: result.numberPlan = PDU_CDMA_MSG_ADDR_NUMBER_TYPE_UNKNOWN; michael@0: michael@0: if (address[0] === '+') { michael@0: address = address.substring(1); michael@0: } michael@0: michael@0: // Try encode with DTMF first michael@0: result.digitMode = PDU_CDMA_MSG_ADDR_DIGIT_MODE_DTMF; michael@0: result.numberMode = PDU_CDMA_MSG_ADDR_NUMBER_MODE_ANSI; michael@0: michael@0: result.address = []; michael@0: for (let i = 0; i < address.length; i++) { michael@0: let addrDigit = this.dtmfChars.indexOf(address.charAt(i)); michael@0: if (addrDigit < 0) { michael@0: result.digitMode = PDU_CDMA_MSG_ADDR_DIGIT_MODE_ASCII; michael@0: result.numberMode = PDU_CDMA_MSG_ADDR_NUMBER_MODE_ASCII; michael@0: result.address = []; michael@0: break; michael@0: } michael@0: result.address.push(addrDigit); michael@0: } michael@0: michael@0: // Address can't be encoded with DTMF, then use 7-bit ASCII michael@0: if (result.digitMode !== PDU_CDMA_MSG_ADDR_DIGIT_MODE_DTMF) { michael@0: if (address.indexOf("@") !== -1) { michael@0: result.numberType = PDU_CDMA_MSG_ADDR_NUMBER_TYPE_NATIONAL; michael@0: } michael@0: michael@0: for (let i = 0; i < address.length; i++) { michael@0: result.address.push(address.charCodeAt(i) & 0x7F); michael@0: } michael@0: } michael@0: michael@0: return result; michael@0: }, michael@0: michael@0: /** michael@0: * Encode SMS contents in options into CDMA userData field. michael@0: * Corresponding and required subparameters will be added automatically. michael@0: * michael@0: * @see 3GGP2 C.S0015-B 2.0, 3.4.3.7 Bearer Data michael@0: * 4.5 Bearer Data Parameters michael@0: * michael@0: * Current used key in options: michael@0: * @param body michael@0: * String containing the message to be sent, segmented part michael@0: * @param encoding michael@0: * Encoding method of CDMA, can be transformed from GSM DCS by function michael@0: * cdmaPduHelp.gsmDcsToCdmaEncoding() michael@0: * @param encodedBodyLength michael@0: * Length of the user data when encoded with the given DCS. For UCS2, michael@0: * in bytes; for 7-bit, in septets. michael@0: * @param requestStatusReport michael@0: * Request status report. michael@0: * @param segmentRef michael@0: * Reference number of concatenated SMS message michael@0: * @param segmentMaxSeq michael@0: * Total number of concatenated SMS message michael@0: * @param segmentSeq michael@0: * Sequence number of concatenated SMS message michael@0: */ michael@0: encodeUserData: function(options) { michael@0: let userDataBuffer = []; michael@0: this.context.BitBufferHelper.startWrite(userDataBuffer); michael@0: michael@0: // Message Identifier michael@0: this.encodeUserDataMsgId(options); michael@0: michael@0: // User Data michael@0: this.encodeUserDataMsg(options); michael@0: michael@0: // Reply Option michael@0: this.encodeUserDataReplyOption(options); michael@0: michael@0: return userDataBuffer; michael@0: }, michael@0: michael@0: /** michael@0: * User data subparameter encoder : Message Identifier michael@0: * michael@0: * @see 3GGP2 C.S0015-B 2.0, 4.5.1 Message Identifier michael@0: */ michael@0: encodeUserDataMsgId: function(options) { michael@0: let BitBufferHelper = this.context.BitBufferHelper; michael@0: BitBufferHelper.writeBits(PDU_CDMA_MSG_USERDATA_MSG_ID, 8); michael@0: BitBufferHelper.writeBits(3, 8); michael@0: BitBufferHelper.writeBits(PDU_CDMA_MSG_TYPE_SUBMIT, 4); michael@0: BitBufferHelper.writeBits(1, 16); // TODO: How to get message ID? michael@0: if (options.segmentMaxSeq > 1) { michael@0: BitBufferHelper.writeBits(1, 1); michael@0: } else { michael@0: BitBufferHelper.writeBits(0, 1); michael@0: } michael@0: michael@0: BitBufferHelper.flushWithPadding(); michael@0: }, michael@0: michael@0: /** michael@0: * User data subparameter encoder : User Data michael@0: * michael@0: * @see 3GGP2 C.S0015-B 2.0, 4.5.2 User Data michael@0: */ michael@0: encodeUserDataMsg: function(options) { michael@0: let BitBufferHelper = this.context.BitBufferHelper; michael@0: BitBufferHelper.writeBits(PDU_CDMA_MSG_USERDATA_BODY, 8); michael@0: // Reserve space for length michael@0: BitBufferHelper.writeBits(0, 8); michael@0: let lengthPosition = BitBufferHelper.getWriteBufferSize(); michael@0: michael@0: BitBufferHelper.writeBits(options.encoding, 5); michael@0: michael@0: // Add user data header for message segement michael@0: let msgBody = options.body, michael@0: msgBodySize = (options.encoding === PDU_CDMA_MSG_CODING_7BITS_ASCII ? michael@0: options.encodedBodyLength : michael@0: msgBody.length); michael@0: if (options.segmentMaxSeq > 1) { michael@0: if (options.encoding === PDU_CDMA_MSG_CODING_7BITS_ASCII) { michael@0: BitBufferHelper.writeBits(msgBodySize + 7, 8); // Required length for user data header, in septet(7-bit) michael@0: michael@0: BitBufferHelper.writeBits(5, 8); // total header length 5 bytes michael@0: BitBufferHelper.writeBits(PDU_IEI_CONCATENATED_SHORT_MESSAGES_8BIT, 8); // header id 0 michael@0: BitBufferHelper.writeBits(3, 8); // length of element for id 0 is 3 michael@0: BitBufferHelper.writeBits(options.segmentRef & 0xFF, 8); // Segement reference michael@0: BitBufferHelper.writeBits(options.segmentMaxSeq & 0xFF, 8); // Max segment michael@0: BitBufferHelper.writeBits(options.segmentSeq & 0xFF, 8); // Current segment michael@0: BitBufferHelper.writeBits(0, 1); // Padding to make header data septet(7-bit) aligned michael@0: } else { michael@0: if (options.encoding === PDU_CDMA_MSG_CODING_UNICODE) { michael@0: BitBufferHelper.writeBits(msgBodySize + 3, 8); // Required length for user data header, in 16-bit michael@0: } else { michael@0: BitBufferHelper.writeBits(msgBodySize + 6, 8); // Required length for user data header, in octet(8-bit) michael@0: } michael@0: michael@0: BitBufferHelper.writeBits(5, 8); // total header length 5 bytes michael@0: BitBufferHelper.writeBits(PDU_IEI_CONCATENATED_SHORT_MESSAGES_8BIT, 8); // header id 0 michael@0: BitBufferHelper.writeBits(3, 8); // length of element for id 0 is 3 michael@0: BitBufferHelper.writeBits(options.segmentRef & 0xFF, 8); // Segement reference michael@0: BitBufferHelper.writeBits(options.segmentMaxSeq & 0xFF, 8); // Max segment michael@0: BitBufferHelper.writeBits(options.segmentSeq & 0xFF, 8); // Current segment michael@0: } michael@0: } else { michael@0: BitBufferHelper.writeBits(msgBodySize, 8); michael@0: } michael@0: michael@0: // Encode message based on encoding method michael@0: const langTable = PDU_NL_LOCKING_SHIFT_TABLES[PDU_NL_IDENTIFIER_DEFAULT]; michael@0: const langShiftTable = PDU_NL_SINGLE_SHIFT_TABLES[PDU_NL_IDENTIFIER_DEFAULT]; michael@0: for (let i = 0; i < msgBody.length; i++) { michael@0: switch (options.encoding) { michael@0: case PDU_CDMA_MSG_CODING_OCTET: { michael@0: let msgDigit = msgBody.charCodeAt(i); michael@0: BitBufferHelper.writeBits(msgDigit, 8); michael@0: break; michael@0: } michael@0: case PDU_CDMA_MSG_CODING_7BITS_ASCII: { michael@0: let msgDigit = msgBody.charCodeAt(i), michael@0: msgDigitChar = msgBody.charAt(i); michael@0: michael@0: if (msgDigit >= 32) { michael@0: BitBufferHelper.writeBits(msgDigit, 7); michael@0: } else { michael@0: msgDigit = langTable.indexOf(msgDigitChar); michael@0: michael@0: if (msgDigit === PDU_NL_EXTENDED_ESCAPE) { michael@0: break; michael@0: } michael@0: if (msgDigit >= 0) { michael@0: BitBufferHelper.writeBits(msgDigit, 7); michael@0: } else { michael@0: msgDigit = langShiftTable.indexOf(msgDigitChar); michael@0: if (msgDigit == -1) { michael@0: throw new Error("'" + msgDigitChar + "' is not in 7 bit alphabet " michael@0: + langIndex + ":" + langShiftIndex + "!"); michael@0: } michael@0: michael@0: if (msgDigit === PDU_NL_RESERVED_CONTROL) { michael@0: break; michael@0: } michael@0: michael@0: BitBufferHelper.writeBits(PDU_NL_EXTENDED_ESCAPE, 7); michael@0: BitBufferHelper.writeBits(msgDigit, 7); michael@0: } michael@0: } michael@0: break; michael@0: } michael@0: case PDU_CDMA_MSG_CODING_UNICODE: { michael@0: let msgDigit = msgBody.charCodeAt(i); michael@0: BitBufferHelper.writeBits(msgDigit, 16); michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: BitBufferHelper.flushWithPadding(); michael@0: michael@0: // Fill length michael@0: let currentPosition = BitBufferHelper.getWriteBufferSize(); michael@0: BitBufferHelper.overwriteWriteBuffer(lengthPosition - 1, [currentPosition - lengthPosition]); michael@0: }, michael@0: michael@0: /** michael@0: * User data subparameter encoder : Reply Option michael@0: * michael@0: * @see 3GGP2 C.S0015-B 2.0, 4.5.11 Reply Option michael@0: */ michael@0: encodeUserDataReplyOption: function(options) { michael@0: if (options.requestStatusReport) { michael@0: let BitBufferHelper = this.context.BitBufferHelper; michael@0: BitBufferHelper.writeBits(PDU_CDMA_MSG_USERDATA_REPLY_OPTION, 8); michael@0: BitBufferHelper.writeBits(1, 8); michael@0: BitBufferHelper.writeBits(0, 1); // USER_ACK_REQ michael@0: BitBufferHelper.writeBits(1, 1); // DAK_REQ michael@0: BitBufferHelper.flushWithPadding(); michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Entry point for SMS decoding, the returned object is made compatible michael@0: * with existing readMessage() of GsmPDUHelper michael@0: */ michael@0: readMessage: function() { michael@0: let message = {}; michael@0: michael@0: // Teleservice Identifier michael@0: message.teleservice = this.readInt(); michael@0: michael@0: // Message Type michael@0: let isServicePresent = this.readByte(); michael@0: if (isServicePresent) { michael@0: message.messageType = PDU_CDMA_MSG_TYPE_BROADCAST; michael@0: } else { michael@0: if (message.teleservice) { michael@0: message.messageType = PDU_CDMA_MSG_TYPE_P2P; michael@0: } else { michael@0: message.messageType = PDU_CDMA_MSG_TYPE_ACK; michael@0: } michael@0: } michael@0: michael@0: // Service Category michael@0: message.service = this.readInt(); michael@0: michael@0: // Originated Address michael@0: let addrInfo = {}; michael@0: addrInfo.digitMode = (this.readInt() & 0x01); michael@0: addrInfo.numberMode = (this.readInt() & 0x01); michael@0: addrInfo.numberType = (this.readInt() & 0x01); michael@0: addrInfo.numberPlan = (this.readInt() & 0x01); michael@0: addrInfo.addrLength = this.readByte(); michael@0: addrInfo.address = []; michael@0: for (let i = 0; i < addrInfo.addrLength; i++) { michael@0: addrInfo.address.push(this.readByte()); michael@0: } michael@0: message.sender = this.decodeAddr(addrInfo); michael@0: michael@0: // Originated Subaddress michael@0: addrInfo.Type = (this.readInt() & 0x07); michael@0: addrInfo.Odd = (this.readByte() & 0x01); michael@0: addrInfo.addrLength = this.readByte(); michael@0: for (let i = 0; i < addrInfo.addrLength; i++) { michael@0: let addrDigit = this.readByte(); michael@0: message.sender += String.fromCharCode(addrDigit); michael@0: } michael@0: michael@0: // Bearer Data michael@0: this.decodeUserData(message); michael@0: michael@0: // Bearer Data Sub-Parameter: User Data michael@0: let userData = message[PDU_CDMA_MSG_USERDATA_BODY]; michael@0: [message.header, message.body, message.encoding, message.data] = michael@0: (userData) ? [userData.header, userData.body, userData.encoding, userData.data] michael@0: : [null, null, null, null]; michael@0: michael@0: // Bearer Data Sub-Parameter: Message Status michael@0: // Success Delivery (0) if both Message Status and User Data are absent. michael@0: // Message Status absent (-1) if only User Data is available. michael@0: let msgStatus = message[PDU_CDMA_MSG_USER_DATA_MSG_STATUS]; michael@0: [message.errorClass, message.msgStatus] = michael@0: (msgStatus) ? [msgStatus.errorClass, msgStatus.msgStatus] michael@0: : ((message.body) ? [-1, -1] : [0, 0]); michael@0: michael@0: // Transform message to GSM msg michael@0: let msg = { michael@0: SMSC: "", michael@0: mti: 0, michael@0: udhi: 0, michael@0: sender: message.sender, michael@0: recipient: null, michael@0: pid: PDU_PID_DEFAULT, michael@0: epid: PDU_PID_DEFAULT, michael@0: dcs: 0, michael@0: mwi: null, michael@0: replace: false, michael@0: header: message.header, michael@0: body: message.body, michael@0: data: message.data, michael@0: sentTimestamp: message[PDU_CDMA_MSG_USERDATA_TIMESTAMP], michael@0: language: message[PDU_CDMA_LANGUAGE_INDICATOR], michael@0: status: null, michael@0: scts: null, michael@0: dt: null, michael@0: encoding: message.encoding, michael@0: messageClass: GECKO_SMS_MESSAGE_CLASSES[PDU_DCS_MSG_CLASS_NORMAL], michael@0: messageType: message.messageType, michael@0: serviceCategory: message.service, michael@0: subMsgType: message[PDU_CDMA_MSG_USERDATA_MSG_ID].msgType, michael@0: msgId: message[PDU_CDMA_MSG_USERDATA_MSG_ID].msgId, michael@0: errorClass: message.errorClass, michael@0: msgStatus: message.msgStatus, michael@0: teleservice: message.teleservice michael@0: }; michael@0: michael@0: return msg; michael@0: }, michael@0: michael@0: /** michael@0: * Helper for processing received SMS parcel data. michael@0: * michael@0: * @param length michael@0: * Length of SMS string in the incoming parcel. michael@0: * michael@0: * @return Message parsed or null for invalid message. michael@0: */ michael@0: processReceivedSms: function(length) { michael@0: if (!length) { michael@0: if (DEBUG) this.context.debug("Received empty SMS!"); michael@0: return [null, PDU_FCS_UNSPECIFIED]; michael@0: } michael@0: michael@0: let message = this.readMessage(); michael@0: if (DEBUG) this.context.debug("Got new SMS: " + JSON.stringify(message)); michael@0: michael@0: // Determine result michael@0: if (!message) { michael@0: return [null, PDU_FCS_UNSPECIFIED]; michael@0: } michael@0: michael@0: return [message, PDU_FCS_OK]; michael@0: }, michael@0: michael@0: /** michael@0: * Data readers michael@0: */ michael@0: readInt: function() { michael@0: return this.context.Buf.readInt32(); michael@0: }, michael@0: michael@0: readByte: function() { michael@0: return (this.context.Buf.readInt32() & 0xFF); michael@0: }, michael@0: michael@0: /** michael@0: * Decode CDMA address data into address string michael@0: * michael@0: * @see 3GGP2 C.S0015-B 2.0, 3.4.3.3 Address Parameters michael@0: * michael@0: * Required key in addrInfo michael@0: * @param addrLength michael@0: * Length of address michael@0: * @param digitMode michael@0: * Address encoding method michael@0: * @param address michael@0: * Array of encoded address data michael@0: */ michael@0: decodeAddr: function(addrInfo) { michael@0: let result = ""; michael@0: for (let i = 0; i < addrInfo.addrLength; i++) { michael@0: if (addrInfo.digitMode === PDU_CDMA_MSG_ADDR_DIGIT_MODE_DTMF) { michael@0: result += this.dtmfChars.charAt(addrInfo.address[i]); michael@0: } else { michael@0: result += String.fromCharCode(addrInfo.address[i]); michael@0: } michael@0: } michael@0: return result; michael@0: }, michael@0: michael@0: /** michael@0: * Read userData in parcel buffer and decode into message object. michael@0: * Each subparameter will be stored in corresponding key. michael@0: * michael@0: * @see 3GGP2 C.S0015-B 2.0, 3.4.3.7 Bearer Data michael@0: * 4.5 Bearer Data Parameters michael@0: */ michael@0: decodeUserData: function(message) { michael@0: let userDataLength = this.readInt(); michael@0: michael@0: while (userDataLength > 0) { michael@0: let id = this.readByte(), michael@0: length = this.readByte(), michael@0: userDataBuffer = []; michael@0: michael@0: for (let i = 0; i < length; i++) { michael@0: userDataBuffer.push(this.readByte()); michael@0: } michael@0: michael@0: this.context.BitBufferHelper.startRead(userDataBuffer); michael@0: michael@0: switch (id) { michael@0: case PDU_CDMA_MSG_USERDATA_MSG_ID: michael@0: message[id] = this.decodeUserDataMsgId(); michael@0: break; michael@0: case PDU_CDMA_MSG_USERDATA_BODY: michael@0: message[id] = this.decodeUserDataMsg(message[PDU_CDMA_MSG_USERDATA_MSG_ID].userHeader); michael@0: break; michael@0: case PDU_CDMA_MSG_USERDATA_TIMESTAMP: michael@0: message[id] = this.decodeUserDataTimestamp(); michael@0: break; michael@0: case PDU_CDMA_MSG_USERDATA_REPLY_OPTION: michael@0: message[id] = this.decodeUserDataReplyOption(); michael@0: break; michael@0: case PDU_CDMA_LANGUAGE_INDICATOR: michael@0: message[id] = this.decodeLanguageIndicator(); michael@0: break; michael@0: case PDU_CDMA_MSG_USERDATA_CALLBACK_NUMBER: michael@0: message[id] = this.decodeUserDataCallbackNumber(); michael@0: break; michael@0: case PDU_CDMA_MSG_USER_DATA_MSG_STATUS: michael@0: message[id] = this.decodeUserDataMsgStatus(); michael@0: break; michael@0: } michael@0: michael@0: userDataLength -= (length + 2); michael@0: userDataBuffer = []; michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * User data subparameter decoder: Message Identifier michael@0: * michael@0: * @see 3GGP2 C.S0015-B 2.0, 4.5.1 Message Identifier michael@0: */ michael@0: decodeUserDataMsgId: function() { michael@0: let result = {}; michael@0: let BitBufferHelper = this.context.BitBufferHelper; michael@0: result.msgType = BitBufferHelper.readBits(4); michael@0: result.msgId = BitBufferHelper.readBits(16); michael@0: result.userHeader = BitBufferHelper.readBits(1); michael@0: michael@0: return result; michael@0: }, michael@0: michael@0: /** michael@0: * Decode user data header, we only care about segment information michael@0: * on CDMA. michael@0: * michael@0: * This function is mostly copied from gsmPduHelper.readUserDataHeader() but michael@0: * change the read function, because CDMA user header decoding is't byte-wise michael@0: * aligned. michael@0: */ michael@0: decodeUserDataHeader: function(encoding) { michael@0: let BitBufferHelper = this.context.BitBufferHelper; michael@0: let header = {}, michael@0: headerSize = BitBufferHelper.readBits(8), michael@0: userDataHeaderSize = headerSize + 1, michael@0: headerPaddingBits = 0; michael@0: michael@0: // Calculate header size michael@0: if (encoding === PDU_DCS_MSG_CODING_7BITS_ALPHABET) { michael@0: // Length is in 7-bit michael@0: header.length = Math.ceil(userDataHeaderSize * 8 / 7); michael@0: // Calulate padding length michael@0: headerPaddingBits = (header.length * 7) - (userDataHeaderSize * 8); michael@0: } else if (encoding === PDU_DCS_MSG_CODING_8BITS_ALPHABET) { michael@0: header.length = userDataHeaderSize; michael@0: } else { michael@0: header.length = userDataHeaderSize / 2; michael@0: } michael@0: michael@0: while (headerSize) { michael@0: let identifier = BitBufferHelper.readBits(8), michael@0: length = BitBufferHelper.readBits(8); michael@0: michael@0: headerSize -= (2 + length); michael@0: michael@0: switch (identifier) { michael@0: case PDU_IEI_CONCATENATED_SHORT_MESSAGES_8BIT: { michael@0: let ref = BitBufferHelper.readBits(8), michael@0: max = BitBufferHelper.readBits(8), michael@0: seq = BitBufferHelper.readBits(8); michael@0: if (max && seq && (seq <= max)) { michael@0: header.segmentRef = ref; michael@0: header.segmentMaxSeq = max; michael@0: header.segmentSeq = seq; michael@0: } michael@0: break; michael@0: } michael@0: case PDU_IEI_APPLICATION_PORT_ADDRESSING_SCHEME_8BIT: { michael@0: let dstp = BitBufferHelper.readBits(8), michael@0: orip = BitBufferHelper.readBits(8); michael@0: if ((dstp < PDU_APA_RESERVED_8BIT_PORTS) michael@0: || (orip < PDU_APA_RESERVED_8BIT_PORTS)) { michael@0: // 3GPP TS 23.040 clause 9.2.3.24.3: "A receiving entity shall michael@0: // ignore any information element where the value of the michael@0: // Information-Element-Data is Reserved or not supported" michael@0: break; michael@0: } michael@0: header.destinationPort = dstp; michael@0: header.originatorPort = orip; michael@0: break; michael@0: } michael@0: case PDU_IEI_APPLICATION_PORT_ADDRESSING_SCHEME_16BIT: { michael@0: let dstp = (BitBufferHelper.readBits(8) << 8) | BitBufferHelper.readBits(8), michael@0: orip = (BitBufferHelper.readBits(8) << 8) | BitBufferHelper.readBits(8); michael@0: // 3GPP TS 23.040 clause 9.2.3.24.4: "A receiving entity shall michael@0: // ignore any information element where the value of the michael@0: // Information-Element-Data is Reserved or not supported" michael@0: if ((dstp < PDU_APA_VALID_16BIT_PORTS) michael@0: && (orip < PDU_APA_VALID_16BIT_PORTS)) { michael@0: header.destinationPort = dstp; michael@0: header.originatorPort = orip; michael@0: } michael@0: break; michael@0: } michael@0: case PDU_IEI_CONCATENATED_SHORT_MESSAGES_16BIT: { michael@0: let ref = (BitBufferHelper.readBits(8) << 8) | BitBufferHelper.readBits(8), michael@0: max = BitBufferHelper.readBits(8), michael@0: seq = BitBufferHelper.readBits(8); michael@0: if (max && seq && (seq <= max)) { michael@0: header.segmentRef = ref; michael@0: header.segmentMaxSeq = max; michael@0: header.segmentSeq = seq; michael@0: } michael@0: break; michael@0: } michael@0: case PDU_IEI_NATIONAL_LANGUAGE_SINGLE_SHIFT: { michael@0: let langShiftIndex = BitBufferHelper.readBits(8); michael@0: if (langShiftIndex < PDU_NL_SINGLE_SHIFT_TABLES.length) { michael@0: header.langShiftIndex = langShiftIndex; michael@0: } michael@0: break; michael@0: } michael@0: case PDU_IEI_NATIONAL_LANGUAGE_LOCKING_SHIFT: { michael@0: let langIndex = BitBufferHelper.readBits(8); michael@0: if (langIndex < PDU_NL_LOCKING_SHIFT_TABLES.length) { michael@0: header.langIndex = langIndex; michael@0: } michael@0: break; michael@0: } michael@0: case PDU_IEI_SPECIAL_SMS_MESSAGE_INDICATION: { michael@0: let msgInd = BitBufferHelper.readBits(8) & 0xFF, michael@0: msgCount = BitBufferHelper.readBits(8); michael@0: /* michael@0: * TS 23.040 V6.8.1 Sec 9.2.3.24.2 michael@0: * bits 1 0 : basic message indication type michael@0: * bits 4 3 2 : extended message indication type michael@0: * bits 6 5 : Profile id michael@0: * bit 7 : storage type michael@0: */ michael@0: let storeType = msgInd & PDU_MWI_STORE_TYPE_BIT; michael@0: header.mwi = {}; michael@0: mwi = header.mwi; michael@0: michael@0: if (storeType == PDU_MWI_STORE_TYPE_STORE) { michael@0: // Store message because TP_UDH indicates so, note this may override michael@0: // the setting in DCS, but that is expected michael@0: mwi.discard = false; michael@0: } else if (mwi.discard === undefined) { michael@0: // storeType == PDU_MWI_STORE_TYPE_DISCARD michael@0: // only override mwi.discard here if it hasn't already been set michael@0: mwi.discard = true; michael@0: } michael@0: michael@0: mwi.msgCount = msgCount & 0xFF; michael@0: mwi.active = mwi.msgCount > 0; michael@0: michael@0: if (DEBUG) { michael@0: this.context.debug("MWI in TP_UDH received: " + JSON.stringify(mwi)); michael@0: } michael@0: break; michael@0: } michael@0: default: michael@0: // Drop unsupported id michael@0: for (let i = 0; i < length; i++) { michael@0: BitBufferHelper.readBits(8); michael@0: } michael@0: } michael@0: } michael@0: michael@0: // Consume padding bits michael@0: if (headerPaddingBits) { michael@0: BitBufferHelper.readBits(headerPaddingBits); michael@0: } michael@0: michael@0: return header; michael@0: }, michael@0: michael@0: getCdmaMsgEncoding: function(encoding) { michael@0: // Determine encoding method michael@0: switch (encoding) { michael@0: case PDU_CDMA_MSG_CODING_7BITS_ASCII: michael@0: case PDU_CDMA_MSG_CODING_IA5: michael@0: case PDU_CDMA_MSG_CODING_7BITS_GSM: michael@0: return PDU_DCS_MSG_CODING_7BITS_ALPHABET; michael@0: case PDU_CDMA_MSG_CODING_OCTET: michael@0: case PDU_CDMA_MSG_CODING_IS_91: michael@0: case PDU_CDMA_MSG_CODING_LATIN_HEBREW: michael@0: case PDU_CDMA_MSG_CODING_LATIN: michael@0: return PDU_DCS_MSG_CODING_8BITS_ALPHABET; michael@0: case PDU_CDMA_MSG_CODING_UNICODE: michael@0: case PDU_CDMA_MSG_CODING_SHIFT_JIS: michael@0: case PDU_CDMA_MSG_CODING_KOREAN: michael@0: return PDU_DCS_MSG_CODING_16BITS_ALPHABET; michael@0: } michael@0: return null; michael@0: }, michael@0: michael@0: decodeCdmaPDUMsg: function(encoding, msgType, msgBodySize) { michael@0: const langTable = PDU_NL_LOCKING_SHIFT_TABLES[PDU_NL_IDENTIFIER_DEFAULT]; michael@0: const langShiftTable = PDU_NL_SINGLE_SHIFT_TABLES[PDU_NL_IDENTIFIER_DEFAULT]; michael@0: let BitBufferHelper = this.context.BitBufferHelper; michael@0: let result = ""; michael@0: let msgDigit; michael@0: switch (encoding) { michael@0: case PDU_CDMA_MSG_CODING_OCTET: // TODO : Require Test michael@0: while(msgBodySize > 0) { michael@0: msgDigit = String.fromCharCode(BitBufferHelper.readBits(8)); michael@0: result += msgDigit; michael@0: msgBodySize--; michael@0: } michael@0: break; michael@0: case PDU_CDMA_MSG_CODING_IS_91: // TODO : Require Test michael@0: // Referenced from android code michael@0: switch (msgType) { michael@0: case PDU_CDMA_MSG_CODING_IS_91_TYPE_SMS: michael@0: case PDU_CDMA_MSG_CODING_IS_91_TYPE_SMS_FULL: michael@0: case PDU_CDMA_MSG_CODING_IS_91_TYPE_VOICEMAIL_STATUS: michael@0: while(msgBodySize > 0) { michael@0: msgDigit = String.fromCharCode(BitBufferHelper.readBits(6) + 0x20); michael@0: result += msgDigit; michael@0: msgBodySize--; michael@0: } michael@0: break; michael@0: case PDU_CDMA_MSG_CODING_IS_91_TYPE_CLI: michael@0: let addrInfo = {}; michael@0: addrInfo.digitMode = PDU_CDMA_MSG_ADDR_DIGIT_MODE_DTMF; michael@0: addrInfo.numberMode = PDU_CDMA_MSG_ADDR_NUMBER_MODE_ANSI; michael@0: addrInfo.numberType = PDU_CDMA_MSG_ADDR_NUMBER_TYPE_UNKNOWN; michael@0: addrInfo.numberPlan = PDU_CDMA_MSG_ADDR_NUMBER_PLAN_UNKNOWN; michael@0: addrInfo.addrLength = msgBodySize; michael@0: addrInfo.address = []; michael@0: for (let i = 0; i < addrInfo.addrLength; i++) { michael@0: addrInfo.address.push(BitBufferHelper.readBits(4)); michael@0: } michael@0: result = this.decodeAddr(addrInfo); michael@0: break; michael@0: } michael@0: // Fall through. michael@0: case PDU_CDMA_MSG_CODING_7BITS_ASCII: michael@0: case PDU_CDMA_MSG_CODING_IA5: // TODO : Require Test michael@0: while(msgBodySize > 0) { michael@0: msgDigit = BitBufferHelper.readBits(7); michael@0: if (msgDigit >= 32) { michael@0: msgDigit = String.fromCharCode(msgDigit); michael@0: } else { michael@0: if (msgDigit !== PDU_NL_EXTENDED_ESCAPE) { michael@0: msgDigit = langTable[msgDigit]; michael@0: } else { michael@0: msgDigit = BitBufferHelper.readBits(7); michael@0: msgBodySize--; michael@0: msgDigit = langShiftTable[msgDigit]; michael@0: } michael@0: } michael@0: result += msgDigit; michael@0: msgBodySize--; michael@0: } michael@0: break; michael@0: case PDU_CDMA_MSG_CODING_UNICODE: michael@0: while(msgBodySize > 0) { michael@0: msgDigit = String.fromCharCode(BitBufferHelper.readBits(16)); michael@0: result += msgDigit; michael@0: msgBodySize--; michael@0: } michael@0: break; michael@0: case PDU_CDMA_MSG_CODING_7BITS_GSM: // TODO : Require Test michael@0: while(msgBodySize > 0) { michael@0: msgDigit = BitBufferHelper.readBits(7); michael@0: if (msgDigit !== PDU_NL_EXTENDED_ESCAPE) { michael@0: msgDigit = langTable[msgDigit]; michael@0: } else { michael@0: msgDigit = BitBufferHelper.readBits(7); michael@0: msgBodySize--; michael@0: msgDigit = langShiftTable[msgDigit]; michael@0: } michael@0: result += msgDigit; michael@0: msgBodySize--; michael@0: } michael@0: break; michael@0: case PDU_CDMA_MSG_CODING_LATIN: // TODO : Require Test michael@0: // Reference : http://en.wikipedia.org/wiki/ISO/IEC_8859-1 michael@0: while(msgBodySize > 0) { michael@0: msgDigit = String.fromCharCode(BitBufferHelper.readBits(8)); michael@0: result += msgDigit; michael@0: msgBodySize--; michael@0: } michael@0: break; michael@0: case PDU_CDMA_MSG_CODING_LATIN_HEBREW: // TODO : Require Test michael@0: // Reference : http://en.wikipedia.org/wiki/ISO/IEC_8859-8 michael@0: while(msgBodySize > 0) { michael@0: msgDigit = BitBufferHelper.readBits(8); michael@0: if (msgDigit === 0xDF) { michael@0: msgDigit = String.fromCharCode(0x2017); michael@0: } else if (msgDigit === 0xFD) { michael@0: msgDigit = String.fromCharCode(0x200E); michael@0: } else if (msgDigit === 0xFE) { michael@0: msgDigit = String.fromCharCode(0x200F); michael@0: } else if (msgDigit >= 0xE0 && msgDigit <= 0xFA) { michael@0: msgDigit = String.fromCharCode(0x4F0 + msgDigit); michael@0: } else { michael@0: msgDigit = String.fromCharCode(msgDigit); michael@0: } michael@0: result += msgDigit; michael@0: msgBodySize--; michael@0: } michael@0: break; michael@0: case PDU_CDMA_MSG_CODING_SHIFT_JIS: michael@0: // Reference : http://msdn.microsoft.com/en-US/goglobal/cc305152.aspx michael@0: // http://demo.icu-project.org/icu-bin/convexp?conv=Shift_JIS michael@0: let shift_jis_message = []; michael@0: michael@0: while (msgBodySize > 0) { michael@0: shift_jis_message.push(BitBufferHelper.readBits(8)); michael@0: msgBodySize--; michael@0: } michael@0: michael@0: let decoder = new TextDecoder("shift_jis"); michael@0: result = decoder.decode(new Uint8Array(shift_jis_message)); michael@0: break; michael@0: case PDU_CDMA_MSG_CODING_KOREAN: michael@0: case PDU_CDMA_MSG_CODING_GSM_DCS: michael@0: // Fall through. michael@0: default: michael@0: break; michael@0: } michael@0: return result; michael@0: }, michael@0: michael@0: /** michael@0: * User data subparameter decoder : User Data michael@0: * michael@0: * @see 3GGP2 C.S0015-B 2.0, 4.5.2 User Data michael@0: */ michael@0: decodeUserDataMsg: function(hasUserHeader) { michael@0: let BitBufferHelper = this.context.BitBufferHelper; michael@0: let result = {}, michael@0: encoding = BitBufferHelper.readBits(5), michael@0: msgType; michael@0: michael@0: if (encoding === PDU_CDMA_MSG_CODING_IS_91) { michael@0: msgType = BitBufferHelper.readBits(8); michael@0: } michael@0: result.encoding = this.getCdmaMsgEncoding(encoding); michael@0: michael@0: let msgBodySize = BitBufferHelper.readBits(8); michael@0: michael@0: // For segmented SMS, a user header is included before sms content michael@0: if (hasUserHeader) { michael@0: result.header = this.decodeUserDataHeader(result.encoding); michael@0: // header size is included in body size, they are decoded michael@0: msgBodySize -= result.header.length; michael@0: } michael@0: michael@0: // Store original payload if enconding is OCTET for further handling of WAP Push, etc. michael@0: if (encoding === PDU_CDMA_MSG_CODING_OCTET && msgBodySize > 0) { michael@0: result.data = new Uint8Array(msgBodySize); michael@0: for (let i = 0; i < msgBodySize; i++) { michael@0: result.data[i] = BitBufferHelper.readBits(8); michael@0: } michael@0: BitBufferHelper.backwardReadPilot(8 * msgBodySize); michael@0: } michael@0: michael@0: // Decode sms content michael@0: result.body = this.decodeCdmaPDUMsg(encoding, msgType, msgBodySize); michael@0: michael@0: return result; michael@0: }, michael@0: michael@0: decodeBcd: function(value) { michael@0: return ((value >> 4) & 0xF) * 10 + (value & 0x0F); michael@0: }, michael@0: michael@0: /** michael@0: * User data subparameter decoder : Time Stamp michael@0: * michael@0: * @see 3GGP2 C.S0015-B 2.0, 4.5.4 Message Center Time Stamp michael@0: */ michael@0: decodeUserDataTimestamp: function() { michael@0: let BitBufferHelper = this.context.BitBufferHelper; michael@0: let year = this.decodeBcd(BitBufferHelper.readBits(8)), michael@0: month = this.decodeBcd(BitBufferHelper.readBits(8)) - 1, michael@0: date = this.decodeBcd(BitBufferHelper.readBits(8)), michael@0: hour = this.decodeBcd(BitBufferHelper.readBits(8)), michael@0: min = this.decodeBcd(BitBufferHelper.readBits(8)), michael@0: sec = this.decodeBcd(BitBufferHelper.readBits(8)); michael@0: michael@0: if (year >= 96 && year <= 99) { michael@0: year += 1900; michael@0: } else { michael@0: year += 2000; michael@0: } michael@0: michael@0: let result = (new Date(year, month, date, hour, min, sec, 0)).valueOf(); michael@0: michael@0: return result; michael@0: }, michael@0: michael@0: /** michael@0: * User data subparameter decoder : Reply Option michael@0: * michael@0: * @see 3GGP2 C.S0015-B 2.0, 4.5.11 Reply Option michael@0: */ michael@0: decodeUserDataReplyOption: function() { michael@0: let replyAction = this.context.BitBufferHelper.readBits(4), michael@0: result = { userAck: (replyAction & 0x8) ? true : false, michael@0: deliverAck: (replyAction & 0x4) ? true : false, michael@0: readAck: (replyAction & 0x2) ? true : false, michael@0: report: (replyAction & 0x1) ? true : false michael@0: }; michael@0: michael@0: return result; michael@0: }, michael@0: michael@0: /** michael@0: * User data subparameter decoder : Language Indicator michael@0: * michael@0: * @see 3GGP2 C.S0015-B 2.0, 4.5.14 Language Indicator michael@0: */ michael@0: decodeLanguageIndicator: function() { michael@0: let language = this.context.BitBufferHelper.readBits(8); michael@0: let result = CB_CDMA_LANG_GROUP[language]; michael@0: return result; michael@0: }, michael@0: michael@0: /** michael@0: * User data subparameter decoder : Call-Back Number michael@0: * michael@0: * @see 3GGP2 C.S0015-B 2.0, 4.5.15 Call-Back Number michael@0: */ michael@0: decodeUserDataCallbackNumber: function() { michael@0: let BitBufferHelper = this.context.BitBufferHelper; michael@0: let digitMode = BitBufferHelper.readBits(1); michael@0: if (digitMode) { michael@0: let numberType = BitBufferHelper.readBits(3), michael@0: numberPlan = BitBufferHelper.readBits(4); michael@0: } michael@0: let numberFields = BitBufferHelper.readBits(8), michael@0: result = ""; michael@0: for (let i = 0; i < numberFields; i++) { michael@0: if (digitMode === PDU_CDMA_MSG_ADDR_DIGIT_MODE_DTMF) { michael@0: let addrDigit = BitBufferHelper.readBits(4); michael@0: result += this.dtmfChars.charAt(addrDigit); michael@0: } else { michael@0: let addrDigit = BitBufferHelper.readBits(8); michael@0: result += String.fromCharCode(addrDigit); michael@0: } michael@0: } michael@0: michael@0: return result; michael@0: }, michael@0: michael@0: /** michael@0: * User data subparameter decoder : Message Status michael@0: * michael@0: * @see 3GGP2 C.S0015-B 2.0, 4.5.21 Message Status michael@0: */ michael@0: decodeUserDataMsgStatus: function() { michael@0: let BitBufferHelper = this.context.BitBufferHelper; michael@0: let result = { michael@0: errorClass: BitBufferHelper.readBits(2), michael@0: msgStatus: BitBufferHelper.readBits(6) michael@0: }; michael@0: michael@0: return result; michael@0: }, michael@0: michael@0: /** michael@0: * Decode information record parcel. michael@0: */ michael@0: decodeInformationRecord: function() { michael@0: let Buf = this.context.Buf; michael@0: let record = {}; michael@0: let numOfRecords = Buf.readInt32(); michael@0: michael@0: let type; michael@0: for (let i = 0; i < numOfRecords; i++) { michael@0: type = Buf.readInt32(); michael@0: michael@0: switch (type) { michael@0: /* michael@0: * Every type is encaped by ril, except extended display michael@0: */ michael@0: case PDU_CDMA_INFO_REC_TYPE_DISPLAY: michael@0: record.display = Buf.readString(); michael@0: break; michael@0: case PDU_CDMA_INFO_REC_TYPE_CALLED_PARTY_NUMBER: michael@0: record.calledNumber = {}; michael@0: record.calledNumber.number = Buf.readString(); michael@0: record.calledNumber.type = Buf.readInt32(); michael@0: record.calledNumber.plan = Buf.readInt32(); michael@0: record.calledNumber.pi = Buf.readInt32(); michael@0: record.calledNumber.si = Buf.readInt32(); michael@0: break; michael@0: case PDU_CDMA_INFO_REC_TYPE_CALLING_PARTY_NUMBER: michael@0: record.callingNumber = {}; michael@0: record.callingNumber.number = Buf.readString(); michael@0: record.callingNumber.type = Buf.readInt32(); michael@0: record.callingNumber.plan = Buf.readInt32(); michael@0: record.callingNumber.pi = Buf.readInt32(); michael@0: record.callingNumber.si = Buf.readInt32(); michael@0: break; michael@0: case PDU_CDMA_INFO_REC_TYPE_CONNECTED_NUMBER: michael@0: record.connectedNumber = {}; michael@0: record.connectedNumber.number = Buf.readString(); michael@0: record.connectedNumber.type = Buf.readInt32(); michael@0: record.connectedNumber.plan = Buf.readInt32(); michael@0: record.connectedNumber.pi = Buf.readInt32(); michael@0: record.connectedNumber.si = Buf.readInt32(); michael@0: break; michael@0: case PDU_CDMA_INFO_REC_TYPE_SIGNAL: michael@0: record.signal = {}; michael@0: record.signal.present = Buf.readInt32(); michael@0: record.signal.type = Buf.readInt32(); michael@0: record.signal.alertPitch = Buf.readInt32(); michael@0: record.signal.signal = Buf.readInt32(); michael@0: break; michael@0: case PDU_CDMA_INFO_REC_TYPE_REDIRECTING_NUMBER: michael@0: record.redirect = {}; michael@0: record.redirect.number = Buf.readString(); michael@0: record.redirect.type = Buf.readInt32(); michael@0: record.redirect.plan = Buf.readInt32(); michael@0: record.redirect.pi = Buf.readInt32(); michael@0: record.redirect.si = Buf.readInt32(); michael@0: record.redirect.reason = Buf.readInt32(); michael@0: break; michael@0: case PDU_CDMA_INFO_REC_TYPE_LINE_CONTROL: michael@0: record.lineControl = {}; michael@0: record.lineControl.polarityIncluded = Buf.readInt32(); michael@0: record.lineControl.toggle = Buf.readInt32(); michael@0: record.lineControl.recerse = Buf.readInt32(); michael@0: record.lineControl.powerDenial = Buf.readInt32(); michael@0: break; michael@0: case PDU_CDMA_INFO_REC_TYPE_EXTENDED_DISPLAY: michael@0: let length = Buf.readInt32(); michael@0: /* michael@0: * Extended display is still in format defined in michael@0: * C.S0005-F v1.0, 3.7.5.16 michael@0: */ michael@0: record.extendedDisplay = {}; michael@0: michael@0: let headerByte = Buf.readInt32(); michael@0: length--; michael@0: // Based on spec, headerByte must be 0x80 now michael@0: record.extendedDisplay.indicator = (headerByte >> 7); michael@0: record.extendedDisplay.type = (headerByte & 0x7F); michael@0: record.extendedDisplay.records = []; michael@0: michael@0: while (length > 0) { michael@0: let display = {}; michael@0: michael@0: display.tag = Buf.readInt32(); michael@0: length--; michael@0: if (display.tag !== INFO_REC_EXTENDED_DISPLAY_BLANK && michael@0: display.tag !== INFO_REC_EXTENDED_DISPLAY_SKIP) { michael@0: display.content = Buf.readString(); michael@0: length -= (display.content.length + 1); michael@0: } michael@0: michael@0: record.extendedDisplay.records.push(display); michael@0: } michael@0: break; michael@0: case PDU_CDMA_INFO_REC_TYPE_T53_CLIR: michael@0: record.cause = Buf.readInt32(); michael@0: break; michael@0: case PDU_CDMA_INFO_REC_TYPE_T53_AUDIO_CONTROL: michael@0: record.audioControl = {}; michael@0: record.audioControl.upLink = Buf.readInt32(); michael@0: record.audioControl.downLink = Buf.readInt32(); michael@0: break; michael@0: case PDU_CDMA_INFO_REC_TYPE_T53_RELEASE: michael@0: // Fall through michael@0: default: michael@0: throw new Error("UNSOLICITED_CDMA_INFO_REC(), Unsupported information record type " + record.type + "\n"); michael@0: } michael@0: } michael@0: michael@0: return record; michael@0: } michael@0: }; michael@0: michael@0: /** michael@0: * Helper for processing ICC PDUs. michael@0: */ michael@0: function ICCPDUHelperObject(aContext) { michael@0: this.context = aContext; michael@0: } michael@0: ICCPDUHelperObject.prototype = { michael@0: context: null, michael@0: michael@0: /** michael@0: * Read GSM 8-bit unpacked octets, michael@0: * which are default 7-bit alphabets with bit 8 set to 0. michael@0: * michael@0: * @param numOctets michael@0: * Number of octets to be read. michael@0: */ michael@0: read8BitUnpackedToString: function(numOctets) { michael@0: let GsmPDUHelper = this.context.GsmPDUHelper; michael@0: michael@0: let ret = ""; michael@0: let escapeFound = false; michael@0: let i; michael@0: const langTable = PDU_NL_LOCKING_SHIFT_TABLES[PDU_NL_IDENTIFIER_DEFAULT]; michael@0: const langShiftTable = PDU_NL_SINGLE_SHIFT_TABLES[PDU_NL_IDENTIFIER_DEFAULT]; michael@0: michael@0: for(i = 0; i < numOctets; i++) { michael@0: let octet = GsmPDUHelper.readHexOctet(); michael@0: if (octet == 0xff) { michael@0: i++; michael@0: break; michael@0: } michael@0: michael@0: if (escapeFound) { michael@0: escapeFound = false; michael@0: if (octet == PDU_NL_EXTENDED_ESCAPE) { michael@0: // According to 3GPP TS 23.038, section 6.2.1.1, NOTE 1, "On michael@0: // receipt of this code, a receiving entity shall display a space michael@0: // until another extensiion table is defined." michael@0: ret += " "; michael@0: } else if (octet == PDU_NL_RESERVED_CONTROL) { michael@0: // According to 3GPP TS 23.038 B.2, "This code represents a control michael@0: // character and therefore must not be used for language specific michael@0: // characters." michael@0: ret += " "; michael@0: } else { michael@0: ret += langShiftTable[octet]; michael@0: } michael@0: } else if (octet == PDU_NL_EXTENDED_ESCAPE) { michael@0: escapeFound = true; michael@0: } else { michael@0: ret += langTable[octet]; michael@0: } michael@0: } michael@0: michael@0: let Buf = this.context.Buf; michael@0: Buf.seekIncoming((numOctets - i) * Buf.PDU_HEX_OCTET_SIZE); michael@0: return ret; michael@0: }, michael@0: michael@0: /** michael@0: * Write GSM 8-bit unpacked octets. michael@0: * michael@0: * @param numOctets Number of total octets to be writen, including trailing michael@0: * 0xff. michael@0: * @param str String to be written. Could be null. michael@0: */ michael@0: writeStringTo8BitUnpacked: function(numOctets, str) { michael@0: const langTable = PDU_NL_LOCKING_SHIFT_TABLES[PDU_NL_IDENTIFIER_DEFAULT]; michael@0: const langShiftTable = PDU_NL_SINGLE_SHIFT_TABLES[PDU_NL_IDENTIFIER_DEFAULT]; michael@0: michael@0: let GsmPDUHelper = this.context.GsmPDUHelper; michael@0: michael@0: // If the character is GSM extended alphabet, two octets will be written. michael@0: // So we need to keep track of number of octets to be written. michael@0: let i, j; michael@0: let len = str ? str.length : 0; michael@0: for (i = 0, j = 0; i < len && j < numOctets; i++) { michael@0: let c = str.charAt(i); michael@0: let octet = langTable.indexOf(c); michael@0: michael@0: if (octet == -1) { michael@0: // Make sure we still have enough space to write two octets. michael@0: if (j + 2 > numOctets) { michael@0: break; michael@0: } michael@0: michael@0: octet = langShiftTable.indexOf(c); michael@0: if (octet == -1) { michael@0: // Fallback to ASCII space. michael@0: octet = langTable.indexOf(' '); michael@0: } michael@0: GsmPDUHelper.writeHexOctet(PDU_NL_EXTENDED_ESCAPE); michael@0: j++; michael@0: } michael@0: GsmPDUHelper.writeHexOctet(octet); michael@0: j++; michael@0: } michael@0: michael@0: // trailing 0xff michael@0: while (j++ < numOctets) { michael@0: GsmPDUHelper.writeHexOctet(0xff); michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Read UCS2 String on UICC. michael@0: * michael@0: * @see TS 101.221, Annex A. michael@0: * @param scheme michael@0: * Coding scheme for UCS2 on UICC. One of 0x80, 0x81 or 0x82. michael@0: * @param numOctets michael@0: * Number of octets to be read as UCS2 string. michael@0: */ michael@0: readICCUCS2String: function(scheme, numOctets) { michael@0: let Buf = this.context.Buf; michael@0: let GsmPDUHelper = this.context.GsmPDUHelper; michael@0: michael@0: let str = ""; michael@0: switch (scheme) { michael@0: /** michael@0: * +------+---------+---------+---------+---------+------+------+ michael@0: * | 0x80 | Ch1_msb | Ch1_lsb | Ch2_msb | Ch2_lsb | 0xff | 0xff | michael@0: * +------+---------+---------+---------+---------+------+------+ michael@0: */ michael@0: case 0x80: michael@0: let isOdd = numOctets % 2; michael@0: let i; michael@0: for (i = 0; i < numOctets - isOdd; i += 2) { michael@0: let code = (GsmPDUHelper.readHexOctet() << 8) | GsmPDUHelper.readHexOctet(); michael@0: if (code == 0xffff) { michael@0: i += 2; michael@0: break; michael@0: } michael@0: str += String.fromCharCode(code); michael@0: } michael@0: michael@0: // Skip trailing 0xff michael@0: Buf.seekIncoming((numOctets - i) * Buf.PDU_HEX_OCTET_SIZE); michael@0: break; michael@0: case 0x81: // Fall through michael@0: case 0x82: michael@0: /** michael@0: * +------+-----+--------+-----+-----+-----+--------+------+ michael@0: * | 0x81 | len | offset | Ch1 | Ch2 | ... | Ch_len | 0xff | michael@0: * +------+-----+--------+-----+-----+-----+--------+------+ michael@0: * michael@0: * len : The length of characters. michael@0: * offset : 0hhh hhhh h000 0000 michael@0: * Ch_n: bit 8 = 0 michael@0: * GSM default alphabets michael@0: * bit 8 = 1 michael@0: * UCS2 character whose char code is (Ch_n & 0x7f) + offset michael@0: * michael@0: * +------+-----+------------+------------+-----+-----+-----+--------+ michael@0: * | 0x82 | len | offset_msb | offset_lsb | Ch1 | Ch2 | ... | Ch_len | michael@0: * +------+-----+------------+------------+-----+-----+-----+--------+ michael@0: * michael@0: * len : The length of characters. michael@0: * offset_msb, offset_lsn: offset michael@0: * Ch_n: bit 8 = 0 michael@0: * GSM default alphabets michael@0: * bit 8 = 1 michael@0: * UCS2 character whose char code is (Ch_n & 0x7f) + offset michael@0: */ michael@0: let len = GsmPDUHelper.readHexOctet(); michael@0: let offset, headerLen; michael@0: if (scheme == 0x81) { michael@0: offset = GsmPDUHelper.readHexOctet() << 7; michael@0: headerLen = 2; michael@0: } else { michael@0: offset = (GsmPDUHelper.readHexOctet() << 8) | GsmPDUHelper.readHexOctet(); michael@0: headerLen = 3; michael@0: } michael@0: michael@0: for (let i = 0; i < len; i++) { michael@0: let ch = GsmPDUHelper.readHexOctet(); michael@0: if (ch & 0x80) { michael@0: // UCS2 michael@0: str += String.fromCharCode((ch & 0x7f) + offset); michael@0: } else { michael@0: // GSM 8bit michael@0: let count = 0, gotUCS2 = 0; michael@0: while ((i + count + 1 < len)) { michael@0: count++; michael@0: if (GsmPDUHelper.readHexOctet() & 0x80) { michael@0: gotUCS2 = 1; michael@0: break; michael@0: } michael@0: } michael@0: // Unread. michael@0: // +1 for the GSM alphabet indexed at i, michael@0: Buf.seekIncoming(-1 * (count + 1) * Buf.PDU_HEX_OCTET_SIZE); michael@0: str += this.read8BitUnpackedToString(count + 1 - gotUCS2); michael@0: i += count - gotUCS2; michael@0: } michael@0: } michael@0: michael@0: // Skipping trailing 0xff michael@0: Buf.seekIncoming((numOctets - len - headerLen) * Buf.PDU_HEX_OCTET_SIZE); michael@0: break; michael@0: } michael@0: return str; michael@0: }, michael@0: michael@0: /** michael@0: * Read Alpha Id and Dialling number from TS TS 151.011 clause 10.5.1 michael@0: * michael@0: * @param recordSize The size of linear fixed record. michael@0: */ michael@0: readAlphaIdDiallingNumber: function(recordSize) { michael@0: let Buf = this.context.Buf; michael@0: let length = Buf.readInt32(); michael@0: michael@0: let alphaLen = recordSize - ADN_FOOTER_SIZE_BYTES; michael@0: let alphaId = this.readAlphaIdentifier(alphaLen); michael@0: michael@0: let number = this.readNumberWithLength(); michael@0: michael@0: // Skip 2 unused octets, CCP and EXT1. michael@0: Buf.seekIncoming(2 * Buf.PDU_HEX_OCTET_SIZE); michael@0: Buf.readStringDelimiter(length); michael@0: michael@0: let contact = null; michael@0: if (alphaId || number) { michael@0: contact = {alphaId: alphaId, michael@0: number: number}; michael@0: } michael@0: return contact; michael@0: }, michael@0: michael@0: /** michael@0: * Write Alpha Identifier and Dialling number from TS 151.011 clause 10.5.1 michael@0: * michael@0: * @param recordSize The size of linear fixed record. michael@0: * @param alphaId Alpha Identifier to be written. michael@0: * @param number Dialling Number to be written. michael@0: */ michael@0: writeAlphaIdDiallingNumber: function(recordSize, alphaId, number) { michael@0: let Buf = this.context.Buf; michael@0: let GsmPDUHelper = this.context.GsmPDUHelper; michael@0: michael@0: // Write String length michael@0: let strLen = recordSize * 2; michael@0: Buf.writeInt32(strLen); michael@0: michael@0: let alphaLen = recordSize - ADN_FOOTER_SIZE_BYTES; michael@0: this.writeAlphaIdentifier(alphaLen, alphaId); michael@0: this.writeNumberWithLength(number); michael@0: michael@0: // Write unused octets 0xff, CCP and EXT1. michael@0: GsmPDUHelper.writeHexOctet(0xff); michael@0: GsmPDUHelper.writeHexOctet(0xff); michael@0: Buf.writeStringDelimiter(strLen); michael@0: }, michael@0: michael@0: /** michael@0: * Read Alpha Identifier. michael@0: * michael@0: * @see TS 131.102 michael@0: * michael@0: * @param numOctets michael@0: * Number of octets to be read. michael@0: * michael@0: * It uses either michael@0: * 1. SMS default 7-bit alphabet with bit 8 set to 0. michael@0: * 2. UCS2 string. michael@0: * michael@0: * Unused bytes should be set to 0xff. michael@0: */ michael@0: readAlphaIdentifier: function(numOctets) { michael@0: if (numOctets === 0) { michael@0: return ""; michael@0: } michael@0: michael@0: let temp; michael@0: // Read the 1st octet to determine the encoding. michael@0: if ((temp = this.context.GsmPDUHelper.readHexOctet()) == 0x80 || michael@0: temp == 0x81 || michael@0: temp == 0x82) { michael@0: numOctets--; michael@0: return this.readICCUCS2String(temp, numOctets); michael@0: } else { michael@0: let Buf = this.context.Buf; michael@0: Buf.seekIncoming(-1 * Buf.PDU_HEX_OCTET_SIZE); michael@0: return this.read8BitUnpackedToString(numOctets); michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Write Alpha Identifier. michael@0: * michael@0: * @param numOctets michael@0: * Total number of octets to be written. This includes the length of michael@0: * alphaId and the length of trailing unused octets(0xff). michael@0: * @param alphaId michael@0: * Alpha Identifier to be written. michael@0: * michael@0: * Unused octets will be written as 0xff. michael@0: */ michael@0: writeAlphaIdentifier: function(numOctets, alphaId) { michael@0: if (numOctets === 0) { michael@0: return; michael@0: } michael@0: michael@0: // If alphaId is empty or it's of GSM 8 bit. michael@0: if (!alphaId || this.context.ICCUtilsHelper.isGsm8BitAlphabet(alphaId)) { michael@0: this.writeStringTo8BitUnpacked(numOctets, alphaId); michael@0: } else { michael@0: let GsmPDUHelper = this.context.GsmPDUHelper; michael@0: michael@0: // Currently only support UCS2 coding scheme 0x80. michael@0: GsmPDUHelper.writeHexOctet(0x80); michael@0: numOctets--; michael@0: // Now the alphaId is UCS2 string, each character will take 2 octets. michael@0: if (alphaId.length * 2 > numOctets) { michael@0: alphaId = alphaId.substring(0, Math.floor(numOctets / 2)); michael@0: } michael@0: GsmPDUHelper.writeUCS2String(alphaId); michael@0: for (let i = alphaId.length * 2; i < numOctets; i++) { michael@0: GsmPDUHelper.writeHexOctet(0xff); michael@0: } michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Read Dialling number. michael@0: * michael@0: * @see TS 131.102 michael@0: * michael@0: * @param len michael@0: * The Length of BCD number. michael@0: * michael@0: * From TS 131.102, in EF_ADN, EF_FDN, the field 'Length of BCD number' michael@0: * means the total bytes should be allocated to store the TON/NPI and michael@0: * the dialing number. michael@0: * For example, if the dialing number is 1234567890, michael@0: * and the TON/NPI is 0x81, michael@0: * The field 'Length of BCD number' should be 06, which is michael@0: * 1 byte to store the TON/NPI, 0x81 michael@0: * 5 bytes to store the BCD number 2143658709. michael@0: * michael@0: * Here the definition of the length is different from SMS spec, michael@0: * TS 23.040 9.1.2.5, which the length means michael@0: * "number of useful semi-octets within the Address-Value field". michael@0: */ michael@0: readDiallingNumber: function(len) { michael@0: if (DEBUG) this.context.debug("PDU: Going to read Dialling number: " + len); michael@0: if (len === 0) { michael@0: return ""; michael@0: } michael@0: michael@0: let GsmPDUHelper = this.context.GsmPDUHelper; michael@0: michael@0: // TOA = TON + NPI michael@0: let toa = GsmPDUHelper.readHexOctet(); michael@0: michael@0: let number = GsmPDUHelper.readSwappedNibbleBcdString(len - 1); michael@0: if (number.length <= 0) { michael@0: if (DEBUG) this.context.debug("No number provided"); michael@0: return ""; michael@0: } michael@0: if ((toa >> 4) == (PDU_TOA_INTERNATIONAL >> 4)) { michael@0: number = '+' + number; michael@0: } michael@0: return number; michael@0: }, michael@0: michael@0: /** michael@0: * Write Dialling Number. michael@0: * michael@0: * @param number The Dialling number michael@0: */ michael@0: writeDiallingNumber: function(number) { michael@0: let GsmPDUHelper = this.context.GsmPDUHelper; michael@0: michael@0: let toa = PDU_TOA_ISDN; // 81 michael@0: if (number[0] == '+') { michael@0: toa = PDU_TOA_INTERNATIONAL | PDU_TOA_ISDN; // 91 michael@0: number = number.substring(1); michael@0: } michael@0: GsmPDUHelper.writeHexOctet(toa); michael@0: GsmPDUHelper.writeSwappedNibbleBCD(number); michael@0: }, michael@0: michael@0: readNumberWithLength: function() { michael@0: let Buf = this.context.Buf; michael@0: let number; michael@0: let numLen = this.context.GsmPDUHelper.readHexOctet(); michael@0: if (numLen != 0xff) { michael@0: if (numLen > ADN_MAX_BCD_NUMBER_BYTES) { michael@0: throw new Error("invalid length of BCD number/SSC contents - " + numLen); michael@0: } michael@0: michael@0: number = this.readDiallingNumber(numLen); michael@0: Buf.seekIncoming((ADN_MAX_BCD_NUMBER_BYTES - numLen) * Buf.PDU_HEX_OCTET_SIZE); michael@0: } else { michael@0: Buf.seekIncoming(ADN_MAX_BCD_NUMBER_BYTES * Buf.PDU_HEX_OCTET_SIZE); michael@0: } michael@0: michael@0: return number; michael@0: }, michael@0: michael@0: writeNumberWithLength: function(number) { michael@0: let GsmPDUHelper = this.context.GsmPDUHelper; michael@0: michael@0: if (number) { michael@0: let numStart = number[0] == "+" ? 1 : 0; michael@0: number = number.substring(0, numStart) + michael@0: number.substring(numStart) michael@0: .replace(/[^0-9*#,]/g, "") michael@0: .replace(/\*/g, "a") michael@0: .replace(/\#/g, "b") michael@0: .replace(/\,/g, "c"); michael@0: michael@0: let numDigits = number.length - numStart; michael@0: if (numDigits > ADN_MAX_NUMBER_DIGITS) { michael@0: number = number.substring(0, ADN_MAX_NUMBER_DIGITS + numStart); michael@0: numDigits = number.length - numStart; michael@0: } michael@0: michael@0: // +1 for TON/NPI michael@0: let numLen = Math.ceil(numDigits / 2) + 1; michael@0: GsmPDUHelper.writeHexOctet(numLen); michael@0: this.writeDiallingNumber(number); michael@0: // Write trailing 0xff of Dialling Number. michael@0: for (let i = 0; i < ADN_MAX_BCD_NUMBER_BYTES - numLen; i++) { michael@0: GsmPDUHelper.writeHexOctet(0xff); michael@0: } michael@0: } else { michael@0: // +1 for numLen michael@0: for (let i = 0; i < ADN_MAX_BCD_NUMBER_BYTES + 1; i++) { michael@0: GsmPDUHelper.writeHexOctet(0xff); michael@0: } michael@0: } michael@0: } michael@0: }; michael@0: michael@0: function StkCommandParamsFactoryObject(aContext) { michael@0: this.context = aContext; michael@0: } michael@0: StkCommandParamsFactoryObject.prototype = { michael@0: context: null, michael@0: michael@0: createParam: function(cmdDetails, ctlvs) { michael@0: let method = this[cmdDetails.typeOfCommand]; michael@0: if (typeof method != "function") { michael@0: if (DEBUG) { michael@0: this.context.debug("Unknown proactive command " + michael@0: cmdDetails.typeOfCommand.toString(16)); michael@0: } michael@0: return null; michael@0: } michael@0: return method.call(this, cmdDetails, ctlvs); michael@0: }, michael@0: michael@0: /** michael@0: * Construct a param for Refresh. michael@0: * michael@0: * @param cmdDetails michael@0: * The value object of CommandDetails TLV. michael@0: * @param ctlvs michael@0: * The all TLVs in this proactive command. michael@0: */ michael@0: processRefresh: function(cmdDetails, ctlvs) { michael@0: let refreshType = cmdDetails.commandQualifier; michael@0: switch (refreshType) { michael@0: case STK_REFRESH_FILE_CHANGE: michael@0: case STK_REFRESH_NAA_INIT_AND_FILE_CHANGE: michael@0: let ctlv = this.context.StkProactiveCmdHelper.searchForTag( michael@0: COMPREHENSIONTLV_TAG_FILE_LIST, ctlvs); michael@0: if (ctlv) { michael@0: let list = ctlv.value.fileList; michael@0: if (DEBUG) { michael@0: this.context.debug("Refresh, list = " + list); michael@0: } michael@0: this.context.ICCRecordHelper.fetchICCRecords(); michael@0: } michael@0: break; michael@0: } michael@0: return null; michael@0: }, michael@0: michael@0: /** michael@0: * Construct a param for Poll Interval. michael@0: * michael@0: * @param cmdDetails michael@0: * The value object of CommandDetails TLV. michael@0: * @param ctlvs michael@0: * The all TLVs in this proactive command. michael@0: */ michael@0: processPollInterval: function(cmdDetails, ctlvs) { michael@0: let ctlv = this.context.StkProactiveCmdHelper.searchForTag( michael@0: COMPREHENSIONTLV_TAG_DURATION, ctlvs); michael@0: if (!ctlv) { michael@0: this.context.RIL.sendStkTerminalResponse({ michael@0: command: cmdDetails, michael@0: resultCode: STK_RESULT_REQUIRED_VALUES_MISSING}); michael@0: throw new Error("Stk Poll Interval: Required value missing : Duration"); michael@0: } michael@0: michael@0: return ctlv.value; michael@0: }, michael@0: michael@0: /** michael@0: * Construct a param for Poll Off. michael@0: * michael@0: * @param cmdDetails michael@0: * The value object of CommandDetails TLV. michael@0: * @param ctlvs michael@0: * The all TLVs in this proactive command. michael@0: */ michael@0: processPollOff: function(cmdDetails, ctlvs) { michael@0: return null; michael@0: }, michael@0: michael@0: /** michael@0: * Construct a param for Set Up Event list. michael@0: * michael@0: * @param cmdDetails michael@0: * The value object of CommandDetails TLV. michael@0: * @param ctlvs michael@0: * The all TLVs in this proactive command. michael@0: */ michael@0: processSetUpEventList: function(cmdDetails, ctlvs) { michael@0: let ctlv = this.context.StkProactiveCmdHelper.searchForTag( michael@0: COMPREHENSIONTLV_TAG_EVENT_LIST, ctlvs); michael@0: if (!ctlv) { michael@0: this.context.RIL.sendStkTerminalResponse({ michael@0: command: cmdDetails, michael@0: resultCode: STK_RESULT_REQUIRED_VALUES_MISSING}); michael@0: throw new Error("Stk Event List: Required value missing : Event List"); michael@0: } michael@0: michael@0: return ctlv.value || {eventList: null}; michael@0: }, michael@0: michael@0: /** michael@0: * Construct a param for Select Item. michael@0: * michael@0: * @param cmdDetails michael@0: * The value object of CommandDetails TLV. michael@0: * @param ctlvs michael@0: * The all TLVs in this proactive command. michael@0: */ michael@0: processSelectItem: function(cmdDetails, ctlvs) { michael@0: let StkProactiveCmdHelper = this.context.StkProactiveCmdHelper; michael@0: let menu = {}; michael@0: michael@0: let ctlv = StkProactiveCmdHelper.searchForTag(COMPREHENSIONTLV_TAG_ALPHA_ID, ctlvs); michael@0: if (ctlv) { michael@0: menu.title = ctlv.value.identifier; michael@0: } michael@0: michael@0: menu.items = []; michael@0: for (let i = 0; i < ctlvs.length; i++) { michael@0: let ctlv = ctlvs[i]; michael@0: if (ctlv.tag == COMPREHENSIONTLV_TAG_ITEM) { michael@0: menu.items.push(ctlv.value); michael@0: } michael@0: } michael@0: michael@0: if (menu.items.length === 0) { michael@0: this.context.RIL.sendStkTerminalResponse({ michael@0: command: cmdDetails, michael@0: resultCode: STK_RESULT_REQUIRED_VALUES_MISSING}); michael@0: throw new Error("Stk Menu: Required value missing : items"); michael@0: } michael@0: michael@0: ctlv = StkProactiveCmdHelper.searchForTag(COMPREHENSIONTLV_TAG_ITEM_ID, ctlvs); michael@0: if (ctlv) { michael@0: menu.defaultItem = ctlv.value.identifier - 1; michael@0: } michael@0: michael@0: // The 1st bit and 2nd bit determines the presentation type. michael@0: menu.presentationType = cmdDetails.commandQualifier & 0x03; michael@0: michael@0: // Help information available. michael@0: if (cmdDetails.commandQualifier & 0x80) { michael@0: menu.isHelpAvailable = true; michael@0: } michael@0: michael@0: ctlv = StkProactiveCmdHelper.searchForTag(COMPREHENSIONTLV_TAG_NEXT_ACTION_IND, ctlvs); michael@0: if (ctlv) { michael@0: menu.nextActionList = ctlv.value; michael@0: } michael@0: michael@0: return menu; michael@0: }, michael@0: michael@0: processDisplayText: function(cmdDetails, ctlvs) { michael@0: let StkProactiveCmdHelper = this.context.StkProactiveCmdHelper; michael@0: let textMsg = {}; michael@0: michael@0: let ctlv = StkProactiveCmdHelper.searchForTag(COMPREHENSIONTLV_TAG_TEXT_STRING, ctlvs); michael@0: if (!ctlv) { michael@0: this.context.RIL.sendStkTerminalResponse({ michael@0: command: cmdDetails, michael@0: resultCode: STK_RESULT_REQUIRED_VALUES_MISSING}); michael@0: throw new Error("Stk Display Text: Required value missing : Text String"); michael@0: } michael@0: textMsg.text = ctlv.value.textString; michael@0: michael@0: ctlv = StkProactiveCmdHelper.searchForTag(COMPREHENSIONTLV_TAG_IMMEDIATE_RESPONSE, ctlvs); michael@0: if (ctlv) { michael@0: textMsg.responseNeeded = true; michael@0: } michael@0: michael@0: ctlv = StkProactiveCmdHelper.searchForTag(COMPREHENSIONTLV_TAG_DURATION, ctlvs); michael@0: if (ctlv) { michael@0: textMsg.duration = ctlv.value; michael@0: } michael@0: michael@0: // High priority. michael@0: if (cmdDetails.commandQualifier & 0x01) { michael@0: textMsg.isHighPriority = true; michael@0: } michael@0: michael@0: // User clear. michael@0: if (cmdDetails.commandQualifier & 0x80) { michael@0: textMsg.userClear = true; michael@0: } michael@0: michael@0: return textMsg; michael@0: }, michael@0: michael@0: processSetUpIdleModeText: function(cmdDetails, ctlvs) { michael@0: let textMsg = {}; michael@0: michael@0: let ctlv = this.context.StkProactiveCmdHelper.searchForTag( michael@0: COMPREHENSIONTLV_TAG_TEXT_STRING, ctlvs); michael@0: if (!ctlv) { michael@0: this.context.RIL.sendStkTerminalResponse({ michael@0: command: cmdDetails, michael@0: resultCode: STK_RESULT_REQUIRED_VALUES_MISSING}); michael@0: throw new Error("Stk Set Up Idle Text: Required value missing : Text String"); michael@0: } michael@0: textMsg.text = ctlv.value.textString; michael@0: michael@0: return textMsg; michael@0: }, michael@0: michael@0: processGetInkey: function(cmdDetails, ctlvs) { michael@0: let StkProactiveCmdHelper = this.context.StkProactiveCmdHelper; michael@0: let input = {}; michael@0: michael@0: let ctlv = StkProactiveCmdHelper.searchForTag(COMPREHENSIONTLV_TAG_TEXT_STRING, ctlvs); michael@0: if (!ctlv) { michael@0: this.context.RIL.sendStkTerminalResponse({ michael@0: command: cmdDetails, michael@0: resultCode: STK_RESULT_REQUIRED_VALUES_MISSING}); michael@0: throw new Error("Stk Get InKey: Required value missing : Text String"); michael@0: } michael@0: input.text = ctlv.value.textString; michael@0: michael@0: // duration michael@0: ctlv = StkProactiveCmdHelper.searchForTag( michael@0: COMPREHENSIONTLV_TAG_DURATION, ctlvs); michael@0: if (ctlv) { michael@0: input.duration = ctlv.value; michael@0: } michael@0: michael@0: input.minLength = 1; michael@0: input.maxLength = 1; michael@0: michael@0: // isAlphabet michael@0: if (cmdDetails.commandQualifier & 0x01) { michael@0: input.isAlphabet = true; michael@0: } michael@0: michael@0: // UCS2 michael@0: if (cmdDetails.commandQualifier & 0x02) { michael@0: input.isUCS2 = true; michael@0: } michael@0: michael@0: // Character sets defined in bit 1 and bit 2 are disable and michael@0: // the YES/NO reponse is required. michael@0: if (cmdDetails.commandQualifier & 0x04) { michael@0: input.isYesNoRequested = true; michael@0: } michael@0: michael@0: // Help information available. michael@0: if (cmdDetails.commandQualifier & 0x80) { michael@0: input.isHelpAvailable = true; michael@0: } michael@0: michael@0: return input; michael@0: }, michael@0: michael@0: processGetInput: function(cmdDetails, ctlvs) { michael@0: let StkProactiveCmdHelper = this.context.StkProactiveCmdHelper; michael@0: let input = {}; michael@0: michael@0: let ctlv = StkProactiveCmdHelper.searchForTag(COMPREHENSIONTLV_TAG_TEXT_STRING, ctlvs); michael@0: if (!ctlv) { michael@0: this.context.RIL.sendStkTerminalResponse({ michael@0: command: cmdDetails, michael@0: resultCode: STK_RESULT_REQUIRED_VALUES_MISSING}); michael@0: throw new Error("Stk Get Input: Required value missing : Text String"); michael@0: } michael@0: input.text = ctlv.value.textString; michael@0: michael@0: ctlv = StkProactiveCmdHelper.searchForTag(COMPREHENSIONTLV_TAG_RESPONSE_LENGTH, ctlvs); michael@0: if (ctlv) { michael@0: input.minLength = ctlv.value.minLength; michael@0: input.maxLength = ctlv.value.maxLength; michael@0: } michael@0: michael@0: ctlv = StkProactiveCmdHelper.searchForTag(COMPREHENSIONTLV_TAG_DEFAULT_TEXT, ctlvs); michael@0: if (ctlv) { michael@0: input.defaultText = ctlv.value.textString; michael@0: } michael@0: michael@0: // Alphabet only michael@0: if (cmdDetails.commandQualifier & 0x01) { michael@0: input.isAlphabet = true; michael@0: } michael@0: michael@0: // UCS2 michael@0: if (cmdDetails.commandQualifier & 0x02) { michael@0: input.isUCS2 = true; michael@0: } michael@0: michael@0: // User input shall not be revealed michael@0: if (cmdDetails.commandQualifier & 0x04) { michael@0: input.hideInput = true; michael@0: } michael@0: michael@0: // User input in SMS packed format michael@0: if (cmdDetails.commandQualifier & 0x08) { michael@0: input.isPacked = true; michael@0: } michael@0: michael@0: // Help information available. michael@0: if (cmdDetails.commandQualifier & 0x80) { michael@0: input.isHelpAvailable = true; michael@0: } michael@0: michael@0: return input; michael@0: }, michael@0: michael@0: processEventNotify: function(cmdDetails, ctlvs) { michael@0: let textMsg = {}; michael@0: michael@0: let ctlv = this.context.StkProactiveCmdHelper.searchForTag( michael@0: COMPREHENSIONTLV_TAG_ALPHA_ID, ctlvs); michael@0: if (!ctlv) { michael@0: this.context.RIL.sendStkTerminalResponse({ michael@0: command: cmdDetails, michael@0: resultCode: STK_RESULT_REQUIRED_VALUES_MISSING}); michael@0: throw new Error("Stk Event Notfiy: Required value missing : Alpha ID"); michael@0: } michael@0: textMsg.text = ctlv.value.identifier; michael@0: michael@0: return textMsg; michael@0: }, michael@0: michael@0: processSetupCall: function(cmdDetails, ctlvs) { michael@0: let StkProactiveCmdHelper = this.context.StkProactiveCmdHelper; michael@0: let call = {}; michael@0: let iter = Iterator(ctlvs); michael@0: michael@0: let ctlv = StkProactiveCmdHelper.searchForNextTag(COMPREHENSIONTLV_TAG_ALPHA_ID, iter); michael@0: if (ctlv) { michael@0: call.confirmMessage = ctlv.value.identifier; michael@0: } michael@0: michael@0: ctlv = StkProactiveCmdHelper.searchForNextTag(COMPREHENSIONTLV_TAG_ALPHA_ID, iter); michael@0: if (ctlv) { michael@0: call.callMessage = ctlv.value.identifier; michael@0: } michael@0: michael@0: ctlv = StkProactiveCmdHelper.searchForTag(COMPREHENSIONTLV_TAG_ADDRESS, ctlvs); michael@0: if (!ctlv) { michael@0: this.context.RIL.sendStkTerminalResponse({ michael@0: command: cmdDetails, michael@0: resultCode: STK_RESULT_REQUIRED_VALUES_MISSING}); michael@0: throw new Error("Stk Set Up Call: Required value missing : Adress"); michael@0: } michael@0: call.address = ctlv.value.number; michael@0: michael@0: // see 3GPP TS 31.111 section 6.4.13 michael@0: ctlv = StkProactiveCmdHelper.searchForTag(COMPREHENSIONTLV_TAG_DURATION, ctlvs); michael@0: if (ctlv) { michael@0: call.duration = ctlv.value; michael@0: } michael@0: michael@0: return call; michael@0: }, michael@0: michael@0: processLaunchBrowser: function(cmdDetails, ctlvs) { michael@0: let StkProactiveCmdHelper = this.context.StkProactiveCmdHelper; michael@0: let browser = {}; michael@0: michael@0: let ctlv = StkProactiveCmdHelper.searchForTag(COMPREHENSIONTLV_TAG_URL, ctlvs); michael@0: if (!ctlv) { michael@0: this.context.RIL.sendStkTerminalResponse({ michael@0: command: cmdDetails, michael@0: resultCode: STK_RESULT_REQUIRED_VALUES_MISSING}); michael@0: throw new Error("Stk Launch Browser: Required value missing : URL"); michael@0: } michael@0: browser.url = ctlv.value.url; michael@0: michael@0: ctlv = StkProactiveCmdHelper.searchForTag(COMPREHENSIONTLV_TAG_ALPHA_ID, ctlvs); michael@0: if (ctlv) { michael@0: browser.confirmMessage = ctlv.value.identifier; michael@0: } michael@0: michael@0: browser.mode = cmdDetails.commandQualifier & 0x03; michael@0: michael@0: return browser; michael@0: }, michael@0: michael@0: processPlayTone: function(cmdDetails, ctlvs) { michael@0: let StkProactiveCmdHelper = this.context.StkProactiveCmdHelper; michael@0: let playTone = {}; michael@0: michael@0: let ctlv = StkProactiveCmdHelper.searchForTag( michael@0: COMPREHENSIONTLV_TAG_ALPHA_ID, ctlvs); michael@0: if (ctlv) { michael@0: playTone.text = ctlv.value.identifier; michael@0: } michael@0: michael@0: ctlv = StkProactiveCmdHelper.searchForTag(COMPREHENSIONTLV_TAG_TONE, ctlvs); michael@0: if (ctlv) { michael@0: playTone.tone = ctlv.value.tone; michael@0: } michael@0: michael@0: ctlv = StkProactiveCmdHelper.searchForTag( michael@0: COMPREHENSIONTLV_TAG_DURATION, ctlvs); michael@0: if (ctlv) { michael@0: playTone.duration = ctlv.value; michael@0: } michael@0: michael@0: // vibrate is only defined in TS 102.223 michael@0: playTone.isVibrate = (cmdDetails.commandQualifier & 0x01) !== 0x00; michael@0: michael@0: return playTone; michael@0: }, michael@0: michael@0: /** michael@0: * Construct a param for Provide Local Information michael@0: * michael@0: * @param cmdDetails michael@0: * The value object of CommandDetails TLV. michael@0: * @param ctlvs michael@0: * The all TLVs in this proactive command. michael@0: */ michael@0: processProvideLocalInfo: function(cmdDetails, ctlvs) { michael@0: let provideLocalInfo = { michael@0: localInfoType: cmdDetails.commandQualifier michael@0: }; michael@0: return provideLocalInfo; michael@0: }, michael@0: michael@0: processTimerManagement: function(cmdDetails, ctlvs) { michael@0: let StkProactiveCmdHelper = this.context.StkProactiveCmdHelper; michael@0: let timer = { michael@0: timerAction: cmdDetails.commandQualifier michael@0: }; michael@0: michael@0: let ctlv = StkProactiveCmdHelper.searchForTag( michael@0: COMPREHENSIONTLV_TAG_TIMER_IDENTIFIER, ctlvs); michael@0: if (ctlv) { michael@0: timer.timerId = ctlv.value.timerId; michael@0: } michael@0: michael@0: ctlv = StkProactiveCmdHelper.searchForTag( michael@0: COMPREHENSIONTLV_TAG_TIMER_VALUE, ctlvs); michael@0: if (ctlv) { michael@0: timer.timerValue = ctlv.value.timerValue; michael@0: } michael@0: michael@0: return timer; michael@0: }, michael@0: michael@0: /** michael@0: * Construct a param for BIP commands. michael@0: * michael@0: * @param cmdDetails michael@0: * The value object of CommandDetails TLV. michael@0: * @param ctlvs michael@0: * The all TLVs in this proactive command. michael@0: */ michael@0: processBipMessage: function(cmdDetails, ctlvs) { michael@0: let bipMsg = {}; michael@0: michael@0: let ctlv = this.context.StkProactiveCmdHelper.searchForTag( michael@0: COMPREHENSIONTLV_TAG_ALPHA_ID, ctlvs); michael@0: if (ctlv) { michael@0: bipMsg.text = ctlv.value.identifier; michael@0: } michael@0: michael@0: return bipMsg; michael@0: } michael@0: }; michael@0: StkCommandParamsFactoryObject.prototype[STK_CMD_REFRESH] = function STK_CMD_REFRESH(cmdDetails, ctlvs) { michael@0: return this.processRefresh(cmdDetails, ctlvs); michael@0: }; michael@0: StkCommandParamsFactoryObject.prototype[STK_CMD_POLL_INTERVAL] = function STK_CMD_POLL_INTERVAL(cmdDetails, ctlvs) { michael@0: return this.processPollInterval(cmdDetails, ctlvs); michael@0: }; michael@0: StkCommandParamsFactoryObject.prototype[STK_CMD_POLL_OFF] = function STK_CMD_POLL_OFF(cmdDetails, ctlvs) { michael@0: return this.processPollOff(cmdDetails, ctlvs); michael@0: }; michael@0: StkCommandParamsFactoryObject.prototype[STK_CMD_PROVIDE_LOCAL_INFO] = function STK_CMD_PROVIDE_LOCAL_INFO(cmdDetails, ctlvs) { michael@0: return this.processProvideLocalInfo(cmdDetails, ctlvs); michael@0: }; michael@0: StkCommandParamsFactoryObject.prototype[STK_CMD_SET_UP_EVENT_LIST] = function STK_CMD_SET_UP_EVENT_LIST(cmdDetails, ctlvs) { michael@0: return this.processSetUpEventList(cmdDetails, ctlvs); michael@0: }; michael@0: StkCommandParamsFactoryObject.prototype[STK_CMD_SET_UP_MENU] = function STK_CMD_SET_UP_MENU(cmdDetails, ctlvs) { michael@0: return this.processSelectItem(cmdDetails, ctlvs); michael@0: }; michael@0: StkCommandParamsFactoryObject.prototype[STK_CMD_SELECT_ITEM] = function STK_CMD_SELECT_ITEM(cmdDetails, ctlvs) { michael@0: return this.processSelectItem(cmdDetails, ctlvs); michael@0: }; michael@0: StkCommandParamsFactoryObject.prototype[STK_CMD_DISPLAY_TEXT] = function STK_CMD_DISPLAY_TEXT(cmdDetails, ctlvs) { michael@0: return this.processDisplayText(cmdDetails, ctlvs); michael@0: }; michael@0: StkCommandParamsFactoryObject.prototype[STK_CMD_SET_UP_IDLE_MODE_TEXT] = function STK_CMD_SET_UP_IDLE_MODE_TEXT(cmdDetails, ctlvs) { michael@0: return this.processSetUpIdleModeText(cmdDetails, ctlvs); michael@0: }; michael@0: StkCommandParamsFactoryObject.prototype[STK_CMD_GET_INKEY] = function STK_CMD_GET_INKEY(cmdDetails, ctlvs) { michael@0: return this.processGetInkey(cmdDetails, ctlvs); michael@0: }; michael@0: StkCommandParamsFactoryObject.prototype[STK_CMD_GET_INPUT] = function STK_CMD_GET_INPUT(cmdDetails, ctlvs) { michael@0: return this.processGetInput(cmdDetails, ctlvs); michael@0: }; michael@0: StkCommandParamsFactoryObject.prototype[STK_CMD_SEND_SS] = function STK_CMD_SEND_SS(cmdDetails, ctlvs) { michael@0: return this.processEventNotify(cmdDetails, ctlvs); michael@0: }; michael@0: StkCommandParamsFactoryObject.prototype[STK_CMD_SEND_USSD] = function STK_CMD_SEND_USSD(cmdDetails, ctlvs) { michael@0: return this.processEventNotify(cmdDetails, ctlvs); michael@0: }; michael@0: StkCommandParamsFactoryObject.prototype[STK_CMD_SEND_SMS] = function STK_CMD_SEND_SMS(cmdDetails, ctlvs) { michael@0: return this.processEventNotify(cmdDetails, ctlvs); michael@0: }; michael@0: StkCommandParamsFactoryObject.prototype[STK_CMD_SEND_DTMF] = function STK_CMD_SEND_DTMF(cmdDetails, ctlvs) { michael@0: return this.processEventNotify(cmdDetails, ctlvs); michael@0: }; michael@0: StkCommandParamsFactoryObject.prototype[STK_CMD_SET_UP_CALL] = function STK_CMD_SET_UP_CALL(cmdDetails, ctlvs) { michael@0: return this.processSetupCall(cmdDetails, ctlvs); michael@0: }; michael@0: StkCommandParamsFactoryObject.prototype[STK_CMD_LAUNCH_BROWSER] = function STK_CMD_LAUNCH_BROWSER(cmdDetails, ctlvs) { michael@0: return this.processLaunchBrowser(cmdDetails, ctlvs); michael@0: }; michael@0: StkCommandParamsFactoryObject.prototype[STK_CMD_PLAY_TONE] = function STK_CMD_PLAY_TONE(cmdDetails, ctlvs) { michael@0: return this.processPlayTone(cmdDetails, ctlvs); michael@0: }; michael@0: StkCommandParamsFactoryObject.prototype[STK_CMD_TIMER_MANAGEMENT] = function STK_CMD_TIMER_MANAGEMENT(cmdDetails, ctlvs) { michael@0: return this.processTimerManagement(cmdDetails, ctlvs); michael@0: }; michael@0: StkCommandParamsFactoryObject.prototype[STK_CMD_OPEN_CHANNEL] = function STK_CMD_OPEN_CHANNEL(cmdDetails, ctlvs) { michael@0: return this.processBipMessage(cmdDetails, ctlvs); michael@0: }; michael@0: StkCommandParamsFactoryObject.prototype[STK_CMD_CLOSE_CHANNEL] = function STK_CMD_CLOSE_CHANNEL(cmdDetails, ctlvs) { michael@0: return this.processBipMessage(cmdDetails, ctlvs); michael@0: }; michael@0: StkCommandParamsFactoryObject.prototype[STK_CMD_RECEIVE_DATA] = function STK_CMD_RECEIVE_DATA(cmdDetails, ctlvs) { michael@0: return this.processBipMessage(cmdDetails, ctlvs); michael@0: }; michael@0: StkCommandParamsFactoryObject.prototype[STK_CMD_SEND_DATA] = function STK_CMD_SEND_DATA(cmdDetails, ctlvs) { michael@0: return this.processBipMessage(cmdDetails, ctlvs); michael@0: }; michael@0: michael@0: function StkProactiveCmdHelperObject(aContext) { michael@0: this.context = aContext; michael@0: } michael@0: StkProactiveCmdHelperObject.prototype = { michael@0: context: null, michael@0: michael@0: retrieve: function(tag, length) { michael@0: let method = this[tag]; michael@0: if (typeof method != "function") { michael@0: if (DEBUG) { michael@0: this.context.debug("Unknown comprehension tag " + tag.toString(16)); michael@0: } michael@0: let Buf = this.context.Buf; michael@0: Buf.seekIncoming(length * Buf.PDU_HEX_OCTET_SIZE); michael@0: return null; michael@0: } michael@0: return method.call(this, length); michael@0: }, michael@0: michael@0: /** michael@0: * Command Details. michael@0: * michael@0: * | Byte | Description | Length | michael@0: * | 1 | Command details Tag | 1 | michael@0: * | 2 | Length = 03 | 1 | michael@0: * | 3 | Command number | 1 | michael@0: * | 4 | Type of Command | 1 | michael@0: * | 5 | Command Qualifier | 1 | michael@0: */ michael@0: retrieveCommandDetails: function(length) { michael@0: let GsmPDUHelper = this.context.GsmPDUHelper; michael@0: let cmdDetails = { michael@0: commandNumber: GsmPDUHelper.readHexOctet(), michael@0: typeOfCommand: GsmPDUHelper.readHexOctet(), michael@0: commandQualifier: GsmPDUHelper.readHexOctet() michael@0: }; michael@0: return cmdDetails; michael@0: }, michael@0: michael@0: /** michael@0: * Device Identities. michael@0: * michael@0: * | Byte | Description | Length | michael@0: * | 1 | Device Identity Tag | 1 | michael@0: * | 2 | Length = 02 | 1 | michael@0: * | 3 | Source device Identity | 1 | michael@0: * | 4 | Destination device Id | 1 | michael@0: */ michael@0: retrieveDeviceId: function(length) { michael@0: let GsmPDUHelper = this.context.GsmPDUHelper; michael@0: let deviceId = { michael@0: sourceId: GsmPDUHelper.readHexOctet(), michael@0: destinationId: GsmPDUHelper.readHexOctet() michael@0: }; michael@0: return deviceId; michael@0: }, michael@0: michael@0: /** michael@0: * Alpha Identifier. michael@0: * michael@0: * | Byte | Description | Length | michael@0: * | 1 | Alpha Identifier Tag | 1 | michael@0: * | 2 ~ (Y-1)+2 | Length (X) | Y | michael@0: * | (Y-1)+3 ~ | Alpha identfier | X | michael@0: * | (Y-1)+X+2 | | | michael@0: */ michael@0: retrieveAlphaId: function(length) { michael@0: let alphaId = { michael@0: identifier: this.context.ICCPDUHelper.readAlphaIdentifier(length) michael@0: }; michael@0: return alphaId; michael@0: }, michael@0: michael@0: /** michael@0: * Duration. michael@0: * michael@0: * | Byte | Description | Length | michael@0: * | 1 | Response Length Tag | 1 | michael@0: * | 2 | Lenth = 02 | 1 | michael@0: * | 3 | Time unit | 1 | michael@0: * | 4 | Time interval | 1 | michael@0: */ michael@0: retrieveDuration: function(length) { michael@0: let GsmPDUHelper = this.context.GsmPDUHelper; michael@0: let duration = { michael@0: timeUnit: GsmPDUHelper.readHexOctet(), michael@0: timeInterval: GsmPDUHelper.readHexOctet(), michael@0: }; michael@0: return duration; michael@0: }, michael@0: michael@0: /** michael@0: * Address. michael@0: * michael@0: * | Byte | Description | Length | michael@0: * | 1 | Alpha Identifier Tag | 1 | michael@0: * | 2 ~ (Y-1)+2 | Length (X) | Y | michael@0: * | (Y-1)+3 | TON and NPI | 1 | michael@0: * | (Y-1)+4 ~ | Dialling number | X | michael@0: * | (Y-1)+X+2 | | | michael@0: */ michael@0: retrieveAddress: function(length) { michael@0: let address = { michael@0: number : this.context.ICCPDUHelper.readDiallingNumber(length) michael@0: }; michael@0: return address; michael@0: }, michael@0: michael@0: /** michael@0: * Text String. michael@0: * michael@0: * | Byte | Description | Length | michael@0: * | 1 | Text String Tag | 1 | michael@0: * | 2 ~ (Y-1)+2 | Length (X) | Y | michael@0: * | (Y-1)+3 | Data coding scheme | 1 | michael@0: * | (Y-1)+4~ | Text String | X | michael@0: * | (Y-1)+X+2 | | | michael@0: */ michael@0: retrieveTextString: function(length) { michael@0: if (!length) { michael@0: // null string. michael@0: return {textString: null}; michael@0: } michael@0: michael@0: let GsmPDUHelper = this.context.GsmPDUHelper; michael@0: let text = { michael@0: codingScheme: GsmPDUHelper.readHexOctet() michael@0: }; michael@0: michael@0: length--; // -1 for the codingScheme. michael@0: switch (text.codingScheme & 0x0f) { michael@0: case STK_TEXT_CODING_GSM_7BIT_PACKED: michael@0: text.textString = GsmPDUHelper.readSeptetsToString(length * 8 / 7, 0, 0, 0); michael@0: break; michael@0: case STK_TEXT_CODING_GSM_8BIT: michael@0: text.textString = michael@0: this.context.ICCPDUHelper.read8BitUnpackedToString(length); michael@0: break; michael@0: case STK_TEXT_CODING_UCS2: michael@0: text.textString = GsmPDUHelper.readUCS2String(length); michael@0: break; michael@0: } michael@0: return text; michael@0: }, michael@0: michael@0: /** michael@0: * Tone. michael@0: * michael@0: * | Byte | Description | Length | michael@0: * | 1 | Tone Tag | 1 | michael@0: * | 2 | Lenth = 01 | 1 | michael@0: * | 3 | Tone | 1 | michael@0: */ michael@0: retrieveTone: function(length) { michael@0: let tone = { michael@0: tone: this.context.GsmPDUHelper.readHexOctet(), michael@0: }; michael@0: return tone; michael@0: }, michael@0: michael@0: /** michael@0: * Item. michael@0: * michael@0: * | Byte | Description | Length | michael@0: * | 1 | Item Tag | 1 | michael@0: * | 2 ~ (Y-1)+2 | Length (X) | Y | michael@0: * | (Y-1)+3 | Identifier of item | 1 | michael@0: * | (Y-1)+4 ~ | Text string of item | X | michael@0: * | (Y-1)+X+2 | | | michael@0: */ michael@0: retrieveItem: function(length) { michael@0: // TS 102.223 ,clause 6.6.7 SET-UP MENU michael@0: // If the "Item data object for item 1" is a null data object michael@0: // (i.e. length = '00' and no value part), this is an indication to the ME michael@0: // to remove the existing menu from the menu system in the ME. michael@0: if (!length) { michael@0: return null; michael@0: } michael@0: let item = { michael@0: identifier: this.context.GsmPDUHelper.readHexOctet(), michael@0: text: this.context.ICCPDUHelper.readAlphaIdentifier(length - 1) michael@0: }; michael@0: return item; michael@0: }, michael@0: michael@0: /** michael@0: * Item Identifier. michael@0: * michael@0: * | Byte | Description | Length | michael@0: * | 1 | Item Identifier Tag | 1 | michael@0: * | 2 | Lenth = 01 | 1 | michael@0: * | 3 | Identifier of Item chosen | 1 | michael@0: */ michael@0: retrieveItemId: function(length) { michael@0: let itemId = { michael@0: identifier: this.context.GsmPDUHelper.readHexOctet() michael@0: }; michael@0: return itemId; michael@0: }, michael@0: michael@0: /** michael@0: * Response Length. michael@0: * michael@0: * | Byte | Description | Length | michael@0: * | 1 | Response Length Tag | 1 | michael@0: * | 2 | Lenth = 02 | 1 | michael@0: * | 3 | Minimum length of response | 1 | michael@0: * | 4 | Maximum length of response | 1 | michael@0: */ michael@0: retrieveResponseLength: function(length) { michael@0: let GsmPDUHelper = this.context.GsmPDUHelper; michael@0: let rspLength = { michael@0: minLength : GsmPDUHelper.readHexOctet(), michael@0: maxLength : GsmPDUHelper.readHexOctet() michael@0: }; michael@0: return rspLength; michael@0: }, michael@0: michael@0: /** michael@0: * File List. michael@0: * michael@0: * | Byte | Description | Length | michael@0: * | 1 | File List Tag | 1 | michael@0: * | 2 ~ (Y-1)+2 | Length (X) | Y | michael@0: * | (Y-1)+3 | Number of files | 1 | michael@0: * | (Y-1)+4 ~ | Files | X | michael@0: * | (Y-1)+X+2 | | | michael@0: */ michael@0: retrieveFileList: function(length) { michael@0: let num = this.context.GsmPDUHelper.readHexOctet(); michael@0: let fileList = ""; michael@0: length--; // -1 for the num octet. michael@0: for (let i = 0; i < 2 * length; i++) { michael@0: // Didn't use readHexOctet here, michael@0: // otherwise 0x00 will be "0", not "00" michael@0: fileList += String.fromCharCode(this.context.Buf.readUint16()); michael@0: } michael@0: return { michael@0: fileList: fileList michael@0: }; michael@0: }, michael@0: michael@0: /** michael@0: * Default Text. michael@0: * michael@0: * Same as Text String. michael@0: */ michael@0: retrieveDefaultText: function(length) { michael@0: return this.retrieveTextString(length); michael@0: }, michael@0: michael@0: /** michael@0: * Event List. michael@0: */ michael@0: retrieveEventList: function(length) { michael@0: if (!length) { michael@0: // null means an indication to ME to remove the existing list of events michael@0: // in ME. michael@0: return null; michael@0: } michael@0: michael@0: let GsmPDUHelper = this.context.GsmPDUHelper; michael@0: let eventList = []; michael@0: for (let i = 0; i < length; i++) { michael@0: eventList.push(GsmPDUHelper.readHexOctet()); michael@0: } michael@0: return { michael@0: eventList: eventList michael@0: }; michael@0: }, michael@0: michael@0: /** michael@0: * Timer Identifier. michael@0: * michael@0: * | Byte | Description | Length | michael@0: * | 1 | Timer Identifier Tag | 1 | michael@0: * | 2 | Length = 01 | 1 | michael@0: * | 3 | Timer Identifier | 1 | michael@0: */ michael@0: retrieveTimerId: function(length) { michael@0: let id = { michael@0: timerId: this.context.GsmPDUHelper.readHexOctet() michael@0: }; michael@0: return id; michael@0: }, michael@0: michael@0: /** michael@0: * Timer Value. michael@0: * michael@0: * | Byte | Description | Length | michael@0: * | 1 | Timer Value Tag | 1 | michael@0: * | 2 | Length = 03 | 1 | michael@0: * | 3 | Hour | 1 | michael@0: * | 4 | Minute | 1 | michael@0: * | 5 | Second | 1 | michael@0: */ michael@0: retrieveTimerValue: function(length) { michael@0: let GsmPDUHelper = this.context.GsmPDUHelper; michael@0: let value = { michael@0: timerValue: (GsmPDUHelper.readSwappedNibbleBcdNum(1) * 60 * 60) + michael@0: (GsmPDUHelper.readSwappedNibbleBcdNum(1) * 60) + michael@0: (GsmPDUHelper.readSwappedNibbleBcdNum(1)) michael@0: }; michael@0: return value; michael@0: }, michael@0: michael@0: /** michael@0: * Immediate Response. michael@0: * michael@0: * | Byte | Description | Length | michael@0: * | 1 | Immediate Response Tag | 1 | michael@0: * | 2 | Length = 00 | 1 | michael@0: */ michael@0: retrieveImmediaResponse: function(length) { michael@0: return {}; michael@0: }, michael@0: michael@0: /** michael@0: * URL michael@0: * michael@0: * | Byte | Description | Length | michael@0: * | 1 | URL Tag | 1 | michael@0: * | 2 ~ (Y+1) | Length(X) | Y | michael@0: * | (Y+2) ~ | URL | X | michael@0: * | (Y+1+X) | | | michael@0: */ michael@0: retrieveUrl: function(length) { michael@0: let GsmPDUHelper = this.context.GsmPDUHelper; michael@0: let s = ""; michael@0: for (let i = 0; i < length; i++) { michael@0: s += String.fromCharCode(GsmPDUHelper.readHexOctet()); michael@0: } michael@0: return {url: s}; michael@0: }, michael@0: michael@0: /** michael@0: * Next Action Indicator List. michael@0: * michael@0: * | Byte | Description | Length | michael@0: * | 1 | Next Action tag | 1 | michael@0: * | 1 | Length(X) | 1 | michael@0: * | 3~ | Next Action List | X | michael@0: * | 3+X-1 | | | michael@0: */ michael@0: retrieveNextActionList: function(length) { michael@0: let GsmPDUHelper = this.context.GsmPDUHelper; michael@0: let nextActionList = []; michael@0: for (let i = 0; i < length; i++) { michael@0: nextActionList.push(GsmPDUHelper.readHexOctet()); michael@0: } michael@0: return nextActionList; michael@0: }, michael@0: michael@0: searchForTag: function(tag, ctlvs) { michael@0: let iter = Iterator(ctlvs); michael@0: return this.searchForNextTag(tag, iter); michael@0: }, michael@0: michael@0: searchForNextTag: function(tag, iter) { michael@0: for (let [index, ctlv] in iter) { michael@0: if ((ctlv.tag & ~COMPREHENSIONTLV_FLAG_CR) == tag) { michael@0: return ctlv; michael@0: } michael@0: } michael@0: return null; michael@0: }, michael@0: }; michael@0: StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_COMMAND_DETAILS] = function COMPREHENSIONTLV_TAG_COMMAND_DETAILS(length) { michael@0: return this.retrieveCommandDetails(length); michael@0: }; michael@0: StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_DEVICE_ID] = function COMPREHENSIONTLV_TAG_DEVICE_ID(length) { michael@0: return this.retrieveDeviceId(length); michael@0: }; michael@0: StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_ALPHA_ID] = function COMPREHENSIONTLV_TAG_ALPHA_ID(length) { michael@0: return this.retrieveAlphaId(length); michael@0: }; michael@0: StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_DURATION] = function COMPREHENSIONTLV_TAG_DURATION(length) { michael@0: return this.retrieveDuration(length); michael@0: }; michael@0: StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_ADDRESS] = function COMPREHENSIONTLV_TAG_ADDRESS(length) { michael@0: return this.retrieveAddress(length); michael@0: }; michael@0: StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_TEXT_STRING] = function COMPREHENSIONTLV_TAG_TEXT_STRING(length) { michael@0: return this.retrieveTextString(length); michael@0: }; michael@0: StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_TONE] = function COMPREHENSIONTLV_TAG_TONE(length) { michael@0: return this.retrieveTone(length); michael@0: }; michael@0: StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_ITEM] = function COMPREHENSIONTLV_TAG_ITEM(length) { michael@0: return this.retrieveItem(length); michael@0: }; michael@0: StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_ITEM_ID] = function COMPREHENSIONTLV_TAG_ITEM_ID(length) { michael@0: return this.retrieveItemId(length); michael@0: }; michael@0: StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_RESPONSE_LENGTH] = function COMPREHENSIONTLV_TAG_RESPONSE_LENGTH(length) { michael@0: return this.retrieveResponseLength(length); michael@0: }; michael@0: StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_FILE_LIST] = function COMPREHENSIONTLV_TAG_FILE_LIST(length) { michael@0: return this.retrieveFileList(length); michael@0: }; michael@0: StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_DEFAULT_TEXT] = function COMPREHENSIONTLV_TAG_DEFAULT_TEXT(length) { michael@0: return this.retrieveDefaultText(length); michael@0: }; michael@0: StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_EVENT_LIST] = function COMPREHENSIONTLV_TAG_EVENT_LIST(length) { michael@0: return this.retrieveEventList(length); michael@0: }; michael@0: StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_TIMER_IDENTIFIER] = function COMPREHENSIONTLV_TAG_TIMER_IDENTIFIER(length) { michael@0: return this.retrieveTimerId(length); michael@0: }; michael@0: StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_TIMER_VALUE] = function COMPREHENSIONTLV_TAG_TIMER_VALUE(length) { michael@0: return this.retrieveTimerValue(length); michael@0: }; michael@0: StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_IMMEDIATE_RESPONSE] = function COMPREHENSIONTLV_TAG_IMMEDIATE_RESPONSE(length) { michael@0: return this.retrieveImmediaResponse(length); michael@0: }; michael@0: StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_URL] = function COMPREHENSIONTLV_TAG_URL(length) { michael@0: return this.retrieveUrl(length); michael@0: }; michael@0: StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_NEXT_ACTION_IND] = function COMPREHENSIONTLV_TAG_NEXT_ACTION_IND(length) { michael@0: return this.retrieveNextActionList(length); michael@0: }; michael@0: michael@0: function ComprehensionTlvHelperObject(aContext) { michael@0: this.context = aContext; michael@0: } michael@0: ComprehensionTlvHelperObject.prototype = { michael@0: context: null, michael@0: michael@0: /** michael@0: * Decode raw data to a Comprehension-TLV. michael@0: */ michael@0: decode: function() { michael@0: let GsmPDUHelper = this.context.GsmPDUHelper; michael@0: michael@0: let hlen = 0; // For header(tag field + length field) length. michael@0: let temp = GsmPDUHelper.readHexOctet(); michael@0: hlen++; michael@0: michael@0: // TS 101.220, clause 7.1.1 michael@0: let tag, cr; michael@0: switch (temp) { michael@0: // TS 101.220, clause 7.1.1 michael@0: case 0x0: // Not used. michael@0: case 0xff: // Not used. michael@0: case 0x80: // Reserved for future use. michael@0: throw new Error("Invalid octet when parsing Comprehension TLV :" + temp); michael@0: case 0x7f: // Tag is three byte format. michael@0: // TS 101.220 clause 7.1.1.2. michael@0: // | Byte 1 | Byte 2 | Byte 3 | michael@0: // | | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | | michael@0: // | 0x7f |CR | Tag Value | michael@0: tag = (GsmPDUHelper.readHexOctet() << 8) | GsmPDUHelper.readHexOctet(); michael@0: hlen += 2; michael@0: cr = (tag & 0x8000) !== 0; michael@0: tag &= ~0x8000; michael@0: break; michael@0: default: // Tag is single byte format. michael@0: tag = temp; michael@0: // TS 101.220 clause 7.1.1.1. michael@0: // | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | michael@0: // |CR | Tag Value | michael@0: cr = (tag & 0x80) !== 0; michael@0: tag &= ~0x80; michael@0: } michael@0: michael@0: // TS 101.220 clause 7.1.2, Length Encoding. michael@0: // Length | Byte 1 | Byte 2 | Byte 3 | Byte 4 | michael@0: // 0 - 127 | 00 - 7f | N/A | N/A | N/A | michael@0: // 128-255 | 81 | 80 - ff| N/A | N/A | michael@0: // 256-65535| 82 | 0100 - ffff | N/A | michael@0: // 65536- | 83 | 010000 - ffffff | michael@0: // 16777215 michael@0: // michael@0: // Length errors: TS 11.14, clause 6.10.6 michael@0: michael@0: let length; // Data length. michael@0: temp = GsmPDUHelper.readHexOctet(); michael@0: hlen++; michael@0: if (temp < 0x80) { michael@0: length = temp; michael@0: } else if (temp == 0x81) { michael@0: length = GsmPDUHelper.readHexOctet(); michael@0: hlen++; michael@0: if (length < 0x80) { michael@0: throw new Error("Invalid length in Comprehension TLV :" + length); michael@0: } michael@0: } else if (temp == 0x82) { michael@0: length = (GsmPDUHelper.readHexOctet() << 8) | GsmPDUHelper.readHexOctet(); michael@0: hlen += 2; michael@0: if (lenth < 0x0100) { michael@0: throw new Error("Invalid length in 3-byte Comprehension TLV :" + length); michael@0: } michael@0: } else if (temp == 0x83) { michael@0: length = (GsmPDUHelper.readHexOctet() << 16) | michael@0: (GsmPDUHelper.readHexOctet() << 8) | michael@0: GsmPDUHelper.readHexOctet(); michael@0: hlen += 3; michael@0: if (length < 0x010000) { michael@0: throw new Error("Invalid length in 4-byte Comprehension TLV :" + length); michael@0: } michael@0: } else { michael@0: throw new Error("Invalid octet in Comprehension TLV :" + temp); michael@0: } michael@0: michael@0: let ctlv = { michael@0: tag: tag, michael@0: length: length, michael@0: value: this.context.StkProactiveCmdHelper.retrieve(tag, length), michael@0: cr: cr, michael@0: hlen: hlen michael@0: }; michael@0: return ctlv; michael@0: }, michael@0: michael@0: decodeChunks: function(length) { michael@0: let chunks = []; michael@0: let index = 0; michael@0: while (index < length) { michael@0: let tlv = this.decode(); michael@0: chunks.push(tlv); michael@0: index += tlv.length; michael@0: index += tlv.hlen; michael@0: } michael@0: return chunks; michael@0: }, michael@0: michael@0: /** michael@0: * Write Location Info Comprehension TLV. michael@0: * michael@0: * @param loc location Information. michael@0: */ michael@0: writeLocationInfoTlv: function(loc) { michael@0: let GsmPDUHelper = this.context.GsmPDUHelper; michael@0: michael@0: GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_LOCATION_INFO | michael@0: COMPREHENSIONTLV_FLAG_CR); michael@0: GsmPDUHelper.writeHexOctet(loc.gsmCellId > 0xffff ? 9 : 7); michael@0: // From TS 11.14, clause 12.19 michael@0: // "The mobile country code (MCC), the mobile network code (MNC), michael@0: // the location area code (LAC) and the cell ID are michael@0: // coded as in TS 04.08." michael@0: // And from TS 04.08 and TS 24.008, michael@0: // the format is as follows: michael@0: // michael@0: // MCC = MCC_digit_1 + MCC_digit_2 + MCC_digit_3 michael@0: // michael@0: // 8 7 6 5 4 3 2 1 michael@0: // +-------------+-------------+ michael@0: // | MCC digit 2 | MCC digit 1 | octet 2 michael@0: // | MNC digit 3 | MCC digit 3 | octet 3 michael@0: // | MNC digit 2 | MNC digit 1 | octet 4 michael@0: // +-------------+-------------+ michael@0: // michael@0: // Also in TS 24.008 michael@0: // "However a network operator may decide to michael@0: // use only two digits in the MNC in the LAI over the michael@0: // radio interface. In this case, bits 5 to 8 of octet 3 michael@0: // shall be coded as '1111'". michael@0: michael@0: // MCC & MNC, 3 octets michael@0: let mcc = loc.mcc, mnc; michael@0: if (loc.mnc.length == 2) { michael@0: mnc = "F" + loc.mnc; michael@0: } else { michael@0: mnc = loc.mnc[2] + loc.mnc[0] + loc.mnc[1]; michael@0: } michael@0: GsmPDUHelper.writeSwappedNibbleBCD(mcc + mnc); michael@0: michael@0: // LAC, 2 octets michael@0: GsmPDUHelper.writeHexOctet((loc.gsmLocationAreaCode >> 8) & 0xff); michael@0: GsmPDUHelper.writeHexOctet(loc.gsmLocationAreaCode & 0xff); michael@0: michael@0: // Cell Id michael@0: if (loc.gsmCellId > 0xffff) { michael@0: // UMTS/WCDMA, gsmCellId is 28 bits. michael@0: GsmPDUHelper.writeHexOctet((loc.gsmCellId >> 24) & 0xff); michael@0: GsmPDUHelper.writeHexOctet((loc.gsmCellId >> 16) & 0xff); michael@0: GsmPDUHelper.writeHexOctet((loc.gsmCellId >> 8) & 0xff); michael@0: GsmPDUHelper.writeHexOctet(loc.gsmCellId & 0xff); michael@0: } else { michael@0: // GSM, gsmCellId is 16 bits. michael@0: GsmPDUHelper.writeHexOctet((loc.gsmCellId >> 8) & 0xff); michael@0: GsmPDUHelper.writeHexOctet(loc.gsmCellId & 0xff); michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Given a geckoError string, this function translates it into cause value michael@0: * and write the value into buffer. michael@0: * michael@0: * @param geckoError Error string that is passed to gecko. michael@0: */ michael@0: writeCauseTlv: function(geckoError) { michael@0: let GsmPDUHelper = this.context.GsmPDUHelper; michael@0: michael@0: let cause = -1; michael@0: for (let errorNo in RIL_ERROR_TO_GECKO_ERROR) { michael@0: if (geckoError == RIL_ERROR_TO_GECKO_ERROR[errorNo]) { michael@0: cause = errorNo; michael@0: break; michael@0: } michael@0: } michael@0: cause = (cause == -1) ? ERROR_SUCCESS : cause; michael@0: michael@0: GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_CAUSE | michael@0: COMPREHENSIONTLV_FLAG_CR); michael@0: GsmPDUHelper.writeHexOctet(2); // For single cause value. michael@0: michael@0: // TS 04.08, clause 10.5.4.11: National standard code + user location. michael@0: GsmPDUHelper.writeHexOctet(0x60); michael@0: michael@0: // TS 04.08, clause 10.5.4.11: ext bit = 1 + 7 bits for cause. michael@0: // +-----------------+----------------------------------+ michael@0: // | Ext = 1 (1 bit) | Cause (7 bits) | michael@0: // +-----------------+----------------------------------+ michael@0: GsmPDUHelper.writeHexOctet(0x80 | cause); michael@0: }, michael@0: michael@0: writeDateTimeZoneTlv: function(date) { michael@0: let GsmPDUHelper = this.context.GsmPDUHelper; michael@0: michael@0: GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_DATE_TIME_ZONE); michael@0: GsmPDUHelper.writeHexOctet(7); michael@0: GsmPDUHelper.writeTimestamp(date); michael@0: }, michael@0: michael@0: writeLanguageTlv: function(language) { michael@0: let GsmPDUHelper = this.context.GsmPDUHelper; michael@0: michael@0: GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_LANGUAGE); michael@0: GsmPDUHelper.writeHexOctet(2); michael@0: michael@0: // ISO 639-1, Alpha-2 code michael@0: // TS 123.038, clause 6.2.1, GSM 7 bit Default Alphabet michael@0: GsmPDUHelper.writeHexOctet( michael@0: PDU_NL_LOCKING_SHIFT_TABLES[PDU_NL_IDENTIFIER_DEFAULT].indexOf(language[0])); michael@0: GsmPDUHelper.writeHexOctet( michael@0: PDU_NL_LOCKING_SHIFT_TABLES[PDU_NL_IDENTIFIER_DEFAULT].indexOf(language[1])); michael@0: }, michael@0: michael@0: /** michael@0: * Write Timer Value Comprehension TLV. michael@0: * michael@0: * @param seconds length of time during of the timer. michael@0: * @param cr Comprehension Required or not michael@0: */ michael@0: writeTimerValueTlv: function(seconds, cr) { michael@0: let GsmPDUHelper = this.context.GsmPDUHelper; michael@0: michael@0: GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_TIMER_VALUE | michael@0: (cr ? COMPREHENSIONTLV_FLAG_CR : 0)); michael@0: GsmPDUHelper.writeHexOctet(3); michael@0: michael@0: // TS 102.223, clause 8.38 michael@0: // +----------------+------------------+-------------------+ michael@0: // | hours (1 byte) | minutes (1 btye) | secounds (1 byte) | michael@0: // +----------------+------------------+-------------------+ michael@0: GsmPDUHelper.writeSwappedNibbleBCDNum(Math.floor(seconds / 60 / 60)); michael@0: GsmPDUHelper.writeSwappedNibbleBCDNum(Math.floor(seconds / 60) % 60); michael@0: GsmPDUHelper.writeSwappedNibbleBCDNum(seconds % 60); michael@0: }, michael@0: michael@0: getSizeOfLengthOctets: function(length) { michael@0: if (length >= 0x10000) { michael@0: return 4; // 0x83, len_1, len_2, len_3 michael@0: } else if (length >= 0x100) { michael@0: return 3; // 0x82, len_1, len_2 michael@0: } else if (length >= 0x80) { michael@0: return 2; // 0x81, len michael@0: } else { michael@0: return 1; // len michael@0: } michael@0: }, michael@0: michael@0: writeLength: function(length) { michael@0: let GsmPDUHelper = this.context.GsmPDUHelper; michael@0: michael@0: // TS 101.220 clause 7.1.2, Length Encoding. michael@0: // Length | Byte 1 | Byte 2 | Byte 3 | Byte 4 | michael@0: // 0 - 127 | 00 - 7f | N/A | N/A | N/A | michael@0: // 128-255 | 81 | 80 - ff| N/A | N/A | michael@0: // 256-65535| 82 | 0100 - ffff | N/A | michael@0: // 65536- | 83 | 010000 - ffffff | michael@0: // 16777215 michael@0: if (length < 0x80) { michael@0: GsmPDUHelper.writeHexOctet(length); michael@0: } else if (0x80 <= length && length < 0x100) { michael@0: GsmPDUHelper.writeHexOctet(0x81); michael@0: GsmPDUHelper.writeHexOctet(length); michael@0: } else if (0x100 <= length && length < 0x10000) { michael@0: GsmPDUHelper.writeHexOctet(0x82); michael@0: GsmPDUHelper.writeHexOctet((length >> 8) & 0xff); michael@0: GsmPDUHelper.writeHexOctet(length & 0xff); michael@0: } else if (0x10000 <= length && length < 0x1000000) { michael@0: GsmPDUHelper.writeHexOctet(0x83); michael@0: GsmPDUHelper.writeHexOctet((length >> 16) & 0xff); michael@0: GsmPDUHelper.writeHexOctet((length >> 8) & 0xff); michael@0: GsmPDUHelper.writeHexOctet(length & 0xff); michael@0: } else { michael@0: throw new Error("Invalid length value :" + length); michael@0: } michael@0: }, michael@0: }; michael@0: michael@0: function BerTlvHelperObject(aContext) { michael@0: this.context = aContext; michael@0: } michael@0: BerTlvHelperObject.prototype = { michael@0: context: null, michael@0: michael@0: /** michael@0: * Decode Ber TLV. michael@0: * michael@0: * @param dataLen michael@0: * The length of data in bytes. michael@0: */ michael@0: decode: function(dataLen) { michael@0: let GsmPDUHelper = this.context.GsmPDUHelper; michael@0: michael@0: let hlen = 0; michael@0: let tag = GsmPDUHelper.readHexOctet(); michael@0: hlen++; michael@0: michael@0: // The length is coded onto 1 or 2 bytes. michael@0: // Length | Byte 1 | Byte 2 michael@0: // 0 - 127 | length ('00' to '7f') | N/A michael@0: // 128 - 255 | '81' | length ('80' to 'ff') michael@0: let length; michael@0: let temp = GsmPDUHelper.readHexOctet(); michael@0: hlen++; michael@0: if (temp < 0x80) { michael@0: length = temp; michael@0: } else if (temp === 0x81) { michael@0: length = GsmPDUHelper.readHexOctet(); michael@0: hlen++; michael@0: if (length < 0x80) { michael@0: throw new Error("Invalid length " + length); michael@0: } michael@0: } else { michael@0: throw new Error("Invalid length octet " + temp); michael@0: } michael@0: michael@0: // Header + body length check. michael@0: if (dataLen - hlen !== length) { michael@0: throw new Error("Unexpected BerTlvHelper value length!!"); michael@0: } michael@0: michael@0: let method = this[tag]; michael@0: if (typeof method != "function") { michael@0: throw new Error("Unknown Ber tag 0x" + tag.toString(16)); michael@0: } michael@0: michael@0: let value = method.call(this, length); michael@0: michael@0: return { michael@0: tag: tag, michael@0: length: length, michael@0: value: value michael@0: }; michael@0: }, michael@0: michael@0: /** michael@0: * Process the value part for FCP template TLV. michael@0: * michael@0: * @param length michael@0: * The length of data in bytes. michael@0: */ michael@0: processFcpTemplate: function(length) { michael@0: let tlvs = this.decodeChunks(length); michael@0: return tlvs; michael@0: }, michael@0: michael@0: /** michael@0: * Process the value part for proactive command TLV. michael@0: * michael@0: * @param length michael@0: * The length of data in bytes. michael@0: */ michael@0: processProactiveCommand: function(length) { michael@0: let ctlvs = this.context.ComprehensionTlvHelper.decodeChunks(length); michael@0: return ctlvs; michael@0: }, michael@0: michael@0: /** michael@0: * Decode raw data to a Ber-TLV. michael@0: */ michael@0: decodeInnerTlv: function() { michael@0: let GsmPDUHelper = this.context.GsmPDUHelper; michael@0: let tag = GsmPDUHelper.readHexOctet(); michael@0: let length = GsmPDUHelper.readHexOctet(); michael@0: return { michael@0: tag: tag, michael@0: length: length, michael@0: value: this.retrieve(tag, length) michael@0: }; michael@0: }, michael@0: michael@0: decodeChunks: function(length) { michael@0: let chunks = []; michael@0: let index = 0; michael@0: while (index < length) { michael@0: let tlv = this.decodeInnerTlv(); michael@0: if (tlv.value) { michael@0: chunks.push(tlv); michael@0: } michael@0: index += tlv.length; michael@0: // tag + length fields consume 2 bytes. michael@0: index += 2; michael@0: } michael@0: return chunks; michael@0: }, michael@0: michael@0: retrieve: function(tag, length) { michael@0: let method = this[tag]; michael@0: if (typeof method != "function") { michael@0: if (DEBUG) { michael@0: this.context.debug("Unknown Ber tag : 0x" + tag.toString(16)); michael@0: } michael@0: let Buf = this.context.Buf; michael@0: Buf.seekIncoming(length * Buf.PDU_HEX_OCTET_SIZE); michael@0: return null; michael@0: } michael@0: return method.call(this, length); michael@0: }, michael@0: michael@0: /** michael@0: * File Size Data. michael@0: * michael@0: * | Byte | Description | Length | michael@0: * | 1 | Tag | 1 | michael@0: * | 2 | Length | 1 | michael@0: * | 3 to X+24 | Number of allocated data bytes in the file | X | michael@0: * | | , excluding structural information | | michael@0: */ michael@0: retrieveFileSizeData: function(length) { michael@0: let GsmPDUHelper = this.context.GsmPDUHelper; michael@0: let fileSizeData = 0; michael@0: for (let i = 0; i < length; i++) { michael@0: fileSizeData = fileSizeData << 8; michael@0: fileSizeData += GsmPDUHelper.readHexOctet(); michael@0: } michael@0: michael@0: return {fileSizeData: fileSizeData}; michael@0: }, michael@0: michael@0: /** michael@0: * File Descriptor. michael@0: * michael@0: * | Byte | Description | Length | michael@0: * | 1 | Tag | 1 | michael@0: * | 2 | Length | 1 | michael@0: * | 3 | File descriptor byte | 1 | michael@0: * | 4 | Data coding byte | 1 | michael@0: * | 5 ~ 6 | Record length | 2 | michael@0: * | 7 | Number of records | 1 | michael@0: */ michael@0: retrieveFileDescriptor: function(length) { michael@0: let GsmPDUHelper = this.context.GsmPDUHelper; michael@0: let fileDescriptorByte = GsmPDUHelper.readHexOctet(); michael@0: let dataCodingByte = GsmPDUHelper.readHexOctet(); michael@0: // See TS 102 221 Table 11.5, we only care the least 3 bits for the michael@0: // structure of file. michael@0: let fileStructure = fileDescriptorByte & 0x07; michael@0: michael@0: let fileDescriptor = { michael@0: fileStructure: fileStructure michael@0: }; michael@0: // byte 5 ~ 7 are mandatory for linear fixed and cyclic files, otherwise michael@0: // they are not applicable. michael@0: if (fileStructure === UICC_EF_STRUCTURE[EF_TYPE_LINEAR_FIXED] || michael@0: fileStructure === UICC_EF_STRUCTURE[EF_TYPE_CYCLIC]) { michael@0: fileDescriptor.recordLength = (GsmPDUHelper.readHexOctet() << 8) + michael@0: GsmPDUHelper.readHexOctet(); michael@0: fileDescriptor.numOfRecords = GsmPDUHelper.readHexOctet(); michael@0: } michael@0: michael@0: return fileDescriptor; michael@0: }, michael@0: michael@0: /** michael@0: * File identifier. michael@0: * michael@0: * | Byte | Description | Length | michael@0: * | 1 | Tag | 1 | michael@0: * | 2 | Length | 1 | michael@0: * | 3 ~ 4 | File identifier | 2 | michael@0: */ michael@0: retrieveFileIdentifier: function(length) { michael@0: let GsmPDUHelper = this.context.GsmPDUHelper; michael@0: return {fileId : (GsmPDUHelper.readHexOctet() << 8) + michael@0: GsmPDUHelper.readHexOctet()}; michael@0: }, michael@0: michael@0: searchForNextTag: function(tag, iter) { michael@0: for (let [index, tlv] in iter) { michael@0: if (tlv.tag === tag) { michael@0: return tlv; michael@0: } michael@0: } michael@0: return null; michael@0: } michael@0: }; michael@0: BerTlvHelperObject.prototype[BER_FCP_TEMPLATE_TAG] = function BER_FCP_TEMPLATE_TAG(length) { michael@0: return this.processFcpTemplate(length); michael@0: }; michael@0: BerTlvHelperObject.prototype[BER_PROACTIVE_COMMAND_TAG] = function BER_PROACTIVE_COMMAND_TAG(length) { michael@0: return this.processProactiveCommand(length); michael@0: }; michael@0: BerTlvHelperObject.prototype[BER_FCP_FILE_SIZE_DATA_TAG] = function BER_FCP_FILE_SIZE_DATA_TAG(length) { michael@0: return this.retrieveFileSizeData(length); michael@0: }; michael@0: BerTlvHelperObject.prototype[BER_FCP_FILE_DESCRIPTOR_TAG] = function BER_FCP_FILE_DESCRIPTOR_TAG(length) { michael@0: return this.retrieveFileDescriptor(length); michael@0: }; michael@0: BerTlvHelperObject.prototype[BER_FCP_FILE_IDENTIFIER_TAG] = function BER_FCP_FILE_IDENTIFIER_TAG(length) { michael@0: return this.retrieveFileIdentifier(length); michael@0: }; michael@0: michael@0: /** michael@0: * ICC Helper for getting EF path. michael@0: */ michael@0: function ICCFileHelperObject(aContext) { michael@0: this.context = aContext; michael@0: } michael@0: ICCFileHelperObject.prototype = { michael@0: context: null, michael@0: michael@0: /** michael@0: * This function handles only EFs that are common to RUIM, SIM, USIM michael@0: * and other types of ICC cards. michael@0: */ michael@0: getCommonEFPath: function(fileId) { michael@0: switch (fileId) { michael@0: case ICC_EF_ICCID: michael@0: return EF_PATH_MF_SIM; michael@0: case ICC_EF_ADN: michael@0: return EF_PATH_MF_SIM + EF_PATH_DF_TELECOM; michael@0: case ICC_EF_PBR: michael@0: return EF_PATH_MF_SIM + EF_PATH_DF_TELECOM + EF_PATH_DF_PHONEBOOK; michael@0: } michael@0: return null; michael@0: }, michael@0: michael@0: /** michael@0: * This function handles EFs for SIM. michael@0: */ michael@0: getSimEFPath: function(fileId) { michael@0: switch (fileId) { michael@0: case ICC_EF_FDN: michael@0: case ICC_EF_MSISDN: michael@0: case ICC_EF_SMS: michael@0: return EF_PATH_MF_SIM + EF_PATH_DF_TELECOM; michael@0: case ICC_EF_AD: michael@0: case ICC_EF_MBDN: michael@0: case ICC_EF_MWIS: michael@0: case ICC_EF_PLMNsel: michael@0: case ICC_EF_SPN: michael@0: case ICC_EF_SPDI: michael@0: case ICC_EF_SST: michael@0: case ICC_EF_PHASE: michael@0: case ICC_EF_CBMI: michael@0: case ICC_EF_CBMID: michael@0: case ICC_EF_CBMIR: michael@0: case ICC_EF_OPL: michael@0: case ICC_EF_PNN: michael@0: return EF_PATH_MF_SIM + EF_PATH_DF_GSM; michael@0: default: michael@0: return null; michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * This function handles EFs for USIM. michael@0: */ michael@0: getUSimEFPath: function(fileId) { michael@0: switch (fileId) { michael@0: case ICC_EF_AD: michael@0: case ICC_EF_FDN: michael@0: case ICC_EF_MBDN: michael@0: case ICC_EF_MWIS: michael@0: case ICC_EF_UST: michael@0: case ICC_EF_MSISDN: michael@0: case ICC_EF_SPN: michael@0: case ICC_EF_SPDI: michael@0: case ICC_EF_CBMI: michael@0: case ICC_EF_CBMID: michael@0: case ICC_EF_CBMIR: michael@0: case ICC_EF_OPL: michael@0: case ICC_EF_PNN: michael@0: case ICC_EF_SMS: michael@0: return EF_PATH_MF_SIM + EF_PATH_ADF_USIM; michael@0: default: michael@0: // The file ids in USIM phone book entries are decided by the michael@0: // card manufacturer. So if we don't match any of the cases michael@0: // above and if its a USIM return the phone book path. michael@0: return EF_PATH_MF_SIM + EF_PATH_DF_TELECOM + EF_PATH_DF_PHONEBOOK; michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * This function handles EFs for RUIM michael@0: */ michael@0: getRuimEFPath: function(fileId) { michael@0: switch(fileId) { michael@0: case ICC_EF_CSIM_IMSI_M: michael@0: case ICC_EF_CSIM_CDMAHOME: michael@0: case ICC_EF_CSIM_CST: michael@0: case ICC_EF_CSIM_SPN: michael@0: return EF_PATH_MF_SIM + EF_PATH_DF_CDMA; michael@0: case ICC_EF_FDN: michael@0: return EF_PATH_MF_SIM + EF_PATH_DF_TELECOM; michael@0: default: michael@0: return null; michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Helper function for getting the pathId for the specific ICC record michael@0: * depeding on which type of ICC card we are using. michael@0: * michael@0: * @param fileId michael@0: * File id. michael@0: * @return The pathId or null in case of an error or invalid input. michael@0: */ michael@0: getEFPath: function(fileId) { michael@0: let appType = this.context.RIL.appType; michael@0: if (appType == null) { michael@0: return null; michael@0: } michael@0: michael@0: let path = this.getCommonEFPath(fileId); michael@0: if (path) { michael@0: return path; michael@0: } michael@0: michael@0: switch (appType) { michael@0: case CARD_APPTYPE_SIM: michael@0: return this.getSimEFPath(fileId); michael@0: case CARD_APPTYPE_USIM: michael@0: return this.getUSimEFPath(fileId); michael@0: case CARD_APPTYPE_RUIM: michael@0: return this.getRuimEFPath(fileId); michael@0: default: michael@0: return null; michael@0: } michael@0: } michael@0: }; michael@0: michael@0: /** michael@0: * Helper for ICC IO functionalities. michael@0: */ michael@0: function ICCIOHelperObject(aContext) { michael@0: this.context = aContext; michael@0: } michael@0: ICCIOHelperObject.prototype = { michael@0: context: null, michael@0: michael@0: /** michael@0: * Load EF with type 'Linear Fixed'. michael@0: * michael@0: * @param fileId michael@0: * The file to operate on, one of the ICC_EF_* constants. michael@0: * @param recordNumber [optional] michael@0: * The number of the record shall be loaded. michael@0: * @param recordSize [optional] michael@0: * The size of the record. michael@0: * @param callback [optional] michael@0: * The callback function shall be called when the record(s) is read. michael@0: * @param onerror [optional] michael@0: * The callback function shall be called when failure. michael@0: */ michael@0: loadLinearFixedEF: function(options) { michael@0: let cb; michael@0: let readRecord = (function(options) { michael@0: options.command = ICC_COMMAND_READ_RECORD; michael@0: options.p1 = options.recordNumber || 1; // Record number michael@0: options.p2 = READ_RECORD_ABSOLUTE_MODE; michael@0: options.p3 = options.recordSize; michael@0: options.callback = cb || options.callback; michael@0: this.context.RIL.iccIO(options); michael@0: }).bind(this); michael@0: michael@0: options.type = EF_TYPE_LINEAR_FIXED; michael@0: options.pathId = this.context.ICCFileHelper.getEFPath(options.fileId); michael@0: if (options.recordSize) { michael@0: readRecord(options); michael@0: return; michael@0: } michael@0: michael@0: cb = options.callback; michael@0: options.callback = readRecord; michael@0: this.getResponse(options); michael@0: }, michael@0: michael@0: /** michael@0: * Load next record from current record Id. michael@0: */ michael@0: loadNextRecord: function(options) { michael@0: options.p1++; michael@0: this.context.RIL.iccIO(options); michael@0: }, michael@0: michael@0: /** michael@0: * Update EF with type 'Linear Fixed'. michael@0: * michael@0: * @param fileId michael@0: * The file to operate on, one of the ICC_EF_* constants. michael@0: * @param recordNumber michael@0: * The number of the record shall be updated. michael@0: * @param dataWriter [optional] michael@0: * The function for writing string parameter for the ICC_COMMAND_UPDATE_RECORD. michael@0: * @param pin2 [optional] michael@0: * PIN2 is required when updating ICC_EF_FDN. michael@0: * @param callback [optional] michael@0: * The callback function shall be called when the record is updated. michael@0: * @param onerror [optional] michael@0: * The callback function shall be called when failure. michael@0: */ michael@0: updateLinearFixedEF: function(options) { michael@0: if (!options.fileId || !options.recordNumber) { michael@0: throw new Error("Unexpected fileId " + options.fileId + michael@0: " or recordNumber " + options.recordNumber); michael@0: } michael@0: michael@0: options.type = EF_TYPE_LINEAR_FIXED; michael@0: options.pathId = this.context.ICCFileHelper.getEFPath(options.fileId); michael@0: let cb = options.callback; michael@0: options.callback = function callback(options) { michael@0: options.callback = cb; michael@0: options.command = ICC_COMMAND_UPDATE_RECORD; michael@0: options.p1 = options.recordNumber; michael@0: options.p2 = READ_RECORD_ABSOLUTE_MODE; michael@0: options.p3 = options.recordSize; michael@0: this.context.RIL.iccIO(options); michael@0: }.bind(this); michael@0: this.getResponse(options); michael@0: }, michael@0: michael@0: /** michael@0: * Load EF with type 'Transparent'. michael@0: * michael@0: * @param fileId michael@0: * The file to operate on, one of the ICC_EF_* constants. michael@0: * @param callback [optional] michael@0: * The callback function shall be called when the record(s) is read. michael@0: * @param onerror [optional] michael@0: * The callback function shall be called when failure. michael@0: */ michael@0: loadTransparentEF: function(options) { michael@0: options.type = EF_TYPE_TRANSPARENT; michael@0: let cb = options.callback; michael@0: options.callback = function callback(options) { michael@0: options.callback = cb; michael@0: options.command = ICC_COMMAND_READ_BINARY; michael@0: options.p3 = options.fileSize; michael@0: this.context.RIL.iccIO(options); michael@0: }.bind(this); michael@0: this.getResponse(options); michael@0: }, michael@0: michael@0: /** michael@0: * Use ICC_COMMAND_GET_RESPONSE to query the EF. michael@0: * michael@0: * @param fileId michael@0: * The file to operate on, one of the ICC_EF_* constants. michael@0: */ michael@0: getResponse: function(options) { michael@0: options.command = ICC_COMMAND_GET_RESPONSE; michael@0: options.pathId = options.pathId || michael@0: this.context.ICCFileHelper.getEFPath(options.fileId); michael@0: if (!options.pathId) { michael@0: throw new Error("Unknown pathId for " + options.fileId.toString(16)); michael@0: } michael@0: options.p1 = 0; // For GET_RESPONSE, p1 = 0 michael@0: options.p2 = 0; // For GET_RESPONSE, p2 = 0 michael@0: options.p3 = GET_RESPONSE_EF_SIZE_BYTES; michael@0: this.context.RIL.iccIO(options); michael@0: }, michael@0: michael@0: /** michael@0: * Process ICC I/O response. michael@0: */ michael@0: processICCIO: function(options) { michael@0: let func = this[options.command]; michael@0: func.call(this, options); michael@0: }, michael@0: michael@0: /** michael@0: * Process a ICC_COMMAND_GET_RESPONSE type command for REQUEST_SIM_IO. michael@0: */ michael@0: processICCIOGetResponse: function(options) { michael@0: let Buf = this.context.Buf; michael@0: let strLen = Buf.readInt32(); michael@0: michael@0: let peek = this.context.GsmPDUHelper.readHexOctet(); michael@0: Buf.seekIncoming(-1 * Buf.PDU_HEX_OCTET_SIZE); michael@0: if (peek === BER_FCP_TEMPLATE_TAG) { michael@0: this.processUSimGetResponse(options, strLen / 2); michael@0: } else { michael@0: this.processSimGetResponse(options); michael@0: } michael@0: Buf.readStringDelimiter(strLen); michael@0: michael@0: if (options.callback) { michael@0: options.callback(options); michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Helper function for processing USIM get response. michael@0: */ michael@0: processUSimGetResponse: function(options, octetLen) { michael@0: let BerTlvHelper = this.context.BerTlvHelper; michael@0: michael@0: let berTlv = BerTlvHelper.decode(octetLen); michael@0: // See TS 102 221 Table 11.4 for the content order of getResponse. michael@0: let iter = Iterator(berTlv.value); michael@0: let tlv = BerTlvHelper.searchForNextTag(BER_FCP_FILE_DESCRIPTOR_TAG, michael@0: iter); michael@0: if (!tlv || (tlv.value.fileStructure !== UICC_EF_STRUCTURE[options.type])) { michael@0: throw new Error("Expected EF type " + UICC_EF_STRUCTURE[options.type] + michael@0: " but read " + tlv.value.fileStructure); michael@0: } michael@0: michael@0: if (tlv.value.fileStructure === UICC_EF_STRUCTURE[EF_TYPE_LINEAR_FIXED] || michael@0: tlv.value.fileStructure === UICC_EF_STRUCTURE[EF_TYPE_CYCLIC]) { michael@0: options.recordSize = tlv.value.recordLength; michael@0: options.totalRecords = tlv.value.numOfRecords; michael@0: } michael@0: michael@0: tlv = BerTlvHelper.searchForNextTag(BER_FCP_FILE_IDENTIFIER_TAG, iter); michael@0: if (!tlv || (tlv.value.fileId !== options.fileId)) { michael@0: throw new Error("Expected file ID " + options.fileId.toString(16) + michael@0: " but read " + fileId.toString(16)); michael@0: } michael@0: michael@0: tlv = BerTlvHelper.searchForNextTag(BER_FCP_FILE_SIZE_DATA_TAG, iter); michael@0: if (!tlv) { michael@0: throw new Error("Unexpected file size data"); michael@0: } michael@0: options.fileSize = tlv.value.fileSizeData; michael@0: }, michael@0: michael@0: /** michael@0: * Helper function for processing SIM get response. michael@0: */ michael@0: processSimGetResponse: function(options) { michael@0: let Buf = this.context.Buf; michael@0: let GsmPDUHelper = this.context.GsmPDUHelper; michael@0: michael@0: // The format is from TS 51.011, clause 9.2.1 michael@0: michael@0: // Skip RFU, data[0] data[1]. michael@0: Buf.seekIncoming(2 * Buf.PDU_HEX_OCTET_SIZE); michael@0: michael@0: // File size, data[2], data[3] michael@0: options.fileSize = (GsmPDUHelper.readHexOctet() << 8) | michael@0: GsmPDUHelper.readHexOctet(); michael@0: michael@0: // 2 bytes File id. data[4], data[5] michael@0: let fileId = (GsmPDUHelper.readHexOctet() << 8) | michael@0: GsmPDUHelper.readHexOctet(); michael@0: if (fileId != options.fileId) { michael@0: throw new Error("Expected file ID " + options.fileId.toString(16) + michael@0: " but read " + fileId.toString(16)); michael@0: } michael@0: michael@0: // Type of file, data[6] michael@0: let fileType = GsmPDUHelper.readHexOctet(); michael@0: if (fileType != TYPE_EF) { michael@0: throw new Error("Unexpected file type " + fileType); michael@0: } michael@0: michael@0: // Skip 1 byte RFU, data[7], michael@0: // 3 bytes Access conditions, data[8] data[9] data[10], michael@0: // 1 byte File status, data[11], michael@0: // 1 byte Length of the following data, data[12]. michael@0: Buf.seekIncoming(((RESPONSE_DATA_STRUCTURE - RESPONSE_DATA_FILE_TYPE - 1) * michael@0: Buf.PDU_HEX_OCTET_SIZE)); michael@0: michael@0: // Read Structure of EF, data[13] michael@0: let efType = GsmPDUHelper.readHexOctet(); michael@0: if (efType != options.type) { michael@0: throw new Error("Expected EF type " + options.type + " but read " + efType); michael@0: } michael@0: michael@0: // TODO: Bug 952025. michael@0: // Length of a record, data[14]. michael@0: // Only available for LINEAR_FIXED and CYCLIC. michael@0: if (efType == EF_TYPE_LINEAR_FIXED || efType == EF_TYPE_CYCLIC) { michael@0: options.recordSize = GsmPDUHelper.readHexOctet(); michael@0: options.totalRecords = options.fileSize / options.recordSize; michael@0: } else { michael@0: Buf.seekIncoming(1 * Buf.PDU_HEX_OCTET_SIZE); michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Process a ICC_COMMAND_READ_RECORD type command for REQUEST_SIM_IO. michael@0: */ michael@0: processICCIOReadRecord: function(options) { michael@0: if (options.callback) { michael@0: options.callback(options); michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Process a ICC_COMMAND_READ_BINARY type command for REQUEST_SIM_IO. michael@0: */ michael@0: processICCIOReadBinary: function(options) { michael@0: if (options.callback) { michael@0: options.callback(options); michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Process a ICC_COMMAND_UPDATE_RECORD type command for REQUEST_SIM_IO. michael@0: */ michael@0: processICCIOUpdateRecord: function(options) { michael@0: if (options.callback) { michael@0: options.callback(options); michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Process ICC IO error. michael@0: */ michael@0: processICCIOError: function(options) { michael@0: let requestError = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError]; michael@0: if (DEBUG) { michael@0: // See GSM11.11, TS 51.011 clause 9.4, and ISO 7816-4 for the error michael@0: // description. michael@0: let errorMsg = "ICC I/O Error code " + requestError + michael@0: " EF id = " + options.fileId.toString(16) + michael@0: " command = " + options.command.toString(16); michael@0: if (options.sw1 && options.sw2) { michael@0: errorMsg += "(" + options.sw1.toString(16) + michael@0: "/" + options.sw2.toString(16) + ")"; michael@0: } michael@0: this.context.debug(errorMsg); michael@0: } michael@0: if (options.onerror) { michael@0: options.onerror(requestError); michael@0: } michael@0: }, michael@0: }; michael@0: ICCIOHelperObject.prototype[ICC_COMMAND_SEEK] = null; michael@0: ICCIOHelperObject.prototype[ICC_COMMAND_READ_BINARY] = function ICC_COMMAND_READ_BINARY(options) { michael@0: this.processICCIOReadBinary(options); michael@0: }; michael@0: ICCIOHelperObject.prototype[ICC_COMMAND_READ_RECORD] = function ICC_COMMAND_READ_RECORD(options) { michael@0: this.processICCIOReadRecord(options); michael@0: }; michael@0: ICCIOHelperObject.prototype[ICC_COMMAND_GET_RESPONSE] = function ICC_COMMAND_GET_RESPONSE(options) { michael@0: this.processICCIOGetResponse(options); michael@0: }; michael@0: ICCIOHelperObject.prototype[ICC_COMMAND_UPDATE_BINARY] = null; michael@0: ICCIOHelperObject.prototype[ICC_COMMAND_UPDATE_RECORD] = function ICC_COMMAND_UPDATE_RECORD(options) { michael@0: this.processICCIOUpdateRecord(options); michael@0: }; michael@0: michael@0: /** michael@0: * Helper for ICC records. michael@0: */ michael@0: function ICCRecordHelperObject(aContext) { michael@0: this.context = aContext; michael@0: } michael@0: ICCRecordHelperObject.prototype = { michael@0: context: null, michael@0: michael@0: /** michael@0: * Fetch ICC records. michael@0: */ michael@0: fetchICCRecords: function() { michael@0: switch (this.context.RIL.appType) { michael@0: case CARD_APPTYPE_SIM: michael@0: case CARD_APPTYPE_USIM: michael@0: this.context.SimRecordHelper.fetchSimRecords(); michael@0: break; michael@0: case CARD_APPTYPE_RUIM: michael@0: this.context.RuimRecordHelper.fetchRuimRecords(); michael@0: break; michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Read the ICCID. michael@0: */ michael@0: readICCID: function() { michael@0: function callback() { michael@0: let Buf = this.context.Buf; michael@0: let RIL = this.context.RIL; michael@0: michael@0: let strLen = Buf.readInt32(); michael@0: let octetLen = strLen / 2; michael@0: RIL.iccInfo.iccid = michael@0: this.context.GsmPDUHelper.readSwappedNibbleBcdString(octetLen, true); michael@0: // Consumes the remaining buffer if any. michael@0: let unReadBuffer = this.context.Buf.getReadAvailable() - michael@0: this.context.Buf.PDU_HEX_OCTET_SIZE; michael@0: if (unReadBuffer > 0) { michael@0: this.context.Buf.seekIncoming(unReadBuffer); michael@0: } michael@0: Buf.readStringDelimiter(strLen); michael@0: michael@0: if (DEBUG) this.context.debug("ICCID: " + RIL.iccInfo.iccid); michael@0: if (RIL.iccInfo.iccid) { michael@0: this.context.ICCUtilsHelper.handleICCInfoChange(); michael@0: RIL.reportStkServiceIsRunning(); michael@0: } michael@0: } michael@0: michael@0: this.context.ICCIOHelper.loadTransparentEF({ michael@0: fileId: ICC_EF_ICCID, michael@0: callback: callback.bind(this) michael@0: }); michael@0: }, michael@0: michael@0: /** michael@0: * Read ICC ADN like EF, i.e. EF_ADN, EF_FDN. michael@0: * michael@0: * @param fileId EF id of the ADN or FDN. michael@0: * @param onsuccess Callback to be called when success. michael@0: * @param onerror Callback to be called when error. michael@0: */ michael@0: readADNLike: function(fileId, onsuccess, onerror) { michael@0: let ICCIOHelper = this.context.ICCIOHelper; michael@0: michael@0: function callback(options) { michael@0: let contact = michael@0: this.context.ICCPDUHelper.readAlphaIdDiallingNumber(options.recordSize); michael@0: if (contact) { michael@0: contact.recordId = options.p1; michael@0: contacts.push(contact); michael@0: } michael@0: michael@0: if (options.p1 < options.totalRecords) { michael@0: ICCIOHelper.loadNextRecord(options); michael@0: } else { michael@0: if (DEBUG) { michael@0: for (let i = 0; i < contacts.length; i++) { michael@0: this.context.debug("contact [" + i + "] " + michael@0: JSON.stringify(contacts[i])); michael@0: } michael@0: } michael@0: if (onsuccess) { michael@0: onsuccess(contacts); michael@0: } michael@0: } michael@0: } michael@0: michael@0: let contacts = []; michael@0: ICCIOHelper.loadLinearFixedEF({fileId: fileId, michael@0: callback: callback.bind(this), michael@0: onerror: onerror}); michael@0: }, michael@0: michael@0: /** michael@0: * Update ICC ADN like EFs, like EF_ADN, EF_FDN. michael@0: * michael@0: * @param fileId EF id of the ADN or FDN. michael@0: * @param contact The contact will be updated. (Shall have recordId property) michael@0: * @param pin2 PIN2 is required when updating ICC_EF_FDN. michael@0: * @param onsuccess Callback to be called when success. michael@0: * @param onerror Callback to be called when error. michael@0: */ michael@0: updateADNLike: function(fileId, contact, pin2, onsuccess, onerror) { michael@0: function dataWriter(recordSize) { michael@0: this.context.ICCPDUHelper.writeAlphaIdDiallingNumber(recordSize, michael@0: contact.alphaId, michael@0: contact.number); michael@0: } michael@0: michael@0: function callback(options) { michael@0: if (onsuccess) { michael@0: onsuccess(); michael@0: } michael@0: } michael@0: michael@0: if (!contact || !contact.recordId) { michael@0: if (onerror) onerror(GECKO_ERROR_INVALID_PARAMETER); michael@0: return; michael@0: } michael@0: michael@0: this.context.ICCIOHelper.updateLinearFixedEF({ michael@0: fileId: fileId, michael@0: recordNumber: contact.recordId, michael@0: dataWriter: dataWriter.bind(this), michael@0: pin2: pin2, michael@0: callback: callback.bind(this), michael@0: onerror: onerror michael@0: }); michael@0: }, michael@0: michael@0: /** michael@0: * Read USIM/RUIM Phonebook. michael@0: * michael@0: * @param onsuccess Callback to be called when success. michael@0: * @param onerror Callback to be called when error. michael@0: */ michael@0: readPBR: function(onsuccess, onerror) { michael@0: let Buf = this.context.Buf; michael@0: let GsmPDUHelper = this.context.GsmPDUHelper; michael@0: let ICCIOHelper = this.context.ICCIOHelper; michael@0: let ICCUtilsHelper = this.context.ICCUtilsHelper; michael@0: let RIL = this.context.RIL; michael@0: michael@0: function callback(options) { michael@0: let strLen = Buf.readInt32(); michael@0: let octetLen = strLen / 2, readLen = 0; michael@0: michael@0: let pbrTlvs = []; michael@0: while (readLen < octetLen) { michael@0: let tag = GsmPDUHelper.readHexOctet(); michael@0: if (tag == 0xff) { michael@0: readLen++; michael@0: Buf.seekIncoming((octetLen - readLen) * Buf.PDU_HEX_OCTET_SIZE); michael@0: break; michael@0: } michael@0: michael@0: let tlvLen = GsmPDUHelper.readHexOctet(); michael@0: let tlvs = ICCUtilsHelper.decodeSimTlvs(tlvLen); michael@0: pbrTlvs.push({tag: tag, michael@0: length: tlvLen, michael@0: value: tlvs}); michael@0: michael@0: readLen += tlvLen + 2; // +2 for tag and tlvLen michael@0: } michael@0: Buf.readStringDelimiter(strLen); michael@0: michael@0: if (pbrTlvs.length > 0) { michael@0: let pbr = ICCUtilsHelper.parsePbrTlvs(pbrTlvs); michael@0: // EF_ADN is mandatory if and only if DF_PHONEBOOK is present. michael@0: if (!pbr.adn) { michael@0: if (onerror) onerror("Cannot access ADN."); michael@0: return; michael@0: } michael@0: pbrs.push(pbr); michael@0: } michael@0: michael@0: if (options.p1 < options.totalRecords) { michael@0: ICCIOHelper.loadNextRecord(options); michael@0: } else { michael@0: if (onsuccess) { michael@0: RIL.iccInfoPrivate.pbrs = pbrs; michael@0: onsuccess(pbrs); michael@0: } michael@0: } michael@0: } michael@0: michael@0: if (RIL.iccInfoPrivate.pbrs) { michael@0: onsuccess(RIL.iccInfoPrivate.pbrs); michael@0: return; michael@0: } michael@0: michael@0: let pbrs = []; michael@0: ICCIOHelper.loadLinearFixedEF({fileId : ICC_EF_PBR, michael@0: callback: callback.bind(this), michael@0: onerror: onerror}); michael@0: }, michael@0: michael@0: /** michael@0: * Cache EF_IAP record size. michael@0: */ michael@0: _iapRecordSize: null, michael@0: michael@0: /** michael@0: * Read ICC EF_IAP. (Index Administration Phonebook) michael@0: * michael@0: * @see TS 131.102, clause 4.4.2.2 michael@0: * michael@0: * @param fileId EF id of the IAP. michael@0: * @param recordNumber The number of the record shall be loaded. michael@0: * @param onsuccess Callback to be called when success. michael@0: * @param onerror Callback to be called when error. michael@0: */ michael@0: readIAP: function(fileId, recordNumber, onsuccess, onerror) { michael@0: function callback(options) { michael@0: let Buf = this.context.Buf; michael@0: let strLen = Buf.readInt32(); michael@0: let octetLen = strLen / 2; michael@0: this._iapRecordSize = options.recordSize; michael@0: michael@0: let iap = this.context.GsmPDUHelper.readHexOctetArray(octetLen); michael@0: Buf.readStringDelimiter(strLen); michael@0: michael@0: if (onsuccess) { michael@0: onsuccess(iap); michael@0: } michael@0: } michael@0: michael@0: this.context.ICCIOHelper.loadLinearFixedEF({ michael@0: fileId: fileId, michael@0: recordNumber: recordNumber, michael@0: recordSize: this._iapRecordSize, michael@0: callback: callback.bind(this), michael@0: onerror: onerror michael@0: }); michael@0: }, michael@0: michael@0: /** michael@0: * Update USIM/RUIM Phonebook EF_IAP. michael@0: * michael@0: * @see TS 131.102, clause 4.4.2.13 michael@0: * michael@0: * @param fileId EF id of the IAP. michael@0: * @param recordNumber The identifier of the record shall be updated. michael@0: * @param iap The IAP value to be written. michael@0: * @param onsuccess Callback to be called when success. michael@0: * @param onerror Callback to be called when error. michael@0: */ michael@0: updateIAP: function(fileId, recordNumber, iap, onsuccess, onerror) { michael@0: let dataWriter = function dataWriter(recordSize) { michael@0: let Buf = this.context.Buf; michael@0: let GsmPDUHelper = this.context.GsmPDUHelper; michael@0: michael@0: // Write String length michael@0: let strLen = recordSize * 2; michael@0: Buf.writeInt32(strLen); michael@0: michael@0: for (let i = 0; i < iap.length; i++) { michael@0: GsmPDUHelper.writeHexOctet(iap[i]); michael@0: } michael@0: michael@0: Buf.writeStringDelimiter(strLen); michael@0: }.bind(this); michael@0: michael@0: this.context.ICCIOHelper.updateLinearFixedEF({ michael@0: fileId: fileId, michael@0: recordNumber: recordNumber, michael@0: dataWriter: dataWriter, michael@0: callback: onsuccess, michael@0: onerror: onerror michael@0: }); michael@0: }, michael@0: michael@0: /** michael@0: * Cache EF_Email record size. michael@0: */ michael@0: _emailRecordSize: null, michael@0: michael@0: /** michael@0: * Read USIM/RUIM Phonebook EF_EMAIL. michael@0: * michael@0: * @see TS 131.102, clause 4.4.2.13 michael@0: * michael@0: * @param fileId EF id of the EMAIL. michael@0: * @param fileType The type of the EMAIL, one of the ICC_USIM_TYPE* constants. michael@0: * @param recordNumber The number of the record shall be loaded. michael@0: * @param onsuccess Callback to be called when success. michael@0: * @param onerror Callback to be called when error. michael@0: */ michael@0: readEmail: function(fileId, fileType, recordNumber, onsuccess, onerror) { michael@0: function callback(options) { michael@0: let Buf = this.context.Buf; michael@0: let ICCPDUHelper = this.context.ICCPDUHelper; michael@0: michael@0: let strLen = Buf.readInt32(); michael@0: let octetLen = strLen / 2; michael@0: let email = null; michael@0: this._emailRecordSize = options.recordSize; michael@0: michael@0: // Read contact's email michael@0: // michael@0: // | Byte | Description | Length | M/O michael@0: // | 1 ~ X | E-mail Address | X | M michael@0: // | X+1 | ADN file SFI | 1 | C michael@0: // | X+2 | ADN file Record Identifier | 1 | C michael@0: // Note: The fields marked as C above are mandatort if the file michael@0: // is not type 1 (as specified in EF_PBR) michael@0: if (fileType == ICC_USIM_TYPE1_TAG) { michael@0: email = ICCPDUHelper.read8BitUnpackedToString(octetLen); michael@0: } else { michael@0: email = ICCPDUHelper.read8BitUnpackedToString(octetLen - 2); michael@0: michael@0: // Consumes the remaining buffer michael@0: Buf.seekIncoming(2 * Buf.PDU_HEX_OCTET_SIZE); // For ADN SFI and Record Identifier michael@0: } michael@0: michael@0: Buf.readStringDelimiter(strLen); michael@0: michael@0: if (onsuccess) { michael@0: onsuccess(email); michael@0: } michael@0: } michael@0: michael@0: this.context.ICCIOHelper.loadLinearFixedEF({ michael@0: fileId: fileId, michael@0: recordNumber: recordNumber, michael@0: recordSize: this._emailRecordSize, michael@0: callback: callback.bind(this), michael@0: onerror: onerror michael@0: }); michael@0: }, michael@0: michael@0: /** michael@0: * Update USIM/RUIM Phonebook EF_EMAIL. michael@0: * michael@0: * @see TS 131.102, clause 4.4.2.13 michael@0: * michael@0: * @param pbr Phonebook Reference File. michael@0: * @param recordNumber The identifier of the record shall be updated. michael@0: * @param email The value to be written. michael@0: * @param adnRecordId The record Id of ADN, only needed if the fileType of Email is TYPE2. michael@0: * @param onsuccess Callback to be called when success. michael@0: * @param onerror Callback to be called when error. michael@0: */ michael@0: updateEmail: function(pbr, recordNumber, email, adnRecordId, onsuccess, onerror) { michael@0: let fileId = pbr[USIM_PBR_EMAIL].fileId; michael@0: let fileType = pbr[USIM_PBR_EMAIL].fileType; michael@0: let dataWriter = function dataWriter(recordSize) { michael@0: let Buf = this.context.Buf; michael@0: let GsmPDUHelper = this.context.GsmPDUHelper; michael@0: let ICCPDUHelper = this.context.ICCPDUHelper; michael@0: michael@0: // Write String length michael@0: let strLen = recordSize * 2; michael@0: Buf.writeInt32(strLen); michael@0: michael@0: if (fileType == ICC_USIM_TYPE1_TAG) { michael@0: ICCPDUHelper.writeStringTo8BitUnpacked(recordSize, email); michael@0: } else { michael@0: ICCPDUHelper.writeStringTo8BitUnpacked(recordSize - 2, email); michael@0: GsmPDUHelper.writeHexOctet(pbr.adn.sfi || 0xff); michael@0: GsmPDUHelper.writeHexOctet(adnRecordId); michael@0: } michael@0: michael@0: Buf.writeStringDelimiter(strLen); michael@0: }.bind(this); michael@0: michael@0: this.context.ICCIOHelper.updateLinearFixedEF({ michael@0: fileId: fileId, michael@0: recordNumber: recordNumber, michael@0: dataWriter: dataWriter, michael@0: callback: onsuccess, michael@0: onerror: onerror michael@0: }); michael@0: }, michael@0: michael@0: /** michael@0: * Cache EF_ANR record size. michael@0: */ michael@0: _anrRecordSize: null, michael@0: michael@0: /** michael@0: * Read USIM/RUIM Phonebook EF_ANR. michael@0: * michael@0: * @see TS 131.102, clause 4.4.2.9 michael@0: * michael@0: * @param fileId EF id of the ANR. michael@0: * @param fileType One of the ICC_USIM_TYPE* constants. michael@0: * @param recordNumber The number of the record shall be loaded. michael@0: * @param onsuccess Callback to be called when success. michael@0: * @param onerror Callback to be called when error. michael@0: */ michael@0: readANR: function(fileId, fileType, recordNumber, onsuccess, onerror) { michael@0: function callback(options) { michael@0: let Buf = this.context.Buf; michael@0: let strLen = Buf.readInt32(); michael@0: let number = null; michael@0: this._anrRecordSize = options.recordSize; michael@0: michael@0: // Skip EF_AAS Record ID. michael@0: Buf.seekIncoming(1 * Buf.PDU_HEX_OCTET_SIZE); michael@0: michael@0: number = this.context.ICCPDUHelper.readNumberWithLength(); michael@0: michael@0: // Skip 2 unused octets, CCP and EXT1. michael@0: Buf.seekIncoming(2 * Buf.PDU_HEX_OCTET_SIZE); michael@0: michael@0: // For Type 2 there are two extra octets. michael@0: if (fileType == ICC_USIM_TYPE2_TAG) { michael@0: // Skip 2 unused octets, ADN SFI and Record Identifier. michael@0: Buf.seekIncoming(2 * Buf.PDU_HEX_OCTET_SIZE); michael@0: } michael@0: michael@0: Buf.readStringDelimiter(strLen); michael@0: michael@0: if (onsuccess) { michael@0: onsuccess(number); michael@0: } michael@0: } michael@0: michael@0: this.context.ICCIOHelper.loadLinearFixedEF({ michael@0: fileId: fileId, michael@0: recordNumber: recordNumber, michael@0: recordSize: this._anrRecordSize, michael@0: callback: callback.bind(this), michael@0: onerror: onerror michael@0: }); michael@0: }, michael@0: michael@0: /** michael@0: * Update USIM/RUIM Phonebook EF_ANR. michael@0: * michael@0: * @see TS 131.102, clause 4.4.2.9 michael@0: * michael@0: * @param pbr Phonebook Reference File. michael@0: * @param recordNumber The identifier of the record shall be updated. michael@0: * @param number The value to be written. michael@0: * @param adnRecordId The record Id of ADN, only needed if the fileType of Email is TYPE2. michael@0: * @param onsuccess Callback to be called when success. michael@0: * @param onerror Callback to be called when error. michael@0: */ michael@0: updateANR: function(pbr, recordNumber, number, adnRecordId, onsuccess, onerror) { michael@0: let fileId = pbr[USIM_PBR_ANR0].fileId; michael@0: let fileType = pbr[USIM_PBR_ANR0].fileType; michael@0: let dataWriter = function dataWriter(recordSize) { michael@0: let Buf = this.context.Buf; michael@0: let GsmPDUHelper = this.context.GsmPDUHelper; michael@0: michael@0: // Write String length michael@0: let strLen = recordSize * 2; michael@0: Buf.writeInt32(strLen); michael@0: michael@0: // EF_AAS record Id. Unused for now. michael@0: GsmPDUHelper.writeHexOctet(0xff); michael@0: michael@0: this.context.ICCPDUHelper.writeNumberWithLength(number); michael@0: michael@0: // Write unused octets 0xff, CCP and EXT1. michael@0: GsmPDUHelper.writeHexOctet(0xff); michael@0: GsmPDUHelper.writeHexOctet(0xff); michael@0: michael@0: // For Type 2 there are two extra octets. michael@0: if (fileType == ICC_USIM_TYPE2_TAG) { michael@0: GsmPDUHelper.writeHexOctet(pbr.adn.sfi || 0xff); michael@0: GsmPDUHelper.writeHexOctet(adnRecordId); michael@0: } michael@0: michael@0: Buf.writeStringDelimiter(strLen); michael@0: }.bind(this); michael@0: michael@0: this.context.ICCIOHelper.updateLinearFixedEF({ michael@0: fileId: fileId, michael@0: recordNumber: recordNumber, michael@0: dataWriter: dataWriter, michael@0: callback: onsuccess, michael@0: onerror: onerror michael@0: }); michael@0: }, michael@0: michael@0: /** michael@0: * Find free record id. michael@0: * michael@0: * @param fileId EF id. michael@0: * @param onsuccess Callback to be called when success. michael@0: * @param onerror Callback to be called when error. michael@0: */ michael@0: findFreeRecordId: function(fileId, onsuccess, onerror) { michael@0: let ICCIOHelper = this.context.ICCIOHelper; michael@0: michael@0: function callback(options) { michael@0: let Buf = this.context.Buf; michael@0: let GsmPDUHelper = this.context.GsmPDUHelper; michael@0: michael@0: let strLen = Buf.readInt32(); michael@0: let octetLen = strLen / 2; michael@0: let readLen = 0; michael@0: michael@0: while (readLen < octetLen) { michael@0: let octet = GsmPDUHelper.readHexOctet(); michael@0: readLen++; michael@0: if (octet != 0xff) { michael@0: break; michael@0: } michael@0: } michael@0: michael@0: if (readLen == octetLen) { michael@0: // Find free record. michael@0: if (onsuccess) { michael@0: onsuccess(options.p1); michael@0: } michael@0: return; michael@0: } else { michael@0: Buf.seekIncoming((octetLen - readLen) * Buf.PDU_HEX_OCTET_SIZE); michael@0: } michael@0: michael@0: Buf.readStringDelimiter(strLen); michael@0: michael@0: if (options.p1 < options.totalRecords) { michael@0: ICCIOHelper.loadNextRecord(options); michael@0: } else { michael@0: // No free record found. michael@0: if (DEBUG) { michael@0: this.context.debug(CONTACT_ERR_NO_FREE_RECORD_FOUND); michael@0: } michael@0: onerror(CONTACT_ERR_NO_FREE_RECORD_FOUND); michael@0: } michael@0: } michael@0: michael@0: ICCIOHelper.loadLinearFixedEF({fileId: fileId, michael@0: callback: callback.bind(this), michael@0: onerror: onerror}); michael@0: }, michael@0: }; michael@0: michael@0: /** michael@0: * Helper for (U)SIM Records. michael@0: */ michael@0: function SimRecordHelperObject(aContext) { michael@0: this.context = aContext; michael@0: } michael@0: SimRecordHelperObject.prototype = { michael@0: context: null, michael@0: michael@0: /** michael@0: * Fetch (U)SIM records. michael@0: */ michael@0: fetchSimRecords: function() { michael@0: this.context.RIL.getIMSI(); michael@0: this.readAD(); michael@0: this.readSST(); michael@0: }, michael@0: michael@0: /** michael@0: * Read EF_phase. michael@0: * This EF is only available in SIM. michael@0: */ michael@0: readSimPhase: function() { michael@0: function callback() { michael@0: let Buf = this.context.Buf; michael@0: let strLen = Buf.readInt32(); michael@0: michael@0: let GsmPDUHelper = this.context.GsmPDUHelper; michael@0: let phase = GsmPDUHelper.readHexOctet(); michael@0: // If EF_phase is coded '03' or greater, an ME supporting STK shall michael@0: // perform the PROFILE DOWNLOAD procedure. michael@0: if (RILQUIRKS_SEND_STK_PROFILE_DOWNLOAD && michael@0: phase >= ICC_PHASE_2_PROFILE_DOWNLOAD_REQUIRED) { michael@0: this.context.RIL.sendStkTerminalProfile(STK_SUPPORTED_TERMINAL_PROFILE); michael@0: } michael@0: michael@0: Buf.readStringDelimiter(strLen); michael@0: } michael@0: michael@0: this.context.ICCIOHelper.loadTransparentEF({ michael@0: fileId: ICC_EF_PHASE, michael@0: callback: callback.bind(this) michael@0: }); michael@0: }, michael@0: michael@0: /** michael@0: * Read the MSISDN from the (U)SIM. michael@0: */ michael@0: readMSISDN: function() { michael@0: function callback(options) { michael@0: let RIL = this.context.RIL; michael@0: michael@0: let contact = michael@0: this.context.ICCPDUHelper.readAlphaIdDiallingNumber(options.recordSize); michael@0: if (!contact || michael@0: (RIL.iccInfo.msisdn !== undefined && michael@0: RIL.iccInfo.msisdn === contact.number)) { michael@0: return; michael@0: } michael@0: RIL.iccInfo.msisdn = contact.number; michael@0: if (DEBUG) this.context.debug("MSISDN: " + RIL.iccInfo.msisdn); michael@0: this.context.ICCUtilsHelper.handleICCInfoChange(); michael@0: } michael@0: michael@0: this.context.ICCIOHelper.loadLinearFixedEF({ michael@0: fileId: ICC_EF_MSISDN, michael@0: callback: callback.bind(this) michael@0: }); michael@0: }, michael@0: michael@0: /** michael@0: * Read the AD (Administrative Data) from the (U)SIM. michael@0: */ michael@0: readAD: function() { michael@0: function callback() { michael@0: let Buf = this.context.Buf; michael@0: let strLen = Buf.readInt32(); michael@0: // Each octet is encoded into two chars. michael@0: let octetLen = strLen / 2; michael@0: let ad = this.context.GsmPDUHelper.readHexOctetArray(octetLen); michael@0: Buf.readStringDelimiter(strLen); michael@0: michael@0: if (DEBUG) { michael@0: let str = ""; michael@0: for (let i = 0; i < ad.length; i++) { michael@0: str += ad[i] + ", "; michael@0: } michael@0: this.context.debug("AD: " + str); michael@0: } michael@0: michael@0: let ICCUtilsHelper = this.context.ICCUtilsHelper; michael@0: let RIL = this.context.RIL; michael@0: // The 4th byte of the response is the length of MNC. michael@0: let mccMnc = ICCUtilsHelper.parseMccMncFromImsi(RIL.iccInfoPrivate.imsi, michael@0: ad && ad[3]); michael@0: if (mccMnc) { michael@0: RIL.iccInfo.mcc = mccMnc.mcc; michael@0: RIL.iccInfo.mnc = mccMnc.mnc; michael@0: ICCUtilsHelper.handleICCInfoChange(); michael@0: } michael@0: } michael@0: michael@0: this.context.ICCIOHelper.loadTransparentEF({ michael@0: fileId: ICC_EF_AD, michael@0: callback: callback.bind(this) michael@0: }); michael@0: }, michael@0: michael@0: /** michael@0: * Read the SPN (Service Provider Name) from the (U)SIM. michael@0: */ michael@0: readSPN: function() { michael@0: function callback() { michael@0: let Buf = this.context.Buf; michael@0: let strLen = Buf.readInt32(); michael@0: // Each octet is encoded into two chars. michael@0: let octetLen = strLen / 2; michael@0: let spnDisplayCondition = this.context.GsmPDUHelper.readHexOctet(); michael@0: // Minus 1 because the first octet is used to store display condition. michael@0: let spn = this.context.ICCPDUHelper.readAlphaIdentifier(octetLen - 1); michael@0: Buf.readStringDelimiter(strLen); michael@0: michael@0: if (DEBUG) { michael@0: this.context.debug("SPN: spn = " + spn + michael@0: ", spnDisplayCondition = " + spnDisplayCondition); michael@0: } michael@0: michael@0: let RIL = this.context.RIL; michael@0: RIL.iccInfoPrivate.spnDisplayCondition = spnDisplayCondition; michael@0: RIL.iccInfo.spn = spn; michael@0: let ICCUtilsHelper = this.context.ICCUtilsHelper; michael@0: ICCUtilsHelper.updateDisplayCondition(); michael@0: ICCUtilsHelper.handleICCInfoChange(); michael@0: } michael@0: michael@0: this.context.ICCIOHelper.loadTransparentEF({ michael@0: fileId: ICC_EF_SPN, michael@0: callback: callback.bind(this) michael@0: }); michael@0: }, michael@0: michael@0: /** michael@0: * Read the (U)SIM Service Table from the (U)SIM. michael@0: */ michael@0: readSST: function() { michael@0: function callback() { michael@0: let Buf = this.context.Buf; michael@0: let RIL = this.context.RIL; michael@0: michael@0: let strLen = Buf.readInt32(); michael@0: // Each octet is encoded into two chars. michael@0: let octetLen = strLen / 2; michael@0: let sst = this.context.GsmPDUHelper.readHexOctetArray(octetLen); michael@0: Buf.readStringDelimiter(strLen); michael@0: RIL.iccInfoPrivate.sst = sst; michael@0: if (DEBUG) { michael@0: let str = ""; michael@0: for (let i = 0; i < sst.length; i++) { michael@0: str += sst[i] + ", "; michael@0: } michael@0: this.context.debug("SST: " + str); michael@0: } michael@0: michael@0: let ICCUtilsHelper = this.context.ICCUtilsHelper; michael@0: if (ICCUtilsHelper.isICCServiceAvailable("MSISDN")) { michael@0: if (DEBUG) this.context.debug("MSISDN: MSISDN is available"); michael@0: this.readMSISDN(); michael@0: } else { michael@0: if (DEBUG) this.context.debug("MSISDN: MSISDN service is not available"); michael@0: } michael@0: michael@0: // Fetch SPN and PLMN list, if some of them are available. michael@0: if (ICCUtilsHelper.isICCServiceAvailable("SPN")) { michael@0: if (DEBUG) this.context.debug("SPN: SPN is available"); michael@0: this.readSPN(); michael@0: } else { michael@0: if (DEBUG) this.context.debug("SPN: SPN service is not available"); michael@0: } michael@0: michael@0: if (ICCUtilsHelper.isICCServiceAvailable("MDN")) { michael@0: if (DEBUG) this.context.debug("MDN: MDN available."); michael@0: this.readMBDN(); michael@0: } else { michael@0: if (DEBUG) this.context.debug("MDN: MDN service is not available"); michael@0: } michael@0: michael@0: if (ICCUtilsHelper.isICCServiceAvailable("MWIS")) { michael@0: if (DEBUG) this.context.debug("MWIS: MWIS is available"); michael@0: this.readMWIS(); michael@0: } else { michael@0: if (DEBUG) this.context.debug("MWIS: MWIS is not available"); michael@0: } michael@0: michael@0: if (ICCUtilsHelper.isICCServiceAvailable("SPDI")) { michael@0: if (DEBUG) this.context.debug("SPDI: SPDI available."); michael@0: this.readSPDI(); michael@0: } else { michael@0: if (DEBUG) this.context.debug("SPDI: SPDI service is not available"); michael@0: } michael@0: michael@0: if (ICCUtilsHelper.isICCServiceAvailable("PNN")) { michael@0: if (DEBUG) this.context.debug("PNN: PNN is available"); michael@0: this.readPNN(); michael@0: } else { michael@0: if (DEBUG) this.context.debug("PNN: PNN is not available"); michael@0: } michael@0: michael@0: if (ICCUtilsHelper.isICCServiceAvailable("OPL")) { michael@0: if (DEBUG) this.context.debug("OPL: OPL is available"); michael@0: this.readOPL(); michael@0: } else { michael@0: if (DEBUG) this.context.debug("OPL: OPL is not available"); michael@0: } michael@0: michael@0: if (ICCUtilsHelper.isICCServiceAvailable("CBMI")) { michael@0: this.readCBMI(); michael@0: } else { michael@0: RIL.cellBroadcastConfigs.CBMI = null; michael@0: } michael@0: if (ICCUtilsHelper.isICCServiceAvailable("DATA_DOWNLOAD_SMS_CB")) { michael@0: this.readCBMID(); michael@0: } else { michael@0: RIL.cellBroadcastConfigs.CBMID = null; michael@0: } michael@0: if (ICCUtilsHelper.isICCServiceAvailable("CBMIR")) { michael@0: this.readCBMIR(); michael@0: } else { michael@0: RIL.cellBroadcastConfigs.CBMIR = null; michael@0: } michael@0: RIL._mergeAllCellBroadcastConfigs(); michael@0: } michael@0: michael@0: // ICC_EF_UST has the same value with ICC_EF_SST. michael@0: this.context.ICCIOHelper.loadTransparentEF({ michael@0: fileId: ICC_EF_SST, michael@0: callback: callback.bind(this) michael@0: }); michael@0: }, michael@0: michael@0: /** michael@0: * Read (U)SIM MBDN. (Mailbox Dialling Number) michael@0: * michael@0: * @see TS 131.102, clause 4.2.60 michael@0: */ michael@0: readMBDN: function() { michael@0: function callback(options) { michael@0: let RIL = this.context.RIL; michael@0: let contact = michael@0: this.context.ICCPDUHelper.readAlphaIdDiallingNumber(options.recordSize); michael@0: if (!contact || michael@0: (RIL.iccInfoPrivate.mbdn !== undefined && michael@0: RIL.iccInfoPrivate.mbdn === contact.number)) { michael@0: return; michael@0: } michael@0: RIL.iccInfoPrivate.mbdn = contact.number; michael@0: if (DEBUG) { michael@0: this.context.debug("MBDN, alphaId=" + contact.alphaId + michael@0: " number=" + contact.number); michael@0: } michael@0: contact.rilMessageType = "iccmbdn"; michael@0: RIL.sendChromeMessage(contact); michael@0: } michael@0: michael@0: this.context.ICCIOHelper.loadLinearFixedEF({ michael@0: fileId: ICC_EF_MBDN, michael@0: callback: callback.bind(this) michael@0: }); michael@0: }, michael@0: michael@0: /** michael@0: * Read ICC MWIS. (Message Waiting Indication Status) michael@0: * michael@0: * @see TS 31.102, clause 4.2.63 for USIM and TS 51.011, clause 10.3.45 for SIM. michael@0: */ michael@0: readMWIS: function() { michael@0: function callback(options) { michael@0: let Buf = this.context.Buf; michael@0: let RIL = this.context.RIL; michael@0: michael@0: let strLen = Buf.readInt32(); michael@0: // Each octet is encoded into two chars. michael@0: let octetLen = strLen / 2; michael@0: let mwis = this.context.GsmPDUHelper.readHexOctetArray(octetLen); michael@0: Buf.readStringDelimiter(strLen); michael@0: if (!mwis) { michael@0: return; michael@0: } michael@0: RIL.iccInfoPrivate.mwis = mwis; //Keep raw MWIS for updateMWIS() michael@0: michael@0: let mwi = {}; michael@0: // b8 b7 B6 b5 b4 b3 b2 b1 4.2.63, TS 31.102 version 11.6.0 michael@0: // | | | | | | | |__ Voicemail michael@0: // | | | | | | |_____ Fax michael@0: // | | | | | |________ Electronic Mail michael@0: // | | | | |___________ Other michael@0: // | | | |______________ Videomail michael@0: // |__|__|_________________ RFU michael@0: mwi.active = ((mwis[0] & 0x01) != 0); michael@0: michael@0: if (mwi.active) { michael@0: // In TS 23.040 msgCount is in the range from 0 to 255. michael@0: // The value 255 shall be taken to mean 255 or greater. michael@0: // michael@0: // However, There is no definition about 0 when MWI is active. michael@0: // michael@0: // Normally, when mwi is active, the msgCount must be larger than 0. michael@0: // Refer to other reference phone, michael@0: // 0 is usually treated as UNKNOWN for storing 2nd level MWI status (DCS). michael@0: mwi.msgCount = (mwis[1] === 0) ? GECKO_VOICEMAIL_MESSAGE_COUNT_UNKNOWN michael@0: : mwis[1]; michael@0: } else { michael@0: mwi.msgCount = 0; michael@0: } michael@0: michael@0: RIL.sendChromeMessage({ rilMessageType: "iccmwis", michael@0: mwi: mwi }); michael@0: } michael@0: michael@0: this.context.ICCIOHelper.loadLinearFixedEF({ michael@0: fileId: ICC_EF_MWIS, michael@0: recordNumber: 1, // Get 1st Subscriber Profile. michael@0: callback: callback.bind(this) michael@0: }); michael@0: }, michael@0: michael@0: /** michael@0: * Update ICC MWIS. (Message Waiting Indication Status) michael@0: * michael@0: * @see TS 31.102, clause 4.2.63 for USIM and TS 51.011, clause 10.3.45 for SIM. michael@0: */ michael@0: updateMWIS: function(mwi) { michael@0: let RIL = this.context.RIL; michael@0: if (!RIL.iccInfoPrivate.mwis) { michael@0: return; michael@0: } michael@0: michael@0: function dataWriter(recordSize) { michael@0: let mwis = RIL.iccInfoPrivate.mwis; michael@0: michael@0: let msgCount = michael@0: (mwi.msgCount === GECKO_VOICEMAIL_MESSAGE_COUNT_UNKNOWN) ? 0 : mwi.msgCount; michael@0: michael@0: [mwis[0], mwis[1]] = (mwi.active) ? [(mwis[0] | 0x01), msgCount] michael@0: : [(mwis[0] & 0xFE), 0]; michael@0: michael@0: let strLen = recordSize * 2; michael@0: let Buf = this.context.Buf; michael@0: Buf.writeInt32(strLen); michael@0: michael@0: let GsmPDUHelper = this.context.GsmPDUHelper; michael@0: for (let i = 0; i < mwis.length; i++) { michael@0: GsmPDUHelper.writeHexOctet(mwis[i]); michael@0: } michael@0: michael@0: Buf.writeStringDelimiter(strLen); michael@0: } michael@0: michael@0: this.context.ICCIOHelper.updateLinearFixedEF({ michael@0: fileId: ICC_EF_MWIS, michael@0: recordNumber: 1, // Update 1st Subscriber Profile. michael@0: dataWriter: dataWriter.bind(this) michael@0: }); michael@0: }, michael@0: michael@0: /** michael@0: * Read the SPDI (Service Provider Display Information) from the (U)SIM. michael@0: * michael@0: * See TS 131.102 section 4.2.66 for USIM and TS 51.011 section 10.3.50 michael@0: * for SIM. michael@0: */ michael@0: readSPDI: function() { michael@0: function callback() { michael@0: let Buf = this.context.Buf; michael@0: let strLen = Buf.readInt32(); michael@0: let octetLen = strLen / 2; michael@0: let readLen = 0; michael@0: let endLoop = false; michael@0: michael@0: let RIL = this.context.RIL; michael@0: RIL.iccInfoPrivate.SPDI = null; michael@0: michael@0: let GsmPDUHelper = this.context.GsmPDUHelper; michael@0: while ((readLen < octetLen) && !endLoop) { michael@0: let tlvTag = GsmPDUHelper.readHexOctet(); michael@0: let tlvLen = GsmPDUHelper.readHexOctet(); michael@0: readLen += 2; // For tag and length fields. michael@0: switch (tlvTag) { michael@0: case SPDI_TAG_SPDI: michael@0: // The value part itself is a TLV. michael@0: continue; michael@0: case SPDI_TAG_PLMN_LIST: michael@0: // This PLMN list is what we want. michael@0: RIL.iccInfoPrivate.SPDI = this.readPLMNEntries(tlvLen / 3); michael@0: readLen += tlvLen; michael@0: endLoop = true; michael@0: break; michael@0: default: michael@0: // We don't care about its content if its tag is not SPDI nor michael@0: // PLMN_LIST. michael@0: endLoop = true; michael@0: break; michael@0: } michael@0: } michael@0: michael@0: // Consume unread octets. michael@0: Buf.seekIncoming((octetLen - readLen) * Buf.PDU_HEX_OCTET_SIZE); michael@0: Buf.readStringDelimiter(strLen); michael@0: michael@0: if (DEBUG) { michael@0: this.context.debug("SPDI: " + JSON.stringify(RIL.iccInfoPrivate.SPDI)); michael@0: } michael@0: let ICCUtilsHelper = this.context.ICCUtilsHelper; michael@0: if (ICCUtilsHelper.updateDisplayCondition()) { michael@0: ICCUtilsHelper.handleICCInfoChange(); michael@0: } michael@0: } michael@0: michael@0: // PLMN List is Servive 51 in USIM, EF_SPDI michael@0: this.context.ICCIOHelper.loadTransparentEF({ michael@0: fileId: ICC_EF_SPDI, michael@0: callback: callback.bind(this) michael@0: }); michael@0: }, michael@0: michael@0: _readCbmiHelper: function(which) { michael@0: let RIL = this.context.RIL; michael@0: michael@0: function callback() { michael@0: let Buf = this.context.Buf; michael@0: let strLength = Buf.readInt32(); michael@0: michael@0: // Each Message Identifier takes two octets and each octet is encoded michael@0: // into two chars. michael@0: let numIds = strLength / 4, list = null; michael@0: if (numIds) { michael@0: list = []; michael@0: let GsmPDUHelper = this.context.GsmPDUHelper; michael@0: for (let i = 0, id; i < numIds; i++) { michael@0: id = GsmPDUHelper.readHexOctet() << 8 | GsmPDUHelper.readHexOctet(); michael@0: // `Unused entries shall be set to 'FF FF'.` michael@0: if (id != 0xFFFF) { michael@0: list.push(id); michael@0: list.push(id + 1); michael@0: } michael@0: } michael@0: } michael@0: if (DEBUG) { michael@0: this.context.debug(which + ": " + JSON.stringify(list)); michael@0: } michael@0: michael@0: Buf.readStringDelimiter(strLength); michael@0: michael@0: RIL.cellBroadcastConfigs[which] = list; michael@0: RIL._mergeAllCellBroadcastConfigs(); michael@0: } michael@0: michael@0: function onerror() { michael@0: RIL.cellBroadcastConfigs[which] = null; michael@0: RIL._mergeAllCellBroadcastConfigs(); michael@0: } michael@0: michael@0: let fileId = GLOBAL["ICC_EF_" + which]; michael@0: this.context.ICCIOHelper.loadTransparentEF({ michael@0: fileId: fileId, michael@0: callback: callback.bind(this), michael@0: onerror: onerror.bind(this) michael@0: }); michael@0: }, michael@0: michael@0: /** michael@0: * Read EFcbmi (Cell Broadcast Message Identifier selection) michael@0: * michael@0: * @see 3GPP TS 31.102 v110.02.0 section 4.2.14 EFcbmi michael@0: * @see 3GPP TS 51.011 v5.0.0 section 10.3.13 EFcbmi michael@0: */ michael@0: readCBMI: function() { michael@0: this._readCbmiHelper("CBMI"); michael@0: }, michael@0: michael@0: /** michael@0: * Read EFcbmid (Cell Broadcast Message Identifier for Data Download) michael@0: * michael@0: * @see 3GPP TS 31.102 v110.02.0 section 4.2.20 EFcbmid michael@0: * @see 3GPP TS 51.011 v5.0.0 section 10.3.26 EFcbmid michael@0: */ michael@0: readCBMID: function() { michael@0: this._readCbmiHelper("CBMID"); michael@0: }, michael@0: michael@0: /** michael@0: * Read EFcbmir (Cell Broadcast Message Identifier Range selection) michael@0: * michael@0: * @see 3GPP TS 31.102 v110.02.0 section 4.2.22 EFcbmir michael@0: * @see 3GPP TS 51.011 v5.0.0 section 10.3.28 EFcbmir michael@0: */ michael@0: readCBMIR: function() { michael@0: let RIL = this.context.RIL; michael@0: michael@0: function callback() { michael@0: let Buf = this.context.Buf; michael@0: let strLength = Buf.readInt32(); michael@0: michael@0: // Each Message Identifier range takes four octets and each octet is michael@0: // encoded into two chars. michael@0: let numIds = strLength / 8, list = null; michael@0: if (numIds) { michael@0: list = []; michael@0: let GsmPDUHelper = this.context.GsmPDUHelper; michael@0: for (let i = 0, from, to; i < numIds; i++) { michael@0: // `Bytes one and two of each range identifier equal the lower value michael@0: // of a cell broadcast range, bytes three and four equal the upper michael@0: // value of a cell broadcast range.` michael@0: from = GsmPDUHelper.readHexOctet() << 8 | GsmPDUHelper.readHexOctet(); michael@0: to = GsmPDUHelper.readHexOctet() << 8 | GsmPDUHelper.readHexOctet(); michael@0: // `Unused entries shall be set to 'FF FF'.` michael@0: if ((from != 0xFFFF) && (to != 0xFFFF)) { michael@0: list.push(from); michael@0: list.push(to + 1); michael@0: } michael@0: } michael@0: } michael@0: if (DEBUG) { michael@0: this.context.debug("CBMIR: " + JSON.stringify(list)); michael@0: } michael@0: michael@0: Buf.readStringDelimiter(strLength); michael@0: michael@0: RIL.cellBroadcastConfigs.CBMIR = list; michael@0: RIL._mergeAllCellBroadcastConfigs(); michael@0: } michael@0: michael@0: function onerror() { michael@0: RIL.cellBroadcastConfigs.CBMIR = null; michael@0: RIL._mergeAllCellBroadcastConfigs(); michael@0: } michael@0: michael@0: this.context.ICCIOHelper.loadTransparentEF({ michael@0: fileId: ICC_EF_CBMIR, michael@0: callback: callback.bind(this), michael@0: onerror: onerror.bind(this) michael@0: }); michael@0: }, michael@0: michael@0: /** michael@0: * Read OPL (Operator PLMN List) from (U)SIM. michael@0: * michael@0: * See 3GPP TS 31.102 Sec. 4.2.59 for USIM michael@0: * 3GPP TS 51.011 Sec. 10.3.42 for SIM. michael@0: */ michael@0: readOPL: function() { michael@0: let ICCIOHelper = this.context.ICCIOHelper; michael@0: let opl = []; michael@0: function callback(options) { michael@0: let Buf = this.context.Buf; michael@0: let GsmPDUHelper = this.context.GsmPDUHelper; michael@0: michael@0: let strLen = Buf.readInt32(); michael@0: // The first 7 bytes are LAI (for UMTS) and the format of LAI is defined michael@0: // in 3GPP TS 23.003, Sec 4.1 michael@0: // +-------------+---------+ michael@0: // | Octet 1 - 3 | MCC/MNC | michael@0: // +-------------+---------+ michael@0: // | Octet 4 - 7 | LAC | michael@0: // +-------------+---------+ michael@0: let mccMnc = [GsmPDUHelper.readHexOctet(), michael@0: GsmPDUHelper.readHexOctet(), michael@0: GsmPDUHelper.readHexOctet()]; michael@0: if (mccMnc[0] != 0xFF || mccMnc[1] != 0xFF || mccMnc[2] != 0xFF) { michael@0: let oplElement = {}; michael@0: let semiOctets = []; michael@0: for (let i = 0; i < mccMnc.length; i++) { michael@0: semiOctets.push((mccMnc[i] & 0xf0) >> 4); michael@0: semiOctets.push(mccMnc[i] & 0x0f); michael@0: } michael@0: let reformat = [semiOctets[1], semiOctets[0], semiOctets[3], michael@0: semiOctets[5], semiOctets[4], semiOctets[2]]; michael@0: let buf = ""; michael@0: for (let i = 0; i < reformat.length; i++) { michael@0: if (reformat[i] != 0xF) { michael@0: buf += GsmPDUHelper.semiOctetToBcdChar(reformat[i]); michael@0: } michael@0: if (i === 2) { michael@0: // 0-2: MCC michael@0: oplElement.mcc = buf; michael@0: buf = ""; michael@0: } else if (i === 5) { michael@0: // 3-5: MNC michael@0: oplElement.mnc = buf; michael@0: } michael@0: } michael@0: // LAC/TAC michael@0: oplElement.lacTacStart = michael@0: (GsmPDUHelper.readHexOctet() << 8) | GsmPDUHelper.readHexOctet(); michael@0: oplElement.lacTacEnd = michael@0: (GsmPDUHelper.readHexOctet() << 8) | GsmPDUHelper.readHexOctet(); michael@0: // PLMN Network Name Record Identifier michael@0: oplElement.pnnRecordId = GsmPDUHelper.readHexOctet(); michael@0: if (DEBUG) { michael@0: this.context.debug("OPL: [" + (opl.length + 1) + "]: " + michael@0: JSON.stringify(oplElement)); michael@0: } michael@0: opl.push(oplElement); michael@0: } else { michael@0: Buf.seekIncoming(5 * Buf.PDU_HEX_OCTET_SIZE); michael@0: } michael@0: Buf.readStringDelimiter(strLen); michael@0: michael@0: if (options.p1 < options.totalRecords) { michael@0: ICCIOHelper.loadNextRecord(options); michael@0: } else { michael@0: this.context.RIL.iccInfoPrivate.OPL = opl; michael@0: } michael@0: } michael@0: michael@0: ICCIOHelper.loadLinearFixedEF({fileId: ICC_EF_OPL, michael@0: callback: callback.bind(this)}); michael@0: }, michael@0: michael@0: /** michael@0: * Read PNN (PLMN Network Name) from (U)SIM. michael@0: * michael@0: * See 3GPP TS 31.102 Sec. 4.2.58 for USIM michael@0: * 3GPP TS 51.011 Sec. 10.3.41 for SIM. michael@0: */ michael@0: readPNN: function() { michael@0: let ICCIOHelper = this.context.ICCIOHelper; michael@0: function callback(options) { michael@0: let pnnElement; michael@0: let Buf = this.context.Buf; michael@0: let strLen = Buf.readInt32(); michael@0: let octetLen = strLen / 2; michael@0: let readLen = 0; michael@0: michael@0: let GsmPDUHelper = this.context.GsmPDUHelper; michael@0: while (readLen < octetLen) { michael@0: let tlvTag = GsmPDUHelper.readHexOctet(); michael@0: michael@0: if (tlvTag == 0xFF) { michael@0: // Unused byte michael@0: readLen++; michael@0: Buf.seekIncoming((octetLen - readLen) * Buf.PDU_HEX_OCTET_SIZE); michael@0: break; michael@0: } michael@0: michael@0: // Needs this check to avoid initializing twice. michael@0: pnnElement = pnnElement || {}; michael@0: michael@0: let tlvLen = GsmPDUHelper.readHexOctet(); michael@0: michael@0: switch (tlvTag) { michael@0: case PNN_IEI_FULL_NETWORK_NAME: michael@0: pnnElement.fullName = GsmPDUHelper.readNetworkName(tlvLen); michael@0: break; michael@0: case PNN_IEI_SHORT_NETWORK_NAME: michael@0: pnnElement.shortName = GsmPDUHelper.readNetworkName(tlvLen); michael@0: break; michael@0: default: michael@0: Buf.seekIncoming(tlvLen * Buf.PDU_HEX_OCTET_SIZE); michael@0: break; michael@0: } michael@0: michael@0: readLen += (tlvLen + 2); // +2 for tlvTag and tlvLen michael@0: } michael@0: Buf.readStringDelimiter(strLen); michael@0: michael@0: if (pnnElement) { michael@0: pnn.push(pnnElement); michael@0: } michael@0: michael@0: // Will ignore remaining records when got the contents of a record are all 0xff. michael@0: if (pnnElement && options.p1 < options.totalRecords) { michael@0: ICCIOHelper.loadNextRecord(options); michael@0: } else { michael@0: if (DEBUG) { michael@0: for (let i = 0; i < pnn.length; i++) { michael@0: this.context.debug("PNN: [" + i + "]: " + JSON.stringify(pnn[i])); michael@0: } michael@0: } michael@0: this.context.RIL.iccInfoPrivate.PNN = pnn; michael@0: } michael@0: } michael@0: michael@0: let pnn = []; michael@0: ICCIOHelper.loadLinearFixedEF({fileId: ICC_EF_PNN, michael@0: callback: callback.bind(this)}); michael@0: }, michael@0: michael@0: /** michael@0: * Read the list of PLMN (Public Land Mobile Network) entries michael@0: * We cannot directly rely on readSwappedNibbleBcdToString(), michael@0: * since it will no correctly handle some corner-cases that are michael@0: * not a problem in our case (0xFF 0xFF 0xFF). michael@0: * michael@0: * @param length The number of PLMN records. michael@0: * @return An array of string corresponding to the PLMNs. michael@0: */ michael@0: readPLMNEntries: function(length) { michael@0: let plmnList = []; michael@0: // Each PLMN entry has 3 bytes. michael@0: if (DEBUG) { michael@0: this.context.debug("PLMN entries length = " + length); michael@0: } michael@0: let GsmPDUHelper = this.context.GsmPDUHelper; michael@0: let index = 0; michael@0: while (index < length) { michael@0: // Unused entries will be 0xFFFFFF, according to EF_SPDI michael@0: // specs (TS 131 102, section 4.2.66) michael@0: try { michael@0: let plmn = [GsmPDUHelper.readHexOctet(), michael@0: GsmPDUHelper.readHexOctet(), michael@0: GsmPDUHelper.readHexOctet()]; michael@0: if (DEBUG) { michael@0: this.context.debug("Reading PLMN entry: [" + index + "]: '" + plmn + "'"); michael@0: } michael@0: if (plmn[0] != 0xFF && michael@0: plmn[1] != 0xFF && michael@0: plmn[2] != 0xFF) { michael@0: let semiOctets = []; michael@0: for (let idx = 0; idx < plmn.length; idx++) { michael@0: semiOctets.push((plmn[idx] & 0xF0) >> 4); michael@0: semiOctets.push(plmn[idx] & 0x0F); michael@0: } michael@0: michael@0: // According to TS 24.301, 9.9.3.12, the semi octets is arranged michael@0: // in format: michael@0: // Byte 1: MCC[2] | MCC[1] michael@0: // Byte 2: MNC[3] | MCC[3] michael@0: // Byte 3: MNC[2] | MNC[1] michael@0: // Therefore, we need to rearrange them. michael@0: let reformat = [semiOctets[1], semiOctets[0], semiOctets[3], michael@0: semiOctets[5], semiOctets[4], semiOctets[2]]; michael@0: let buf = ""; michael@0: let plmnEntry = {}; michael@0: for (let i = 0; i < reformat.length; i++) { michael@0: if (reformat[i] != 0xF) { michael@0: buf += GsmPDUHelper.semiOctetToBcdChar(reformat[i]); michael@0: } michael@0: if (i === 2) { michael@0: // 0-2: MCC michael@0: plmnEntry.mcc = buf; michael@0: buf = ""; michael@0: } else if (i === 5) { michael@0: // 3-5: MNC michael@0: plmnEntry.mnc = buf; michael@0: } michael@0: } michael@0: if (DEBUG) { michael@0: this.context.debug("PLMN = " + plmnEntry.mcc + ", " + plmnEntry.mnc); michael@0: } michael@0: plmnList.push(plmnEntry); michael@0: } michael@0: } catch (e) { michael@0: if (DEBUG) { michael@0: this.context.debug("PLMN entry " + index + " is invalid."); michael@0: } michael@0: break; michael@0: } michael@0: index ++; michael@0: } michael@0: return plmnList; michael@0: }, michael@0: michael@0: /** michael@0: * Read the SMS from the ICC. michael@0: * michael@0: * @param recordNumber The number of the record shall be loaded. michael@0: * @param onsuccess Callback to be called when success. michael@0: * @param onerror Callback to be called when error. michael@0: */ michael@0: readSMS: function(recordNumber, onsuccess, onerror) { michael@0: function callback(options) { michael@0: let Buf = this.context.Buf; michael@0: let strLen = Buf.readInt32(); michael@0: michael@0: // TS 51.011, 10.5.3 EF_SMS michael@0: // b3 b2 b1 michael@0: // 0 0 1 message received by MS from network; message read michael@0: // 0 1 1 message received by MS from network; message to be read michael@0: // 1 1 1 MS originating message; message to be sent michael@0: // 1 0 1 MS originating message; message sent to the network: michael@0: let GsmPDUHelper = this.context.GsmPDUHelper; michael@0: let status = GsmPDUHelper.readHexOctet(); michael@0: michael@0: let message = GsmPDUHelper.readMessage(); michael@0: message.simStatus = status; michael@0: michael@0: // Consumes the remaining buffer michael@0: Buf.seekIncoming(Buf.getReadAvailable() - Buf.PDU_HEX_OCTET_SIZE); michael@0: michael@0: Buf.readStringDelimiter(strLen); michael@0: michael@0: if (message) { michael@0: onsuccess(message); michael@0: } else { michael@0: onerror("Failed to decode SMS on SIM #" + recordNumber); michael@0: } michael@0: } michael@0: michael@0: this.context.ICCIOHelper.loadLinearFixedEF({ michael@0: fileId: ICC_EF_SMS, michael@0: recordNumber: recordNumber, michael@0: callback: callback.bind(this), michael@0: onerror: onerror michael@0: }); michael@0: }, michael@0: }; michael@0: michael@0: function RuimRecordHelperObject(aContext) { michael@0: this.context = aContext; michael@0: } michael@0: RuimRecordHelperObject.prototype = { michael@0: context: null, michael@0: michael@0: fetchRuimRecords: function() { michael@0: this.getIMSI_M(); michael@0: this.readCST(); michael@0: this.readCDMAHome(); michael@0: this.context.RIL.getCdmaSubscription(); michael@0: }, michael@0: michael@0: /** michael@0: * Get IMSI_M from CSIM/RUIM. michael@0: * See 3GPP2 C.S0065 Sec. 5.2.2 michael@0: */ michael@0: getIMSI_M: function() { michael@0: function callback() { michael@0: let Buf = this.context.Buf; michael@0: let strLen = Buf.readInt32(); michael@0: let encodedImsi = this.context.GsmPDUHelper.readHexOctetArray(strLen / 2); michael@0: Buf.readStringDelimiter(strLen); michael@0: michael@0: if ((encodedImsi[CSIM_IMSI_M_PROGRAMMED_BYTE] & 0x80)) { // IMSI_M programmed michael@0: let RIL = this.context.RIL; michael@0: RIL.iccInfoPrivate.imsi = this.decodeIMSI(encodedImsi); michael@0: RIL.sendChromeMessage({rilMessageType: "iccimsi", michael@0: imsi: RIL.iccInfoPrivate.imsi}); michael@0: michael@0: let ICCUtilsHelper = this.context.ICCUtilsHelper; michael@0: let mccMnc = ICCUtilsHelper.parseMccMncFromImsi(RIL.iccInfoPrivate.imsi); michael@0: if (mccMnc) { michael@0: RIL.iccInfo.mcc = mccMnc.mcc; michael@0: RIL.iccInfo.mnc = mccMnc.mnc; michael@0: ICCUtilsHelper.handleICCInfoChange(); michael@0: } michael@0: } michael@0: } michael@0: michael@0: this.context.ICCIOHelper.loadTransparentEF({ michael@0: fileId: ICC_EF_CSIM_IMSI_M, michael@0: callback: callback.bind(this) michael@0: }); michael@0: }, michael@0: michael@0: /** michael@0: * Decode IMSI from IMSI_M michael@0: * See 3GPP2 C.S0005 Sec. 2.3.1 michael@0: * +---+---------+------------+---+--------+---------+---+---------+--------+ michael@0: * |RFU| MCC | programmed |RFU| MNC | MIN1 |RFU| MIN2 | CLASS | michael@0: * +---+---------+------------+---+--------+---------+---+---------+--------+ michael@0: * | 6 | 10 bits | 8 bits | 1 | 7 bits | 24 bits | 6 | 10 bits | 8 bits | michael@0: * +---+---------+------------+---+--------+---------+---+---------+--------+ michael@0: */ michael@0: decodeIMSI: function(encodedImsi) { michael@0: // MCC: 10 bits, 3 digits michael@0: let encodedMCC = ((encodedImsi[CSIM_IMSI_M_MCC_BYTE + 1] & 0x03) << 8) + michael@0: (encodedImsi[CSIM_IMSI_M_MCC_BYTE] & 0xff); michael@0: let mcc = this.decodeIMSIValue(encodedMCC, 3); michael@0: michael@0: // MNC: 7 bits, 2 digits michael@0: let encodedMNC = encodedImsi[CSIM_IMSI_M_MNC_BYTE] & 0x7f; michael@0: let mnc = this.decodeIMSIValue(encodedMNC, 2); michael@0: michael@0: // MIN2: 10 bits, 3 digits michael@0: let encodedMIN2 = ((encodedImsi[CSIM_IMSI_M_MIN2_BYTE + 1] & 0x03) << 8) + michael@0: (encodedImsi[CSIM_IMSI_M_MIN2_BYTE] & 0xff); michael@0: let min2 = this.decodeIMSIValue(encodedMIN2, 3); michael@0: michael@0: // MIN1: 10+4+10 bits, 3+1+3 digits michael@0: let encodedMIN1First3 = ((encodedImsi[CSIM_IMSI_M_MIN1_BYTE + 2] & 0xff) << 2) + michael@0: ((encodedImsi[CSIM_IMSI_M_MIN1_BYTE + 1] & 0xc0) >> 6); michael@0: let min1First3 = this.decodeIMSIValue(encodedMIN1First3, 3); michael@0: michael@0: let encodedFourthDigit = (encodedImsi[CSIM_IMSI_M_MIN1_BYTE + 1] & 0x3c) >> 2; michael@0: if (encodedFourthDigit > 9) { michael@0: encodedFourthDigit = 0; michael@0: } michael@0: let fourthDigit = encodedFourthDigit.toString(); michael@0: michael@0: let encodedMIN1Last3 = ((encodedImsi[CSIM_IMSI_M_MIN1_BYTE + 1] & 0x03) << 8) + michael@0: (encodedImsi[CSIM_IMSI_M_MIN1_BYTE] & 0xff); michael@0: let min1Last3 = this.decodeIMSIValue(encodedMIN1Last3, 3); michael@0: michael@0: return mcc + mnc + min2 + min1First3 + fourthDigit + min1Last3; michael@0: }, michael@0: michael@0: /** michael@0: * Decode IMSI Helper function michael@0: * See 3GPP2 C.S0005 section 2.3.1.1 michael@0: */ michael@0: decodeIMSIValue: function(encoded, length) { michael@0: let offset = length === 3 ? 111 : 11; michael@0: let value = encoded + offset; michael@0: michael@0: for (let base = 10, temp = value, i = 0; i < length; i++) { michael@0: if (temp % 10 === 0) { michael@0: value -= base; michael@0: } michael@0: temp = Math.floor(value / base); michael@0: base = base * 10; michael@0: } michael@0: michael@0: let s = value.toString(); michael@0: while (s.length < length) { michael@0: s = "0" + s; michael@0: } michael@0: michael@0: return s; michael@0: }, michael@0: michael@0: /** michael@0: * Read CDMAHOME for CSIM. michael@0: * See 3GPP2 C.S0023 Sec. 3.4.8. michael@0: */ michael@0: readCDMAHome: function() { michael@0: let ICCIOHelper = this.context.ICCIOHelper; michael@0: michael@0: function callback(options) { michael@0: let Buf = this.context.Buf; michael@0: let GsmPDUHelper = this.context.GsmPDUHelper; michael@0: michael@0: let strLen = Buf.readInt32(); michael@0: let tempOctet = GsmPDUHelper.readHexOctet(); michael@0: cdmaHomeSystemId.push(((GsmPDUHelper.readHexOctet() & 0x7f) << 8) | tempOctet); michael@0: tempOctet = GsmPDUHelper.readHexOctet(); michael@0: cdmaHomeNetworkId.push(((GsmPDUHelper.readHexOctet() & 0xff) << 8) | tempOctet); michael@0: michael@0: // Consuming the last octet: band class. michael@0: Buf.seekIncoming(Buf.PDU_HEX_OCTET_SIZE); michael@0: michael@0: Buf.readStringDelimiter(strLen); michael@0: if (options.p1 < options.totalRecords) { michael@0: ICCIOHelper.loadNextRecord(options); michael@0: } else { michael@0: if (DEBUG) { michael@0: this.context.debug("CDMAHome system id: " + michael@0: JSON.stringify(cdmaHomeSystemId)); michael@0: this.context.debug("CDMAHome network id: " + michael@0: JSON.stringify(cdmaHomeNetworkId)); michael@0: } michael@0: this.context.RIL.cdmaHome = { michael@0: systemId: cdmaHomeSystemId, michael@0: networkId: cdmaHomeNetworkId michael@0: }; michael@0: } michael@0: } michael@0: michael@0: let cdmaHomeSystemId = [], cdmaHomeNetworkId = []; michael@0: ICCIOHelper.loadLinearFixedEF({fileId: ICC_EF_CSIM_CDMAHOME, michael@0: callback: callback.bind(this)}); michael@0: }, michael@0: michael@0: /** michael@0: * Read CDMA Service Table. michael@0: * See 3GPP2 C.S0023 Sec. 3.4.18 michael@0: */ michael@0: readCST: function() { michael@0: function callback() { michael@0: let Buf = this.context.Buf; michael@0: let RIL = this.context.RIL; michael@0: michael@0: let strLen = Buf.readInt32(); michael@0: // Each octet is encoded into two chars. michael@0: RIL.iccInfoPrivate.cst = michael@0: this.context.GsmPDUHelper.readHexOctetArray(strLen / 2); michael@0: Buf.readStringDelimiter(strLen); michael@0: michael@0: if (DEBUG) { michael@0: let str = ""; michael@0: for (let i = 0; i < RIL.iccInfoPrivate.cst.length; i++) { michael@0: str += RIL.iccInfoPrivate.cst[i] + ", "; michael@0: } michael@0: this.context.debug("CST: " + str); michael@0: } michael@0: michael@0: if (this.context.ICCUtilsHelper.isICCServiceAvailable("SPN")) { michael@0: if (DEBUG) this.context.debug("SPN: SPN is available"); michael@0: this.readSPN(); michael@0: } michael@0: } michael@0: this.context.ICCIOHelper.loadTransparentEF({ michael@0: fileId: ICC_EF_CSIM_CST, michael@0: callback: callback.bind(this) michael@0: }); michael@0: }, michael@0: michael@0: readSPN: function() { michael@0: function callback() { michael@0: let Buf = this.context.Buf; michael@0: let strLen = Buf.readInt32(); michael@0: let octetLen = strLen / 2; michael@0: michael@0: let GsmPDUHelper = this.context.GsmPDUHelper; michael@0: let displayCondition = GsmPDUHelper.readHexOctet(); michael@0: let codingScheme = GsmPDUHelper.readHexOctet(); michael@0: // Skip one octet: language indicator. michael@0: Buf.seekIncoming(Buf.PDU_HEX_OCTET_SIZE); michael@0: let readLen = 3; michael@0: michael@0: // SPN String ends up with 0xff. michael@0: let userDataBuffer = []; michael@0: michael@0: while (readLen < octetLen) { michael@0: let octet = GsmPDUHelper.readHexOctet(); michael@0: readLen++; michael@0: if (octet == 0xff) { michael@0: break; michael@0: } michael@0: userDataBuffer.push(octet); michael@0: } michael@0: michael@0: this.context.BitBufferHelper.startRead(userDataBuffer); michael@0: michael@0: let CdmaPDUHelper = this.context.CdmaPDUHelper; michael@0: let msgLen; michael@0: switch (CdmaPDUHelper.getCdmaMsgEncoding(codingScheme)) { michael@0: case PDU_DCS_MSG_CODING_7BITS_ALPHABET: michael@0: msgLen = Math.floor(userDataBuffer.length * 8 / 7); michael@0: break; michael@0: case PDU_DCS_MSG_CODING_8BITS_ALPHABET: michael@0: msgLen = userDataBuffer.length; michael@0: break; michael@0: case PDU_DCS_MSG_CODING_16BITS_ALPHABET: michael@0: msgLen = Math.floor(userDataBuffer.length / 2); michael@0: break; michael@0: } michael@0: michael@0: let RIL = this.context.RIL; michael@0: RIL.iccInfo.spn = CdmaPDUHelper.decodeCdmaPDUMsg(codingScheme, null, msgLen); michael@0: if (DEBUG) { michael@0: this.context.debug("CDMA SPN: " + RIL.iccInfo.spn + michael@0: ", Display condition: " + displayCondition); michael@0: } michael@0: RIL.iccInfoPrivate.spnDisplayCondition = displayCondition; michael@0: Buf.seekIncoming((octetLen - readLen) * Buf.PDU_HEX_OCTET_SIZE); michael@0: Buf.readStringDelimiter(strLen); michael@0: } michael@0: michael@0: this.context.ICCIOHelper.loadTransparentEF({ michael@0: fileId: ICC_EF_CSIM_SPN, michael@0: callback: callback.bind(this) michael@0: }); michael@0: } michael@0: }; michael@0: michael@0: /** michael@0: * Helper functions for ICC utilities. michael@0: */ michael@0: function ICCUtilsHelperObject(aContext) { michael@0: this.context = aContext; michael@0: } michael@0: ICCUtilsHelperObject.prototype = { michael@0: context: null, michael@0: michael@0: /** michael@0: * Get network names by using EF_OPL and EF_PNN michael@0: * michael@0: * @See 3GPP TS 31.102 sec. 4.2.58 and sec. 4.2.59 for USIM, michael@0: * 3GPP TS 51.011 sec. 10.3.41 and sec. 10.3.42 for SIM. michael@0: * michael@0: * @param mcc The mobile country code of the network. michael@0: * @param mnc The mobile network code of the network. michael@0: * @param lac The location area code of the network. michael@0: */ michael@0: getNetworkNameFromICC: function(mcc, mnc, lac) { michael@0: let RIL = this.context.RIL; michael@0: let iccInfoPriv = RIL.iccInfoPrivate; michael@0: let iccInfo = RIL.iccInfo; michael@0: let pnnEntry; michael@0: michael@0: if (!mcc || !mnc || !lac) { michael@0: return null; michael@0: } michael@0: michael@0: // We won't get network name if there is no PNN file. michael@0: if (!iccInfoPriv.PNN) { michael@0: return null; michael@0: } michael@0: michael@0: if (!iccInfoPriv.OPL) { michael@0: // When OPL is not present: michael@0: // According to 3GPP TS 31.102 Sec. 4.2.58 and 3GPP TS 51.011 Sec. 10.3.41, michael@0: // If EF_OPL is not present, the first record in this EF is used for the michael@0: // default network name when registered to the HPLMN. michael@0: // If we haven't get pnnEntry assigned, we should try to assign default michael@0: // value to it. michael@0: if (mcc == iccInfo.mcc && mnc == iccInfo.mnc) { michael@0: pnnEntry = iccInfoPriv.PNN[0]; michael@0: } michael@0: } else { michael@0: // According to 3GPP TS 31.102 Sec. 4.2.59 and 3GPP TS 51.011 Sec. 10.3.42, michael@0: // the ME shall use this EF_OPL in association with the EF_PNN in place michael@0: // of any network name stored within the ME's internal list and any network michael@0: // name received when registered to the PLMN. michael@0: let length = iccInfoPriv.OPL ? iccInfoPriv.OPL.length : 0; michael@0: for (let i = 0; i < length; i++) { michael@0: let opl = iccInfoPriv.OPL[i]; michael@0: // Try to match the MCC/MNC. michael@0: if (mcc != opl.mcc || mnc != opl.mnc) { michael@0: continue; michael@0: } michael@0: // Try to match the location area code. If current local area code is michael@0: // covered by lac range that specified in the OPL entry, use the PNN michael@0: // that specified in the OPL entry. michael@0: if ((opl.lacTacStart === 0x0 && opl.lacTacEnd == 0xFFFE) || michael@0: (opl.lacTacStart <= lac && opl.lacTacEnd >= lac)) { michael@0: if (opl.pnnRecordId === 0) { michael@0: // See 3GPP TS 31.102 Sec. 4.2.59 and 3GPP TS 51.011 Sec. 10.3.42, michael@0: // A value of '00' indicates that the name is to be taken from other michael@0: // sources. michael@0: return null; michael@0: } michael@0: pnnEntry = iccInfoPriv.PNN[opl.pnnRecordId - 1]; michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: michael@0: if (!pnnEntry) { michael@0: return null; michael@0: } michael@0: michael@0: // Return a new object to avoid global variable, PNN, be modified by accident. michael@0: return { fullName: pnnEntry.fullName || "", michael@0: shortName: pnnEntry.shortName || "" }; michael@0: }, michael@0: michael@0: /** michael@0: * This will compute the spnDisplay field of the network. michael@0: * See TS 22.101 Annex A and TS 51.011 10.3.11 for details. michael@0: * michael@0: * @return True if some of iccInfo is changed in by this function. michael@0: */ michael@0: updateDisplayCondition: function() { michael@0: let RIL = this.context.RIL; michael@0: michael@0: // If EFspn isn't existed in SIM or it haven't been read yet, we should michael@0: // just set isDisplayNetworkNameRequired = true and michael@0: // isDisplaySpnRequired = false michael@0: let iccInfo = RIL.iccInfo; michael@0: let iccInfoPriv = RIL.iccInfoPrivate; michael@0: let displayCondition = iccInfoPriv.spnDisplayCondition; michael@0: let origIsDisplayNetworkNameRequired = iccInfo.isDisplayNetworkNameRequired; michael@0: let origIsDisplaySPNRequired = iccInfo.isDisplaySpnRequired; michael@0: michael@0: if (displayCondition === undefined) { michael@0: iccInfo.isDisplayNetworkNameRequired = true; michael@0: iccInfo.isDisplaySpnRequired = false; michael@0: } else if (RIL._isCdma) { michael@0: // CDMA family display rule. michael@0: let cdmaHome = RIL.cdmaHome; michael@0: let cell = RIL.voiceRegistrationState.cell; michael@0: let sid = cell && cell.cdmaSystemId; michael@0: let nid = cell && cell.cdmaNetworkId; michael@0: michael@0: iccInfo.isDisplayNetworkNameRequired = false; michael@0: michael@0: // If display condition is 0x0, we don't even need to check network id michael@0: // or system id. michael@0: if (displayCondition === 0x0) { michael@0: iccInfo.isDisplaySpnRequired = false; michael@0: } else { michael@0: // CDMA SPN Display condition dosen't specify whenever network name is michael@0: // reqired. michael@0: if (!cdmaHome || michael@0: !cdmaHome.systemId || michael@0: cdmaHome.systemId.length === 0 || michael@0: cdmaHome.systemId.length != cdmaHome.networkId.length || michael@0: !sid || !nid) { michael@0: // CDMA Home haven't been ready, or we haven't got the system id and michael@0: // network id of the network we register to, assuming we are in home michael@0: // network. michael@0: iccInfo.isDisplaySpnRequired = true; michael@0: } else { michael@0: // Determine if we are registered in the home service area. michael@0: // System ID and Network ID are described in 3GPP2 C.S0005 Sec. 2.6.5.2. michael@0: let inHomeArea = false; michael@0: for (let i = 0; i < cdmaHome.systemId.length; i++) { michael@0: let homeSid = cdmaHome.systemId[i], michael@0: homeNid = cdmaHome.networkId[i]; michael@0: if (homeSid === 0 || homeNid === 0 // Reserved system id/network id michael@0: || homeSid != sid) { michael@0: continue; michael@0: } michael@0: // According to 3GPP2 C.S0005 Sec. 2.6.5.2, NID number 65535 means michael@0: // all networks in the system should be considered as home. michael@0: if (homeNid == 65535 || homeNid == nid) { michael@0: inHomeArea = true; michael@0: break; michael@0: } michael@0: } michael@0: iccInfo.isDisplaySpnRequired = inHomeArea; michael@0: } michael@0: } michael@0: } else { michael@0: // GSM family display rule. michael@0: let operatorMnc = RIL.operator.mnc; michael@0: let operatorMcc = RIL.operator.mcc; michael@0: michael@0: // First detect if we are on HPLMN or one of the PLMN michael@0: // specified by the SIM card. michael@0: let isOnMatchingPlmn = false; michael@0: michael@0: // If the current network is the one defined as mcc/mnc michael@0: // in SIM card, it's okay. michael@0: if (iccInfo.mcc == operatorMcc && iccInfo.mnc == operatorMnc) { michael@0: isOnMatchingPlmn = true; michael@0: } michael@0: michael@0: // Test to see if operator's mcc/mnc match mcc/mnc of PLMN. michael@0: if (!isOnMatchingPlmn && iccInfoPriv.SPDI) { michael@0: let iccSpdi = iccInfoPriv.SPDI; // PLMN list michael@0: for (let plmn in iccSpdi) { michael@0: let plmnMcc = iccSpdi[plmn].mcc; michael@0: let plmnMnc = iccSpdi[plmn].mnc; michael@0: isOnMatchingPlmn = (plmnMcc == operatorMcc) && (plmnMnc == operatorMnc); michael@0: if (isOnMatchingPlmn) { michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: michael@0: if (isOnMatchingPlmn) { michael@0: // The first bit of display condition tells us if we should display michael@0: // registered PLMN. michael@0: if (DEBUG) { michael@0: this.context.debug("PLMN is HPLMN or PLMN " + "is in PLMN list"); michael@0: } michael@0: michael@0: // TS 31.102 Sec. 4.2.66 and TS 51.011 Sec. 10.3.50 michael@0: // EF_SPDI contains a list of PLMNs in which the Service Provider Name michael@0: // shall be displayed. michael@0: iccInfo.isDisplaySpnRequired = true; michael@0: iccInfo.isDisplayNetworkNameRequired = (displayCondition & 0x01) !== 0; michael@0: } else { michael@0: // The second bit of display condition tells us if we should display michael@0: // registered PLMN. michael@0: if (DEBUG) { michael@0: this.context.debug("PLMN isn't HPLMN and PLMN isn't in PLMN list"); michael@0: } michael@0: michael@0: // We didn't found the requirement of displaying network name if michael@0: // current PLMN isn't HPLMN nor one of PLMN in SPDI. So we keep michael@0: // isDisplayNetworkNameRequired false. michael@0: iccInfo.isDisplayNetworkNameRequired = false; michael@0: iccInfo.isDisplaySpnRequired = (displayCondition & 0x02) === 0; michael@0: } michael@0: } michael@0: michael@0: if (DEBUG) { michael@0: this.context.debug("isDisplayNetworkNameRequired = " + michael@0: iccInfo.isDisplayNetworkNameRequired); michael@0: this.context.debug("isDisplaySpnRequired = " + iccInfo.isDisplaySpnRequired); michael@0: } michael@0: michael@0: return ((origIsDisplayNetworkNameRequired !== iccInfo.isDisplayNetworkNameRequired) || michael@0: (origIsDisplaySPNRequired !== iccInfo.isDisplaySpnRequired)); michael@0: }, michael@0: michael@0: decodeSimTlvs: function(tlvsLen) { michael@0: let GsmPDUHelper = this.context.GsmPDUHelper; michael@0: michael@0: let index = 0; michael@0: let tlvs = []; michael@0: while (index < tlvsLen) { michael@0: let simTlv = { michael@0: tag : GsmPDUHelper.readHexOctet(), michael@0: length : GsmPDUHelper.readHexOctet(), michael@0: }; michael@0: simTlv.value = GsmPDUHelper.readHexOctetArray(simTlv.length); michael@0: tlvs.push(simTlv); michael@0: index += simTlv.length + 2; // The length of 'tag' and 'length' field. michael@0: } michael@0: return tlvs; michael@0: }, michael@0: michael@0: /** michael@0: * Parse those TLVs and convert it to an object. michael@0: */ michael@0: parsePbrTlvs: function(pbrTlvs) { michael@0: let pbr = {}; michael@0: for (let i = 0; i < pbrTlvs.length; i++) { michael@0: let pbrTlv = pbrTlvs[i]; michael@0: let anrIndex = 0; michael@0: for (let j = 0; j < pbrTlv.value.length; j++) { michael@0: let tlv = pbrTlv.value[j]; michael@0: let tagName = USIM_TAG_NAME[tlv.tag]; michael@0: michael@0: // ANR could have multiple files. We save it as anr0, anr1,...etc. michael@0: if (tlv.tag == ICC_USIM_EFANR_TAG) { michael@0: tagName += anrIndex; michael@0: anrIndex++; michael@0: } michael@0: pbr[tagName] = tlv; michael@0: pbr[tagName].fileType = pbrTlv.tag; michael@0: pbr[tagName].fileId = (tlv.value[0] << 8) | tlv.value[1]; michael@0: pbr[tagName].sfi = tlv.value[2]; michael@0: michael@0: // For Type 2, the order of files is in the same order in IAP. michael@0: if (pbrTlv.tag == ICC_USIM_TYPE2_TAG) { michael@0: pbr[tagName].indexInIAP = j; michael@0: } michael@0: } michael@0: } michael@0: michael@0: return pbr; michael@0: }, michael@0: michael@0: /** michael@0: * Update the ICC information to RadioInterfaceLayer. michael@0: */ michael@0: handleICCInfoChange: function() { michael@0: let RIL = this.context.RIL; michael@0: RIL.iccInfo.rilMessageType = "iccinfochange"; michael@0: RIL.sendChromeMessage(RIL.iccInfo); michael@0: }, michael@0: michael@0: /** michael@0: * Get whether specificed (U)SIM service is available. michael@0: * michael@0: * @param geckoService michael@0: * Service name like "ADN", "BDN", etc. michael@0: * michael@0: * @return true if the service is enabled, false otherwise. michael@0: */ michael@0: isICCServiceAvailable: function(geckoService) { michael@0: let RIL = this.context.RIL; michael@0: let serviceTable = RIL._isCdma ? RIL.iccInfoPrivate.cst: michael@0: RIL.iccInfoPrivate.sst; michael@0: let index, bitmask; michael@0: if (RIL.appType == CARD_APPTYPE_SIM || RIL.appType == CARD_APPTYPE_RUIM) { michael@0: /** michael@0: * Service id is valid in 1..N, and 2 bits are used to code each service. michael@0: * michael@0: * +----+-- --+----+----+ michael@0: * | b8 | ... | b2 | b1 | michael@0: * +----+-- --+----+----+ michael@0: * michael@0: * b1 = 0, service not allocated. michael@0: * 1, service allocated. michael@0: * b2 = 0, service not activatd. michael@0: * 1, service allocated. michael@0: * michael@0: * @see 3GPP TS 51.011 10.3.7. michael@0: */ michael@0: let simService; michael@0: if (RIL.appType == CARD_APPTYPE_SIM) { michael@0: simService = GECKO_ICC_SERVICES.sim[geckoService]; michael@0: } else { michael@0: simService = GECKO_ICC_SERVICES.ruim[geckoService]; michael@0: } michael@0: if (!simService) { michael@0: return false; michael@0: } michael@0: simService -= 1; michael@0: index = Math.floor(simService / 4); michael@0: bitmask = 2 << ((simService % 4) << 1); michael@0: } else if (RIL.appType == CARD_APPTYPE_USIM) { michael@0: /** michael@0: * Service id is valid in 1..N, and 1 bit is used to code each service. michael@0: * michael@0: * +----+-- --+----+----+ michael@0: * | b8 | ... | b2 | b1 | michael@0: * +----+-- --+----+----+ michael@0: * michael@0: * b1 = 0, service not avaiable. michael@0: * 1, service available. michael@0: * b2 = 0, service not avaiable. michael@0: * 1, service available. michael@0: * michael@0: * @see 3GPP TS 31.102 4.2.8. michael@0: */ michael@0: let usimService = GECKO_ICC_SERVICES.usim[geckoService]; michael@0: if (!usimService) { michael@0: return false; michael@0: } michael@0: usimService -= 1; michael@0: index = Math.floor(usimService / 8); michael@0: bitmask = 1 << ((usimService % 8) << 0); michael@0: } michael@0: michael@0: return (serviceTable !== null) && michael@0: (index < serviceTable.length) && michael@0: ((serviceTable[index] & bitmask) !== 0); michael@0: }, michael@0: michael@0: /** michael@0: * Check if the string is of GSM default 7-bit coded alphabets with bit 8 michael@0: * set to 0. michael@0: * michael@0: * @param str String to be checked. michael@0: */ michael@0: isGsm8BitAlphabet: function(str) { michael@0: if (!str) { michael@0: return false; michael@0: } michael@0: michael@0: const langTable = PDU_NL_LOCKING_SHIFT_TABLES[PDU_NL_IDENTIFIER_DEFAULT]; michael@0: const langShiftTable = PDU_NL_SINGLE_SHIFT_TABLES[PDU_NL_IDENTIFIER_DEFAULT]; michael@0: michael@0: for (let i = 0; i < str.length; i++) { michael@0: let c = str.charAt(i); michael@0: let octet = langTable.indexOf(c); michael@0: if (octet == -1) { michael@0: octet = langShiftTable.indexOf(c); michael@0: if (octet == -1) { michael@0: return false; michael@0: } michael@0: } michael@0: } michael@0: michael@0: return true; michael@0: }, michael@0: michael@0: /** michael@0: * Parse MCC/MNC from IMSI. If there is no available value for the length of michael@0: * mnc, it will use the data in MCC table to parse. michael@0: * michael@0: * @param imsi michael@0: * The imsi of icc. michael@0: * @param mncLength [optional] michael@0: * The length of mnc. michael@0: * michael@0: * @return An object contains the parsing result of mcc and mnc. michael@0: * Or null if any error occurred. michael@0: */ michael@0: parseMccMncFromImsi: function(imsi, mncLength) { michael@0: if (!imsi) { michael@0: return null; michael@0: } michael@0: michael@0: // MCC is the first 3 digits of IMSI. michael@0: let mcc = imsi.substr(0,3); michael@0: if (!mncLength) { michael@0: // Check the MCC table to decide the length of MNC. michael@0: let index = MCC_TABLE_FOR_MNC_LENGTH_IS_3.indexOf(mcc); michael@0: mncLength = (index !== -1) ? 3 : 2; michael@0: } michael@0: let mnc = imsi.substr(3, mncLength); michael@0: if (DEBUG) { michael@0: this.context.debug("IMSI: " + imsi + " MCC: " + mcc + " MNC: " + mnc); michael@0: } michael@0: michael@0: return { mcc: mcc, mnc: mnc}; michael@0: }, michael@0: }; michael@0: michael@0: /** michael@0: * Helper for ICC Contacts. michael@0: */ michael@0: function ICCContactHelperObject(aContext) { michael@0: this.context = aContext; michael@0: } michael@0: ICCContactHelperObject.prototype = { michael@0: context: null, michael@0: michael@0: /** michael@0: * Helper function to check DF_PHONEBOOK. michael@0: */ michael@0: hasDfPhoneBook: function(appType) { michael@0: switch (appType) { michael@0: case CARD_APPTYPE_SIM: michael@0: return false; michael@0: case CARD_APPTYPE_USIM: michael@0: return true; michael@0: case CARD_APPTYPE_RUIM: michael@0: let ICCUtilsHelper = this.context.ICCUtilsHelper; michael@0: return ICCUtilsHelper.isICCServiceAvailable("ENHANCED_PHONEBOOK"); michael@0: default: michael@0: return false; michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Helper function to read ICC contacts. michael@0: * michael@0: * @param appType One of CARD_APPTYPE_*. michael@0: * @param contactType "adn" or "fdn". michael@0: * @param onsuccess Callback to be called when success. michael@0: * @param onerror Callback to be called when error. michael@0: */ michael@0: readICCContacts: function(appType, contactType, onsuccess, onerror) { michael@0: let ICCRecordHelper = this.context.ICCRecordHelper; michael@0: michael@0: switch (contactType) { michael@0: case "adn": michael@0: if (!this.hasDfPhoneBook(appType)) { michael@0: ICCRecordHelper.readADNLike(ICC_EF_ADN, onsuccess, onerror); michael@0: } else { michael@0: this.readUSimContacts(onsuccess, onerror); michael@0: } michael@0: break; michael@0: case "fdn": michael@0: ICCRecordHelper.readADNLike(ICC_EF_FDN, onsuccess, onerror); michael@0: break; michael@0: default: michael@0: if (DEBUG) { michael@0: this.context.debug("Unsupported contactType :" + contactType); michael@0: } michael@0: onerror(CONTACT_ERR_CONTACT_TYPE_NOT_SUPPORTED); michael@0: break; michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Helper function to find free contact record. michael@0: * michael@0: * @param appType One of CARD_APPTYPE_*. michael@0: * @param contactType "adn" or "fdn". michael@0: * @param onsuccess Callback to be called when success. michael@0: * @param onerror Callback to be called when error. michael@0: */ michael@0: findFreeICCContact: function(appType, contactType, onsuccess, onerror) { michael@0: let ICCRecordHelper = this.context.ICCRecordHelper; michael@0: michael@0: switch (contactType) { michael@0: case "adn": michael@0: if (!this.hasDfPhoneBook(appType)) { michael@0: ICCRecordHelper.findFreeRecordId(ICC_EF_ADN, onsuccess.bind(null, 0), onerror); michael@0: } else { michael@0: let gotPbrCb = function gotPbrCb(pbrs) { michael@0: this.findUSimFreeADNRecordId(pbrs, onsuccess, onerror); michael@0: }.bind(this); michael@0: michael@0: ICCRecordHelper.readPBR(gotPbrCb, onerror); michael@0: } michael@0: break; michael@0: case "fdn": michael@0: ICCRecordHelper.findFreeRecordId(ICC_EF_FDN, onsuccess.bind(null, 0), onerror); michael@0: break; michael@0: default: michael@0: if (DEBUG) { michael@0: this.context.debug("Unsupported contactType :" + contactType); michael@0: } michael@0: onerror(CONTACT_ERR_CONTACT_TYPE_NOT_SUPPORTED); michael@0: break; michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Find free ADN record id in USIM. michael@0: * michael@0: * @param pbrs All Phonebook Reference Files read. michael@0: * @param onsuccess Callback to be called when success. michael@0: * @param onerror Callback to be called when error. michael@0: */ michael@0: findUSimFreeADNRecordId: function(pbrs, onsuccess, onerror) { michael@0: let ICCRecordHelper = this.context.ICCRecordHelper; michael@0: michael@0: (function findFreeRecordId(pbrIndex) { michael@0: if (pbrIndex >= pbrs.length) { michael@0: if (DEBUG) { michael@0: this.context.debug(CONTACT_ERR_NO_FREE_RECORD_FOUND); michael@0: } michael@0: onerror(CONTACT_ERR_NO_FREE_RECORD_FOUND); michael@0: return; michael@0: } michael@0: michael@0: let pbr = pbrs[pbrIndex]; michael@0: ICCRecordHelper.findFreeRecordId( michael@0: pbr.adn.fileId, michael@0: onsuccess.bind(this, pbrIndex), michael@0: findFreeRecordId.bind(null, pbrIndex + 1)); michael@0: })(0); michael@0: }, michael@0: michael@0: /** michael@0: * Helper function to add a new ICC contact. michael@0: * michael@0: * @param appType One of CARD_APPTYPE_*. michael@0: * @param contactType "adn" or "fdn". michael@0: * @param contact The contact will be added. michael@0: * @param pin2 PIN2 is required for FDN. michael@0: * @param onsuccess Callback to be called when success. michael@0: * @param onerror Callback to be called when error. michael@0: */ michael@0: addICCContact: function(appType, contactType, contact, pin2, onsuccess, onerror) { michael@0: let foundFreeCb = (function foundFreeCb(pbrIndex, recordId) { michael@0: contact.pbrIndex = pbrIndex; michael@0: contact.recordId = recordId; michael@0: this.updateICCContact(appType, contactType, contact, pin2, onsuccess, onerror); michael@0: }).bind(this); michael@0: michael@0: // Find free record first. michael@0: this.findFreeICCContact(appType, contactType, foundFreeCb, onerror); michael@0: }, michael@0: michael@0: /** michael@0: * Helper function to update ICC contact. michael@0: * michael@0: * @param appType One of CARD_APPTYPE_*. michael@0: * @param contactType "adn" or "fdn". michael@0: * @param contact The contact will be updated. michael@0: * @param pin2 PIN2 is required for FDN. michael@0: * @param onsuccess Callback to be called when success. michael@0: * @param onerror Callback to be called when error. michael@0: */ michael@0: updateICCContact: function(appType, contactType, contact, pin2, onsuccess, onerror) { michael@0: let ICCRecordHelper = this.context.ICCRecordHelper; michael@0: michael@0: switch (contactType) { michael@0: case "adn": michael@0: if (!this.hasDfPhoneBook(appType)) { michael@0: ICCRecordHelper.updateADNLike(ICC_EF_ADN, contact, null, onsuccess, onerror); michael@0: } else { michael@0: this.updateUSimContact(contact, onsuccess, onerror); michael@0: } michael@0: break; michael@0: case "fdn": michael@0: if (!pin2) { michael@0: onerror(GECKO_ERROR_SIM_PIN2); michael@0: return; michael@0: } michael@0: ICCRecordHelper.updateADNLike(ICC_EF_FDN, contact, pin2, onsuccess, onerror); michael@0: break; michael@0: default: michael@0: if (DEBUG) { michael@0: this.context.debug("Unsupported contactType :" + contactType); michael@0: } michael@0: onerror(CONTACT_ERR_CONTACT_TYPE_NOT_SUPPORTED); michael@0: break; michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Read contacts from USIM. michael@0: * michael@0: * @param onsuccess Callback to be called when success. michael@0: * @param onerror Callback to be called when error. michael@0: */ michael@0: readUSimContacts: function(onsuccess, onerror) { michael@0: let gotPbrCb = function gotPbrCb(pbrs) { michael@0: this.readAllPhonebookSets(pbrs, onsuccess, onerror); michael@0: }.bind(this); michael@0: michael@0: this.context.ICCRecordHelper.readPBR(gotPbrCb, onerror); michael@0: }, michael@0: michael@0: /** michael@0: * Read all Phonebook sets. michael@0: * michael@0: * @param pbrs All Phonebook Reference Files read. michael@0: * @param onsuccess Callback to be called when success. michael@0: * @param onerror Callback to be called when error. michael@0: */ michael@0: readAllPhonebookSets: function(pbrs, onsuccess, onerror) { michael@0: let allContacts = [], pbrIndex = 0; michael@0: let readPhonebook = function readPhonebook(contacts) { michael@0: if (contacts) { michael@0: allContacts = allContacts.concat(contacts); michael@0: } michael@0: michael@0: let cLen = contacts ? contacts.length : 0; michael@0: for (let i = 0; i < cLen; i++) { michael@0: contacts[i].pbrIndex = pbrIndex; michael@0: } michael@0: michael@0: pbrIndex++; michael@0: if (pbrIndex >= pbrs.length) { michael@0: if (onsuccess) { michael@0: onsuccess(allContacts); michael@0: } michael@0: return; michael@0: } michael@0: michael@0: this.readPhonebookSet(pbrs[pbrIndex], readPhonebook, onerror); michael@0: }.bind(this); michael@0: michael@0: this.readPhonebookSet(pbrs[pbrIndex], readPhonebook, onerror); michael@0: }, michael@0: michael@0: /** michael@0: * Read from Phonebook Reference File. michael@0: * michael@0: * @param pbr Phonebook Reference File to be read. michael@0: * @param onsuccess Callback to be called when success. michael@0: * @param onerror Callback to be called when error. michael@0: */ michael@0: readPhonebookSet: function(pbr, onsuccess, onerror) { michael@0: let gotAdnCb = function gotAdnCb(contacts) { michael@0: this.readSupportedPBRFields(pbr, contacts, onsuccess, onerror); michael@0: }.bind(this); michael@0: michael@0: this.context.ICCRecordHelper.readADNLike(pbr.adn.fileId, gotAdnCb, onerror); michael@0: }, michael@0: michael@0: /** michael@0: * Read supported Phonebook fields. michael@0: * michael@0: * @param pbr Phone Book Reference file. michael@0: * @param contacts Contacts stored on ICC. michael@0: * @param onsuccess Callback to be called when success. michael@0: * @param onerror Callback to be called when error. michael@0: */ michael@0: readSupportedPBRFields: function(pbr, contacts, onsuccess, onerror) { michael@0: let fieldIndex = 0; michael@0: (function readField() { michael@0: let field = USIM_PBR_FIELDS[fieldIndex]; michael@0: fieldIndex += 1; michael@0: if (!field) { michael@0: if (onsuccess) { michael@0: onsuccess(contacts); michael@0: } michael@0: return; michael@0: } michael@0: michael@0: this.readPhonebookField(pbr, contacts, field, readField.bind(this), onerror); michael@0: }).call(this); michael@0: }, michael@0: michael@0: /** michael@0: * Read Phonebook field. michael@0: * michael@0: * @param pbr The phonebook reference file. michael@0: * @param contacts Contacts stored on ICC. michael@0: * @param field Phonebook field to be retrieved. michael@0: * @param onsuccess Callback to be called when success. michael@0: * @param onerror Callback to be called when error. michael@0: */ michael@0: readPhonebookField: function(pbr, contacts, field, onsuccess, onerror) { michael@0: if (!pbr[field]) { michael@0: if (onsuccess) { michael@0: onsuccess(contacts); michael@0: } michael@0: return; michael@0: } michael@0: michael@0: (function doReadContactField(n) { michael@0: if (n >= contacts.length) { michael@0: // All contact's fields are read. michael@0: if (onsuccess) { michael@0: onsuccess(contacts); michael@0: } michael@0: return; michael@0: } michael@0: michael@0: // get n-th contact's field. michael@0: this.readContactField(pbr, contacts[n], field, michael@0: doReadContactField.bind(this, n + 1), onerror); michael@0: }).call(this, 0); michael@0: }, michael@0: michael@0: /** michael@0: * Read contact's field from USIM. michael@0: * michael@0: * @param pbr The phonebook reference file. michael@0: * @param contact The contact needs to get field. michael@0: * @param field Phonebook field to be retrieved. michael@0: * @param onsuccess Callback to be called when success. michael@0: * @param onerror Callback to be called when error. michael@0: */ michael@0: readContactField: function(pbr, contact, field, onsuccess, onerror) { michael@0: let gotRecordIdCb = function gotRecordIdCb(recordId) { michael@0: if (recordId == 0xff) { michael@0: if (onsuccess) { michael@0: onsuccess(); michael@0: } michael@0: return; michael@0: } michael@0: michael@0: let fileId = pbr[field].fileId; michael@0: let fileType = pbr[field].fileType; michael@0: let gotFieldCb = function gotFieldCb(value) { michael@0: if (value) { michael@0: // Move anr0 anr1,.. into anr[]. michael@0: if (field.startsWith(USIM_PBR_ANR)) { michael@0: if (!contact[USIM_PBR_ANR]) { michael@0: contact[USIM_PBR_ANR] = []; michael@0: } michael@0: contact[USIM_PBR_ANR].push(value); michael@0: } else { michael@0: contact[field] = value; michael@0: } michael@0: } michael@0: michael@0: if (onsuccess) { michael@0: onsuccess(); michael@0: } michael@0: }.bind(this); michael@0: michael@0: let ICCRecordHelper = this.context.ICCRecordHelper; michael@0: // Detect EF to be read, for anr, it could have anr0, anr1,... michael@0: let ef = field.startsWith(USIM_PBR_ANR) ? USIM_PBR_ANR : field; michael@0: switch (ef) { michael@0: case USIM_PBR_EMAIL: michael@0: ICCRecordHelper.readEmail(fileId, fileType, recordId, gotFieldCb, onerror); michael@0: break; michael@0: case USIM_PBR_ANR: michael@0: ICCRecordHelper.readANR(fileId, fileType, recordId, gotFieldCb, onerror); michael@0: break; michael@0: default: michael@0: if (DEBUG) { michael@0: this.context.debug("Unsupported field :" + field); michael@0: } michael@0: onerror(CONTACT_ERR_FIELD_NOT_SUPPORTED); michael@0: break; michael@0: } michael@0: }.bind(this); michael@0: michael@0: this.getContactFieldRecordId(pbr, contact, field, gotRecordIdCb, onerror); michael@0: }, michael@0: michael@0: /** michael@0: * Get the recordId. michael@0: * michael@0: * If the fileType of field is ICC_USIM_TYPE1_TAG, use corresponding ADN recordId. michael@0: * otherwise get the recordId from IAP. michael@0: * michael@0: * @see TS 131.102, clause 4.4.2.2 michael@0: * michael@0: * @param pbr The phonebook reference file. michael@0: * @param contact The contact will be updated. michael@0: * @param onsuccess Callback to be called when success. michael@0: * @param onerror Callback to be called when error. michael@0: */ michael@0: getContactFieldRecordId: function(pbr, contact, field, onsuccess, onerror) { michael@0: if (pbr[field].fileType == ICC_USIM_TYPE1_TAG) { michael@0: // If the file type is ICC_USIM_TYPE1_TAG, use corresponding ADN recordId. michael@0: if (onsuccess) { michael@0: onsuccess(contact.recordId); michael@0: } michael@0: } else if (pbr[field].fileType == ICC_USIM_TYPE2_TAG) { michael@0: // If the file type is ICC_USIM_TYPE2_TAG, the recordId shall be got from IAP. michael@0: let gotIapCb = function gotIapCb(iap) { michael@0: let indexInIAP = pbr[field].indexInIAP; michael@0: let recordId = iap[indexInIAP]; michael@0: michael@0: if (onsuccess) { michael@0: onsuccess(recordId); michael@0: } michael@0: }.bind(this); michael@0: michael@0: this.context.ICCRecordHelper.readIAP(pbr.iap.fileId, contact.recordId, michael@0: gotIapCb, onerror); michael@0: } else { michael@0: if (DEBUG) { michael@0: this.context.debug("USIM PBR files in Type 3 format are not supported."); michael@0: } michael@0: onerror(CONTACT_ERR_REQUEST_NOT_SUPPORTED); michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Update USIM contact. michael@0: * michael@0: * @param contact The contact will be updated. michael@0: * @param onsuccess Callback to be called when success. michael@0: * @param onerror Callback to be called when error. michael@0: */ michael@0: updateUSimContact: function(contact, onsuccess, onerror) { michael@0: let gotPbrCb = function gotPbrCb(pbrs) { michael@0: let pbr = pbrs[contact.pbrIndex]; michael@0: if (!pbr) { michael@0: if (DEBUG) { michael@0: this.context.debug(CONTACT_ERR_CANNOT_ACCESS_PHONEBOOK); michael@0: } michael@0: onerror(CONTACT_ERR_CANNOT_ACCESS_PHONEBOOK); michael@0: return; michael@0: } michael@0: this.updatePhonebookSet(pbr, contact, onsuccess, onerror); michael@0: }.bind(this); michael@0: michael@0: this.context.ICCRecordHelper.readPBR(gotPbrCb, onerror); michael@0: }, michael@0: michael@0: /** michael@0: * Update fields in Phonebook Reference File. michael@0: * michael@0: * @param pbr Phonebook Reference File to be read. michael@0: * @param onsuccess Callback to be called when success. michael@0: * @param onerror Callback to be called when error. michael@0: */ michael@0: updatePhonebookSet: function(pbr, contact, onsuccess, onerror) { michael@0: let updateAdnCb = function() { michael@0: this.updateSupportedPBRFields(pbr, contact, onsuccess, onerror); michael@0: }.bind(this); michael@0: michael@0: this.context.ICCRecordHelper.updateADNLike(pbr.adn.fileId, contact, null, michael@0: updateAdnCb, onerror); michael@0: }, michael@0: michael@0: /** michael@0: * Update supported Phonebook fields. michael@0: * michael@0: * @param pbr Phone Book Reference file. michael@0: * @param contact Contact to be updated. michael@0: * @param onsuccess Callback to be called when success. michael@0: * @param onerror Callback to be called when error. michael@0: */ michael@0: updateSupportedPBRFields: function(pbr, contact, onsuccess, onerror) { michael@0: let fieldIndex = 0; michael@0: (function updateField() { michael@0: let field = USIM_PBR_FIELDS[fieldIndex]; michael@0: fieldIndex += 1; michael@0: if (!field) { michael@0: if (onsuccess) { michael@0: onsuccess(); michael@0: } michael@0: return; michael@0: } michael@0: michael@0: // Check if PBR has this field. michael@0: if (!pbr[field]) { michael@0: updateField.call(this); michael@0: return; michael@0: } michael@0: michael@0: this.updateContactField(pbr, contact, field, updateField.bind(this), onerror); michael@0: }).call(this); michael@0: }, michael@0: michael@0: /** michael@0: * Update contact's field from USIM. michael@0: * michael@0: * @param pbr The phonebook reference file. michael@0: * @param contact The contact needs to be updated. michael@0: * @param field Phonebook field to be updated. michael@0: * @param onsuccess Callback to be called when success. michael@0: * @param onerror Callback to be called when error. michael@0: */ michael@0: updateContactField: function(pbr, contact, field, onsuccess, onerror) { michael@0: if (pbr[field].fileType === ICC_USIM_TYPE1_TAG) { michael@0: this.updateContactFieldType1(pbr, contact, field, onsuccess, onerror); michael@0: } else if (pbr[field].fileType === ICC_USIM_TYPE2_TAG) { michael@0: this.updateContactFieldType2(pbr, contact, field, onsuccess, onerror); michael@0: } else { michael@0: if (DEBUG) { michael@0: this.context.debug("USIM PBR files in Type 3 format are not supported."); michael@0: } michael@0: onerror(CONTACT_ERR_REQUEST_NOT_SUPPORTED); michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Update Type 1 USIM contact fields. michael@0: * michael@0: * @param pbr The phonebook reference file. michael@0: * @param contact The contact needs to be updated. michael@0: * @param field Phonebook field to be updated. michael@0: * @param onsuccess Callback to be called when success. michael@0: * @param onerror Callback to be called when error. michael@0: */ michael@0: updateContactFieldType1: function(pbr, contact, field, onsuccess, onerror) { michael@0: let ICCRecordHelper = this.context.ICCRecordHelper; michael@0: michael@0: if (field === USIM_PBR_EMAIL) { michael@0: ICCRecordHelper.updateEmail(pbr, contact.recordId, contact.email, null, onsuccess, onerror); michael@0: } else if (field === USIM_PBR_ANR0) { michael@0: let anr = Array.isArray(contact.anr) ? contact.anr[0] : null; michael@0: ICCRecordHelper.updateANR(pbr, contact.recordId, anr, null, onsuccess, onerror); michael@0: } else { michael@0: if (DEBUG) { michael@0: this.context.debug("Unsupported field :" + field); michael@0: } michael@0: onerror(CONTACT_ERR_FIELD_NOT_SUPPORTED); michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Update Type 2 USIM contact fields. michael@0: * michael@0: * @param pbr The phonebook reference file. michael@0: * @param contact The contact needs to be updated. michael@0: * @param field Phonebook field to be updated. michael@0: * @param onsuccess Callback to be called when success. michael@0: * @param onerror Callback to be called when error. michael@0: */ michael@0: updateContactFieldType2: function(pbr, contact, field, onsuccess, onerror) { michael@0: let ICCRecordHelper = this.context.ICCRecordHelper; michael@0: michael@0: // Case 1 : EF_IAP[adnRecordId] doesn't have a value(0xff) michael@0: // Find a free recordId for EF_field michael@0: // Update field with that free recordId. michael@0: // Update IAP. michael@0: // michael@0: // Case 2: EF_IAP[adnRecordId] has a value michael@0: // update EF_field[iap[field.indexInIAP]] michael@0: michael@0: let gotIapCb = function gotIapCb(iap) { michael@0: let recordId = iap[pbr[field].indexInIAP]; michael@0: if (recordId === 0xff) { michael@0: // If the value in IAP[index] is 0xff, which means the contact stored on michael@0: // the SIM doesn't have the additional attribute (email or anr). michael@0: // So if the contact to be updated doesn't have the attribute either, michael@0: // we don't have to update it. michael@0: if ((field === USIM_PBR_EMAIL && contact.email) || michael@0: (field === USIM_PBR_ANR0 && michael@0: (Array.isArray(contact.anr) && contact.anr[0]))) { michael@0: // Case 1. michael@0: this.addContactFieldType2(pbr, contact, field, onsuccess, onerror); michael@0: } else { michael@0: if (onsuccess) { michael@0: onsuccess(); michael@0: } michael@0: } michael@0: return; michael@0: } michael@0: michael@0: // Case 2. michael@0: if (field === USIM_PBR_EMAIL) { michael@0: ICCRecordHelper.updateEmail(pbr, recordId, contact.email, contact.recordId, onsuccess, onerror); michael@0: } else if (field === USIM_PBR_ANR0) { michael@0: let anr = Array.isArray(contact.anr) ? contact.anr[0] : null; michael@0: ICCRecordHelper.updateANR(pbr, recordId, anr, contact.recordId, onsuccess, onerror); michael@0: } else { michael@0: if (DEBUG) { michael@0: this.context.debug("Unsupported field :" + field); michael@0: } michael@0: onerror(CONTACT_ERR_FIELD_NOT_SUPPORTED); michael@0: } michael@0: michael@0: }.bind(this); michael@0: michael@0: ICCRecordHelper.readIAP(pbr.iap.fileId, contact.recordId, gotIapCb, onerror); michael@0: }, michael@0: michael@0: /** michael@0: * Add Type 2 USIM contact fields. michael@0: * michael@0: * @param pbr The phonebook reference file. michael@0: * @param contact The contact needs to be updated. michael@0: * @param field Phonebook field to be updated. michael@0: * @param onsuccess Callback to be called when success. michael@0: * @param onerror Callback to be called when error. michael@0: */ michael@0: addContactFieldType2: function(pbr, contact, field, onsuccess, onerror) { michael@0: let ICCRecordHelper = this.context.ICCRecordHelper; michael@0: michael@0: let successCb = function successCb(recordId) { michael@0: let updateCb = function updateCb() { michael@0: this.updateContactFieldIndexInIAP(pbr, contact.recordId, field, recordId, onsuccess, onerror); michael@0: }.bind(this); michael@0: michael@0: if (field === USIM_PBR_EMAIL) { michael@0: ICCRecordHelper.updateEmail(pbr, recordId, contact.email, contact.recordId, updateCb, onerror); michael@0: } else if (field === USIM_PBR_ANR0) { michael@0: ICCRecordHelper.updateANR(pbr, recordId, contact.anr[0], contact.recordId, updateCb, onerror); michael@0: } michael@0: }.bind(this); michael@0: michael@0: let errorCb = function errorCb(errorMsg) { michael@0: if (DEBUG) { michael@0: this.context.debug(errorMsg + " USIM field " + field); michael@0: } michael@0: onerror(errorMsg); michael@0: }.bind(this); michael@0: michael@0: ICCRecordHelper.findFreeRecordId(pbr[field].fileId, successCb, errorCb); michael@0: }, michael@0: michael@0: /** michael@0: * Update IAP value. michael@0: * michael@0: * @param pbr The phonebook reference file. michael@0: * @param recordNumber The record identifier of EF_IAP. michael@0: * @param field Phonebook field. michael@0: * @param value The value of 'field' in IAP. michael@0: * @param onsuccess Callback to be called when success. michael@0: * @param onerror Callback to be called when error. michael@0: * michael@0: */ michael@0: updateContactFieldIndexInIAP: function(pbr, recordNumber, field, value, onsuccess, onerror) { michael@0: let ICCRecordHelper = this.context.ICCRecordHelper; michael@0: michael@0: let gotIAPCb = function gotIAPCb(iap) { michael@0: iap[pbr[field].indexInIAP] = value; michael@0: ICCRecordHelper.updateIAP(pbr.iap.fileId, recordNumber, iap, onsuccess, onerror); michael@0: }.bind(this); michael@0: ICCRecordHelper.readIAP(pbr.iap.fileId, recordNumber, gotIAPCb, onerror); michael@0: }, michael@0: }; michael@0: michael@0: /** michael@0: * Global stuff. michael@0: */ michael@0: michael@0: function Context(aClientId) { michael@0: this.clientId = aClientId; michael@0: michael@0: this.Buf = new BufObject(this); michael@0: this.Buf.init(); michael@0: michael@0: this.RIL = new RilObject(this); michael@0: this.RIL.initRILState(); michael@0: } michael@0: Context.prototype = { michael@0: clientId: null, michael@0: Buf: null, michael@0: RIL: null, michael@0: michael@0: debug: function(aMessage) { michael@0: GLOBAL.debug("[" + this.clientId + "] " + aMessage); michael@0: } michael@0: }; michael@0: michael@0: (function() { michael@0: let lazySymbols = [ michael@0: "BerTlvHelper", "BitBufferHelper", "CdmaPDUHelper", michael@0: "ComprehensionTlvHelper", "GsmPDUHelper", "ICCContactHelper", michael@0: "ICCFileHelper", "ICCIOHelper", "ICCPDUHelper", "ICCRecordHelper", michael@0: "ICCUtilsHelper", "RuimRecordHelper", "SimRecordHelper", michael@0: "StkCommandParamsFactory", "StkProactiveCmdHelper", michael@0: ]; michael@0: michael@0: for (let i = 0; i < lazySymbols.length; i++) { michael@0: let symbol = lazySymbols[i]; michael@0: Object.defineProperty(Context.prototype, symbol, { michael@0: get: function() { michael@0: let real = new GLOBAL[symbol + "Object"](this); michael@0: Object.defineProperty(this, symbol, { michael@0: value: real, michael@0: enumerable: true michael@0: }); michael@0: return real; michael@0: }, michael@0: configurable: true, michael@0: enumerable: true michael@0: }); michael@0: } michael@0: })(); michael@0: michael@0: let ContextPool = { michael@0: _contexts: [], michael@0: michael@0: handleRilMessage: function(aClientId, aUint8Array) { michael@0: let context = this._contexts[aClientId]; michael@0: context.Buf.processIncoming(aUint8Array); michael@0: }, michael@0: michael@0: handleChromeMessage: function(aMessage) { michael@0: let clientId = aMessage.rilMessageClientId; michael@0: if (clientId != null) { michael@0: let context = this._contexts[clientId]; michael@0: context.RIL.handleChromeMessage(aMessage); michael@0: return; michael@0: } michael@0: michael@0: if (DEBUG) debug("Received global chrome message " + JSON.stringify(aMessage)); michael@0: let method = this[aMessage.rilMessageType]; michael@0: if (typeof method != "function") { michael@0: if (DEBUG) { michael@0: debug("Don't know what to do"); michael@0: } michael@0: return; michael@0: } michael@0: method.call(this, aMessage); michael@0: }, michael@0: michael@0: setInitialOptions: function(aOptions) { michael@0: DEBUG = DEBUG_WORKER || aOptions.debug; michael@0: RIL_EMERGENCY_NUMBERS = aOptions.rilEmergencyNumbers; michael@0: RIL_CELLBROADCAST_DISABLED = aOptions.cellBroadcastDisabled; michael@0: RIL_CLIR_MODE = aOptions.clirMode; michael@0: michael@0: let quirks = aOptions.quirks; michael@0: RILQUIRKS_CALLSTATE_EXTRA_UINT32 = quirks.callstateExtraUint32; michael@0: RILQUIRKS_V5_LEGACY = quirks.v5Legacy; michael@0: RILQUIRKS_REQUEST_USE_DIAL_EMERGENCY_CALL = quirks.requestUseDialEmergencyCall; michael@0: RILQUIRKS_SIM_APP_STATE_EXTRA_FIELDS = quirks.simAppStateExtraFields; michael@0: RILQUIRKS_EXTRA_UINT32_2ND_CALL = quirks.extraUint2ndCall; michael@0: RILQUIRKS_HAVE_QUERY_ICC_LOCK_RETRY_COUNT = quirks.haveQueryIccLockRetryCount; michael@0: RILQUIRKS_SEND_STK_PROFILE_DOWNLOAD = quirks.sendStkProfileDownload; michael@0: RILQUIRKS_DATA_REGISTRATION_ON_DEMAND = quirks.dataRegistrationOnDemand; michael@0: }, michael@0: michael@0: registerClient: function(aOptions) { michael@0: let clientId = aOptions.clientId; michael@0: this._contexts[clientId] = new Context(clientId); michael@0: }, michael@0: }; michael@0: michael@0: function onRILMessage(aClientId, aUint8Array) { michael@0: ContextPool.handleRilMessage(aClientId, aUint8Array); michael@0: } michael@0: michael@0: onmessage = function onmessage(event) { michael@0: ContextPool.handleChromeMessage(event.data); michael@0: }; michael@0: michael@0: onerror = function onerror(event) { michael@0: if (DEBUG) debug("onerror" + event.message + "\n"); michael@0: };