dom/system/gonk/ril_worker.js

Thu, 22 Jan 2015 13:21:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 22 Jan 2015 13:21:57 +0100
branch
TOR_BUG_9701
changeset 15
b8a032363ba2
permissions
-rw-r--r--

Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6

     1 /* Copyright 2012 Mozilla Foundation and Mozilla contributors
     2  *
     3  * Licensed under the Apache License, Version 2.0 (the "License");
     4  * you may not use this file except in compliance with the License.
     5  * You may obtain a copy of the License at
     6  *
     7  *     http://www.apache.org/licenses/LICENSE-2.0
     8  *
     9  * Unless required by applicable law or agreed to in writing, software
    10  * distributed under the License is distributed on an "AS IS" BASIS,
    11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  * See the License for the specific language governing permissions and
    13  * limitations under the License.
    14  */
    16 /**
    17  * This file implements the RIL worker thread. It communicates with
    18  * the main thread to provide a high-level API to the phone's RIL
    19  * stack, and with the RIL IPC thread to communicate with the RIL
    20  * device itself. These communication channels use message events as
    21  * known from Web Workers:
    22  *
    23  * - postMessage()/"message" events for main thread communication
    24  *
    25  * - postRILMessage()/"RILMessageEvent" events for RIL IPC thread
    26  *   communication.
    27  *
    28  * The two main objects in this file represent individual parts of this
    29  * communication chain:
    30  *
    31  * - RILMessageEvent -> Buf -> RIL -> postMessage() -> nsIRadioInterfaceLayer
    32  * - nsIRadioInterfaceLayer -> postMessage() -> RIL -> Buf -> postRILMessage()
    33  *
    34  * Note: The code below is purposely lean on abstractions to be as lean in
    35  * terms of object allocations. As a result, it may look more like C than
    36  * JavaScript, and that's intended.
    37  */
    39 "use strict";
    41 importScripts("ril_consts.js");
    42 importScripts("resource://gre/modules/workers/require.js");
    44 // set to true in ril_consts.js to see debug messages
    45 let DEBUG = DEBUG_WORKER;
    46 let GLOBAL = this;
    48 if (!this.debug) {
    49   // Debugging stub that goes nowhere.
    50   this.debug = function debug(message) {
    51     dump("RIL Worker: " + message + "\n");
    52   };
    53 }
    55 let RIL_CELLBROADCAST_DISABLED;
    56 let RIL_CLIR_MODE;
    57 let RIL_EMERGENCY_NUMBERS;
    58 const DEFAULT_EMERGENCY_NUMBERS = ["112", "911"];
    60 // Timeout value for emergency callback mode.
    61 const EMERGENCY_CB_MODE_TIMEOUT_MS = 300000;  // 5 mins = 300000 ms.
    63 const ICC_MAX_LINEAR_FIXED_RECORDS = 0xfe;
    65 // MMI match groups
    66 const MMI_MATCH_GROUP_FULL_MMI = 1;
    67 const MMI_MATCH_GROUP_MMI_PROCEDURE = 2;
    68 const MMI_MATCH_GROUP_SERVICE_CODE = 3;
    69 const MMI_MATCH_GROUP_SIA = 5;
    70 const MMI_MATCH_GROUP_SIB = 7;
    71 const MMI_MATCH_GROUP_SIC = 9;
    72 const MMI_MATCH_GROUP_PWD_CONFIRM = 11;
    73 const MMI_MATCH_GROUP_DIALING_NUMBER = 12;
    75 const MMI_MAX_LENGTH_SHORT_CODE = 2;
    77 const MMI_END_OF_USSD = "#";
    79 // Should match the value we set in dom/telephony/TelephonyCommon.h
    80 const OUTGOING_PLACEHOLDER_CALL_INDEX = 0xffffffff;
    82 let RILQUIRKS_CALLSTATE_EXTRA_UINT32;
    83 // This may change at runtime since in RIL v6 and later, we get the version
    84 // number via the UNSOLICITED_RIL_CONNECTED parcel.
    85 let RILQUIRKS_V5_LEGACY;
    86 let RILQUIRKS_REQUEST_USE_DIAL_EMERGENCY_CALL;
    87 let RILQUIRKS_SIM_APP_STATE_EXTRA_FIELDS;
    88 // Needed for call-waiting on Peak device
    89 let RILQUIRKS_EXTRA_UINT32_2ND_CALL;
    90 // On the emulator we support querying the number of lock retries
    91 let RILQUIRKS_HAVE_QUERY_ICC_LOCK_RETRY_COUNT;
    93 // Ril quirk to Send STK Profile Download
    94 let RILQUIRKS_SEND_STK_PROFILE_DOWNLOAD;
    96 // Ril quirk to attach data registration on demand.
    97 let RILQUIRKS_DATA_REGISTRATION_ON_DEMAND;
    99 function BufObject(aContext) {
   100   this.context = aContext;
   101 }
   102 BufObject.prototype = {
   103   context: null,
   105   mToken: 0,
   106   mTokenRequestMap: null,
   108   init: function() {
   109     this._init();
   111     // This gets incremented each time we send out a parcel.
   112     this.mToken = 1;
   114     // Maps tokens we send out with requests to the request type, so that
   115     // when we get a response parcel back, we know what request it was for.
   116     this.mTokenRequestMap = new Map();
   117   },
   119   /**
   120    * Process one parcel.
   121    */
   122   processParcel: function() {
   123     let response_type = this.readInt32();
   125     let request_type, options;
   126     if (response_type == RESPONSE_TYPE_SOLICITED) {
   127       let token = this.readInt32();
   128       let error = this.readInt32();
   130       options = this.mTokenRequestMap.get(token);
   131       if (!options) {
   132         if (DEBUG) {
   133           this.context.debug("Suspicious uninvited request found: " +
   134                              token + ". Ignored!");
   135         }
   136         return;
   137       }
   139       this.mTokenRequestMap.delete(token);
   140       request_type = options.rilRequestType;
   142       options.rilRequestError = error;
   143       if (DEBUG) {
   144         this.context.debug("Solicited response for request type " + request_type +
   145                            ", token " + token + ", error " + error);
   146       }
   147     } else if (response_type == RESPONSE_TYPE_UNSOLICITED) {
   148       request_type = this.readInt32();
   149       if (DEBUG) {
   150         this.context.debug("Unsolicited response for request type " + request_type);
   151       }
   152     } else {
   153       if (DEBUG) {
   154         this.context.debug("Unknown response type: " + response_type);
   155       }
   156       return;
   157     }
   159     this.context.RIL.handleParcel(request_type, this.readAvailable, options);
   160   },
   162   /**
   163    * Start a new outgoing parcel.
   164    *
   165    * @param type
   166    *        Integer specifying the request type.
   167    * @param options [optional]
   168    *        Object containing information about the request, e.g. the
   169    *        original main thread message object that led to the RIL request.
   170    */
   171   newParcel: function(type, options) {
   172     if (DEBUG) this.context.debug("New outgoing parcel of type " + type);
   174     // We're going to leave room for the parcel size at the beginning.
   175     this.outgoingIndex = this.PARCEL_SIZE_SIZE;
   176     this.writeInt32(type);
   177     this.writeInt32(this.mToken);
   179     if (!options) {
   180       options = {};
   181     }
   182     options.rilRequestType = type;
   183     options.rilRequestError = null;
   184     this.mTokenRequestMap.set(this.mToken, options);
   185     this.mToken++;
   186     return this.mToken;
   187   },
   189   simpleRequest: function(type, options) {
   190     this.newParcel(type, options);
   191     this.sendParcel();
   192   },
   194   onSendParcel: function(parcel) {
   195     postRILMessage(this.context.clientId, parcel);
   196   }
   197 };
   199 (function() {
   200   let base = require("resource://gre/modules/workers/worker_buf.js").Buf;
   201   for (let p in base) {
   202     BufObject.prototype[p] = base[p];
   203   }
   204 })();
   206 /**
   207  * The RIL state machine.
   208  *
   209  * This object communicates with rild via parcels and with the main thread
   210  * via post messages. It maintains state about the radio, ICC, calls, etc.
   211  * and acts upon state changes accordingly.
   212  */
   213 function RilObject(aContext) {
   214   this.context = aContext;
   216   this.currentCalls = {};
   217   this.currentConference = {state: null, participants: {}};
   218   this.currentDataCalls = {};
   219   this._pendingSentSmsMap = {};
   220   this.pendingNetworkType = {};
   221   this._receivedSmsCbPagesMap = {};
   223   // Init properties that are only initialized once.
   224   this.v5Legacy = RILQUIRKS_V5_LEGACY;
   225   this.cellBroadcastDisabled = RIL_CELLBROADCAST_DISABLED;
   226   this.clirMode = RIL_CLIR_MODE;
   227 }
   228 RilObject.prototype = {
   229   context: null,
   231   v5Legacy: null,
   233   /**
   234    * Valid calls.
   235    */
   236   currentCalls: null,
   238   /**
   239    * Existing conference call and its participants.
   240    */
   241   currentConference: null,
   243   /**
   244    * Existing data calls.
   245    */
   246   currentDataCalls: null,
   248   /**
   249    * Outgoing messages waiting for SMS-STATUS-REPORT.
   250    */
   251   _pendingSentSmsMap: null,
   253   /**
   254    * Index of the RIL_PREFERRED_NETWORK_TYPE_TO_GECKO. Its value should be
   255    * preserved over rild reset.
   256    */
   257   preferredNetworkType: null,
   259   /**
   260    * Marker object.
   261    */
   262   pendingNetworkType: null,
   264   /**
   265    * Global Cell Broadcast switch.
   266    */
   267   cellBroadcastDisabled: false,
   269   /**
   270    * Global CLIR mode settings.
   271    */
   272   clirMode: CLIR_DEFAULT,
   274   /**
   275    * Parsed Cell Broadcast search lists.
   276    * cellBroadcastConfigs.MMI should be preserved over rild reset.
   277    */
   278   cellBroadcastConfigs: null,
   279   mergedCellBroadcastConfig: null,
   281   _receivedSmsCbPagesMap: null,
   283   initRILState: function() {
   284     /**
   285      * One of the RADIO_STATE_* constants.
   286      */
   287     this.radioState = GECKO_RADIOSTATE_UNAVAILABLE;
   289     /**
   290      * True if we are on a CDMA phone.
   291      */
   292     this._isCdma = false;
   294     /**
   295      * True if we are in emergency callback mode.
   296      */
   297     this._isInEmergencyCbMode = false;
   299     /**
   300      * Set when radio is ready but radio tech is unknown. That is, we are
   301      * waiting for REQUEST_VOICE_RADIO_TECH
   302      */
   303     this._waitingRadioTech = false;
   305     /**
   306      * ICC status. Keeps a reference of the data response to the
   307      * getICCStatus request.
   308      */
   309     this.iccStatus = null;
   311     /**
   312      * Card state
   313      */
   314     this.cardState = GECKO_CARDSTATE_UNINITIALIZED;
   316     /**
   317      * Strings
   318      */
   319     this.IMEI = null;
   320     this.IMEISV = null;
   321     this.ESN = null;
   322     this.MEID = null;
   323     this.SMSC = null;
   325     /**
   326      * ICC information that is not exposed to Gaia.
   327      */
   328     this.iccInfoPrivate = {};
   330     /**
   331      * ICC information, such as MSISDN, MCC, MNC, SPN...etc.
   332      */
   333     this.iccInfo = {};
   335     /**
   336      * CDMA specific information. ex. CDMA Network ID, CDMA System ID... etc.
   337      */
   338     this.cdmaHome = null;
   340     /**
   341      * Application identification for apps in ICC.
   342      */
   343     this.aid = null;
   345     /**
   346      * Application type for apps in ICC.
   347      */
   348     this.appType = null;
   350     this.networkSelectionMode = null;
   352     this.voiceRegistrationState = {};
   353     this.dataRegistrationState = {};
   355     /**
   356      * List of strings identifying the network operator.
   357      */
   358     this.operator = null;
   360     /**
   361      * String containing the baseband version.
   362      */
   363     this.basebandVersion = null;
   365     // Clean up this.currentCalls: rild might have restarted.
   366     for each (let currentCall in this.currentCalls) {
   367       delete this.currentCalls[currentCall.callIndex];
   368       this._handleDisconnectedCall(currentCall);
   369     }
   371     // Deactivate this.currentDataCalls: rild might have restarted.
   372     for each (let datacall in this.currentDataCalls) {
   373       this.deactivateDataCall(datacall);
   374     }
   376     // Don't clean up this._pendingSentSmsMap
   377     // because on rild restart: we may continue with the pending segments.
   379     /**
   380      * Whether or not the multiple requests in requestNetworkInfo() are currently
   381      * being processed
   382      */
   383     this._processingNetworkInfo = false;
   385     /**
   386      * Multiple requestNetworkInfo() in a row before finishing the first
   387      * request, hence we need to fire requestNetworkInfo() again after
   388      * gathering all necessary stuffs. This is to make sure that ril_worker
   389      * gets precise network information.
   390      */
   391     this._needRepollNetworkInfo = false;
   393     /**
   394      * Pending messages to be send in batch from requestNetworkInfo()
   395      */
   396     this._pendingNetworkInfo = {rilMessageType: "networkinfochanged"};
   398     /**
   399      * USSD session flag.
   400      * Only one USSD session may exist at a time, and the session is assumed
   401      * to exist until:
   402      *    a) There's a call to cancelUSSD()
   403      *    b) The implementation sends a UNSOLICITED_ON_USSD with a type code
   404      *       of "0" (USSD-Notify/no further action) or "2" (session terminated)
   405      */
   406     this._ussdSession = null;
   408    /**
   409     * Regular expresion to parse MMI codes.
   410     */
   411     this._mmiRegExp = null;
   413     /**
   414      * Cell Broadcast Search Lists.
   415      */
   416     let cbmmi = this.cellBroadcastConfigs && this.cellBroadcastConfigs.MMI;
   417     this.cellBroadcastConfigs = {
   418       MMI: cbmmi || null
   419     };
   420     this.mergedCellBroadcastConfig = null;
   421   },
   423   /**
   424    * Parse an integer from a string, falling back to a default value
   425    * if the the provided value is not a string or does not contain a valid
   426    * number.
   427    *
   428    * @param string
   429    *        String to be parsed.
   430    * @param defaultValue [optional]
   431    *        Default value to be used.
   432    * @param radix [optional]
   433    *        A number that represents the numeral system to be used. Default 10.
   434    */
   435   parseInt: function(string, defaultValue, radix) {
   436     let number = parseInt(string, radix || 10);
   437     if (!isNaN(number)) {
   438       return number;
   439     }
   440     if (defaultValue === undefined) {
   441       defaultValue = null;
   442     }
   443     return defaultValue;
   444   },
   447   /**
   448    * Outgoing requests to the RIL. These can be triggered from the
   449    * main thread via messages that look like this:
   450    *
   451    *   {rilMessageType: "methodName",
   452    *    extra:          "parameters",
   453    *    go:             "here"}
   454    *
   455    * So if one of the following methods takes arguments, it takes only one,
   456    * an object, which then contains all of the parameters as attributes.
   457    * The "@param" documentation is to be interpreted accordingly.
   458    */
   460   /**
   461    * Retrieve the ICC's status.
   462    */
   463   getICCStatus: function() {
   464     this.context.Buf.simpleRequest(REQUEST_GET_SIM_STATUS);
   465   },
   467   /**
   468    * Helper function for unlocking ICC locks.
   469    */
   470   iccUnlockCardLock: function(options) {
   471     switch (options.lockType) {
   472       case GECKO_CARDLOCK_PIN:
   473         this.enterICCPIN(options);
   474         break;
   475       case GECKO_CARDLOCK_PIN2:
   476         this.enterICCPIN2(options);
   477         break;
   478       case GECKO_CARDLOCK_PUK:
   479         this.enterICCPUK(options);
   480         break;
   481       case GECKO_CARDLOCK_PUK2:
   482         this.enterICCPUK2(options);
   483         break;
   484       case GECKO_CARDLOCK_NCK:
   485       case GECKO_CARDLOCK_NCK1:
   486       case GECKO_CARDLOCK_NCK2:
   487       case GECKO_CARDLOCK_HNCK:
   488       case GECKO_CARDLOCK_CCK:
   489       case GECKO_CARDLOCK_SPCK:
   490       case GECKO_CARDLOCK_RCCK: // Fall through.
   491       case GECKO_CARDLOCK_RSPCK: {
   492         let type = GECKO_PERSO_LOCK_TO_CARD_PERSO_LOCK[options.lockType];
   493         this.enterDepersonalization(type, options.pin, options);
   494         break;
   495       }
   496       case GECKO_CARDLOCK_NCK_PUK:
   497       case GECKO_CARDLOCK_NCK1_PUK:
   498       case GECKO_CARDLOCK_NCK2_PUK:
   499       case GECKO_CARDLOCK_HNCK_PUK:
   500       case GECKO_CARDLOCK_CCK_PUK:
   501       case GECKO_CARDLOCK_SPCK_PUK:
   502       case GECKO_CARDLOCK_RCCK_PUK: // Fall through.
   503       case GECKO_CARDLOCK_RSPCK_PUK: {
   504         let type = GECKO_PERSO_LOCK_TO_CARD_PERSO_LOCK[options.lockType];
   505         this.enterDepersonalization(type, options.puk, options);
   506         break;
   507       }
   508       default:
   509         options.errorMsg = "Unsupported Card Lock.";
   510         options.success = false;
   511         this.sendChromeMessage(options);
   512     }
   513   },
   515   /**
   516    * Enter a PIN to unlock the ICC.
   517    *
   518    * @param pin
   519    *        String containing the PIN.
   520    * @param [optional] aid
   521    *        AID value.
   522    */
   523   enterICCPIN: function(options) {
   524     let Buf = this.context.Buf;
   525     Buf.newParcel(REQUEST_ENTER_SIM_PIN, options);
   526     Buf.writeInt32(this.v5Legacy ? 1 : 2);
   527     Buf.writeString(options.pin);
   528     if (!this.v5Legacy) {
   529       Buf.writeString(options.aid || this.aid);
   530     }
   531     Buf.sendParcel();
   532   },
   534   /**
   535    * Enter a PIN2 to unlock the ICC.
   536    *
   537    * @param pin
   538    *        String containing the PIN2.
   539    * @param [optional] aid
   540    *        AID value.
   541    */
   542   enterICCPIN2: function(options) {
   543     let Buf = this.context.Buf;
   544     Buf.newParcel(REQUEST_ENTER_SIM_PIN2, options);
   545     Buf.writeInt32(this.v5Legacy ? 1 : 2);
   546     Buf.writeString(options.pin);
   547     if (!this.v5Legacy) {
   548       Buf.writeString(options.aid || this.aid);
   549     }
   550     Buf.sendParcel();
   551   },
   553   /**
   554    * Requests a network personalization be deactivated.
   555    *
   556    * @param type
   557    *        Integer indicating the network personalization be deactivated.
   558    * @param password
   559    *        String containing the password.
   560    */
   561   enterDepersonalization: function(type, password, options) {
   562     let Buf = this.context.Buf;
   563     Buf.newParcel(REQUEST_ENTER_NETWORK_DEPERSONALIZATION_CODE, options);
   564     Buf.writeInt32(type);
   565     Buf.writeString(password);
   566     Buf.sendParcel();
   567   },
   569   /**
   570    * Helper function for changing ICC locks.
   571    */
   572   iccSetCardLock: function(options) {
   573     if (options.newPin !== undefined) { // Change PIN lock.
   574       switch (options.lockType) {
   575         case GECKO_CARDLOCK_PIN:
   576           this.changeICCPIN(options);
   577           break;
   578         case GECKO_CARDLOCK_PIN2:
   579           this.changeICCPIN2(options);
   580           break;
   581         default:
   582           options.errorMsg = "Unsupported Card Lock.";
   583           options.success = false;
   584           this.sendChromeMessage(options);
   585       }
   586     } else { // Enable/Disable lock.
   587       switch (options.lockType) {
   588         case GECKO_CARDLOCK_PIN:
   589           options.facility = ICC_CB_FACILITY_SIM;
   590           options.password = options.pin;
   591           break;
   592         case GECKO_CARDLOCK_FDN:
   593           options.facility = ICC_CB_FACILITY_FDN;
   594           options.password = options.pin2;
   595           break;
   596         default:
   597           options.errorMsg = "Unsupported Card Lock.";
   598           options.success = false;
   599           this.sendChromeMessage(options);
   600           return;
   601       }
   602       options.enabled = options.enabled;
   603       options.serviceClass = ICC_SERVICE_CLASS_VOICE |
   604                              ICC_SERVICE_CLASS_DATA  |
   605                              ICC_SERVICE_CLASS_FAX;
   606       this.setICCFacilityLock(options);
   607     }
   608   },
   610   /**
   611    * Change the current ICC PIN number.
   612    *
   613    * @param pin
   614    *        String containing the old PIN value
   615    * @param newPin
   616    *        String containing the new PIN value
   617    * @param [optional] aid
   618    *        AID value.
   619    */
   620   changeICCPIN: function(options) {
   621     let Buf = this.context.Buf;
   622     Buf.newParcel(REQUEST_CHANGE_SIM_PIN, options);
   623     Buf.writeInt32(this.v5Legacy ? 2 : 3);
   624     Buf.writeString(options.pin);
   625     Buf.writeString(options.newPin);
   626     if (!this.v5Legacy) {
   627       Buf.writeString(options.aid || this.aid);
   628     }
   629     Buf.sendParcel();
   630   },
   632   /**
   633    * Change the current ICC PIN2 number.
   634    *
   635    * @param pin
   636    *        String containing the old PIN2 value
   637    * @param newPin
   638    *        String containing the new PIN2 value
   639    * @param [optional] aid
   640    *        AID value.
   641    */
   642   changeICCPIN2: function(options) {
   643     let Buf = this.context.Buf;
   644     Buf.newParcel(REQUEST_CHANGE_SIM_PIN2, options);
   645     Buf.writeInt32(this.v5Legacy ? 2 : 3);
   646     Buf.writeString(options.pin);
   647     Buf.writeString(options.newPin);
   648     if (!this.v5Legacy) {
   649       Buf.writeString(options.aid || this.aid);
   650     }
   651     Buf.sendParcel();
   652   },
   653   /**
   654    * Supplies ICC PUK and a new PIN to unlock the ICC.
   655    *
   656    * @param puk
   657    *        String containing the PUK value.
   658    * @param newPin
   659    *        String containing the new PIN value.
   660    * @param [optional] aid
   661    *        AID value.
   662    */
   663    enterICCPUK: function(options) {
   664      let Buf = this.context.Buf;
   665      Buf.newParcel(REQUEST_ENTER_SIM_PUK, options);
   666      Buf.writeInt32(this.v5Legacy ? 2 : 3);
   667      Buf.writeString(options.puk);
   668      Buf.writeString(options.newPin);
   669      if (!this.v5Legacy) {
   670        Buf.writeString(options.aid || this.aid);
   671      }
   672      Buf.sendParcel();
   673    },
   675   /**
   676    * Supplies ICC PUK2 and a new PIN2 to unlock the ICC.
   677    *
   678    * @param puk
   679    *        String containing the PUK2 value.
   680    * @param newPin
   681    *        String containing the new PIN2 value.
   682    * @param [optional] aid
   683    *        AID value.
   684    */
   685    enterICCPUK2: function(options) {
   686      let Buf = this.context.Buf;
   687      Buf.newParcel(REQUEST_ENTER_SIM_PUK2, options);
   688      Buf.writeInt32(this.v5Legacy ? 2 : 3);
   689      Buf.writeString(options.puk);
   690      Buf.writeString(options.newPin);
   691      if (!this.v5Legacy) {
   692        Buf.writeString(options.aid || this.aid);
   693      }
   694      Buf.sendParcel();
   695    },
   697   /**
   698    * Helper function for fetching the state of ICC locks.
   699    */
   700   iccGetCardLockState: function(options) {
   701     switch (options.lockType) {
   702       case GECKO_CARDLOCK_PIN:
   703         options.facility = ICC_CB_FACILITY_SIM;
   704         break;
   705       case GECKO_CARDLOCK_FDN:
   706         options.facility = ICC_CB_FACILITY_FDN;
   707         break;
   708       default:
   709         options.errorMsg = "Unsupported Card Lock.";
   710         options.success = false;
   711         this.sendChromeMessage(options);
   712         return;
   713     }
   715     options.password = ""; // For query no need to provide pin.
   716     options.serviceClass = ICC_SERVICE_CLASS_VOICE |
   717                            ICC_SERVICE_CLASS_DATA  |
   718                            ICC_SERVICE_CLASS_FAX;
   719     this.queryICCFacilityLock(options);
   720   },
   722   /**
   723    * Helper function for fetching the number of unlock retries of ICC locks.
   724    *
   725    * We only query the retry count when we're on the emulator. The phones do
   726    * not support the request id and their rild doesn't return an error.
   727    */
   728   iccGetCardLockRetryCount: function(options) {
   729     var selCode = {
   730       pin: ICC_SEL_CODE_SIM_PIN,
   731       puk: ICC_SEL_CODE_SIM_PUK,
   732       pin2: ICC_SEL_CODE_SIM_PIN2,
   733       puk2: ICC_SEL_CODE_SIM_PUK2,
   734       nck: ICC_SEL_CODE_PH_NET_PIN,
   735       cck: ICC_SEL_CODE_PH_CORP_PIN,
   736       spck: ICC_SEL_CODE_PH_SP_PIN
   737     };
   739     if (typeof(selCode[options.lockType]) === 'undefined') {
   740       /* unknown lock type */
   741       options.errorMsg = GECKO_ERROR_GENERIC_FAILURE;
   742       options.success = false;
   743       this.sendChromeMessage(options);
   744       return;
   745     }
   747     if (RILQUIRKS_HAVE_QUERY_ICC_LOCK_RETRY_COUNT) {
   748       /* Only the emulator supports this request, ... */
   749       options.selCode = selCode[options.lockType];
   750       this.queryICCLockRetryCount(options);
   751     } else {
   752       /* ... while the phones do not. */
   753       options.errorMsg = GECKO_ERROR_REQUEST_NOT_SUPPORTED;
   754       options.success = false;
   755       this.sendChromeMessage(options);
   756     }
   757   },
   759   /**
   760    * Query ICC lock retry count.
   761    *
   762    * @param selCode
   763    *        One of ICC_SEL_CODE_*.
   764    * @param serviceClass
   765    *        One of ICC_SERVICE_CLASS_*.
   766    */
   767   queryICCLockRetryCount: function(options) {
   768     let Buf = this.context.Buf;
   769     Buf.newParcel(REQUEST_GET_UNLOCK_RETRY_COUNT, options);
   770     Buf.writeInt32(1);
   771     Buf.writeString(options.selCode);
   772     Buf.sendParcel();
   773   },
   775   /**
   776    * Query ICC facility lock.
   777    *
   778    * @param facility
   779    *        One of ICC_CB_FACILITY_*.
   780    * @param password
   781    *        Password for the facility, or "" if not required.
   782    * @param serviceClass
   783    *        One of ICC_SERVICE_CLASS_*.
   784    * @param [optional] aid
   785    *        AID value.
   786    */
   787   queryICCFacilityLock: function(options) {
   788     let Buf = this.context.Buf;
   789     Buf.newParcel(REQUEST_QUERY_FACILITY_LOCK, options);
   790     Buf.writeInt32(this.v5Legacy ? 3 : 4);
   791     Buf.writeString(options.facility);
   792     Buf.writeString(options.password);
   793     Buf.writeString(options.serviceClass.toString());
   794     if (!this.v5Legacy) {
   795       Buf.writeString(options.aid || this.aid);
   796     }
   797     Buf.sendParcel();
   798   },
   800   /**
   801    * Set ICC facility lock.
   802    *
   803    * @param facility
   804    *        One of ICC_CB_FACILITY_*.
   805    * @param enabled
   806    *        true to enable, false to disable.
   807    * @param password
   808    *        Password for the facility, or "" if not required.
   809    * @param serviceClass
   810    *        One of ICC_SERVICE_CLASS_*.
   811    * @param [optional] aid
   812    *        AID value.
   813    */
   814   setICCFacilityLock: function(options) {
   815     let Buf = this.context.Buf;
   816     Buf.newParcel(REQUEST_SET_FACILITY_LOCK, options);
   817     Buf.writeInt32(this.v5Legacy ? 4 : 5);
   818     Buf.writeString(options.facility);
   819     Buf.writeString(options.enabled ? "1" : "0");
   820     Buf.writeString(options.password);
   821     Buf.writeString(options.serviceClass.toString());
   822     if (!this.v5Legacy) {
   823       Buf.writeString(options.aid || this.aid);
   824     }
   825     Buf.sendParcel();
   826   },
   828   /**
   829    *  Request an ICC I/O operation.
   830    *
   831    *  See TS 27.007 "restricted SIM" operation, "AT Command +CRSM".
   832    *  The sequence is in the same order as how libril reads this parcel,
   833    *  see the struct RIL_SIM_IO_v5 or RIL_SIM_IO_v6 defined in ril.h
   834    *
   835    *  @param command
   836    *         The I/O command, one of the ICC_COMMAND_* constants.
   837    *  @param fileId
   838    *         The file to operate on, one of the ICC_EF_* constants.
   839    *  @param pathId
   840    *         String type, check the 'pathid' parameter from TS 27.007 +CRSM.
   841    *  @param p1, p2, p3
   842    *         Arbitrary integer parameters for the command.
   843    *  @param [optional] dataWriter
   844    *         The function for writing string parameter for the ICC_COMMAND_UPDATE_RECORD.
   845    *  @param [optional] pin2
   846    *         String containing the PIN2.
   847    *  @param [optional] aid
   848    *         AID value.
   849    */
   850   iccIO: function(options) {
   851     let Buf = this.context.Buf;
   852     Buf.newParcel(REQUEST_SIM_IO, options);
   853     Buf.writeInt32(options.command);
   854     Buf.writeInt32(options.fileId);
   855     Buf.writeString(options.pathId);
   856     Buf.writeInt32(options.p1);
   857     Buf.writeInt32(options.p2);
   858     Buf.writeInt32(options.p3);
   860     // Write data.
   861     if (options.command == ICC_COMMAND_UPDATE_RECORD &&
   862         options.dataWriter) {
   863       options.dataWriter(options.p3);
   864     } else {
   865       Buf.writeString(null);
   866     }
   868     // Write pin2.
   869     if (options.command == ICC_COMMAND_UPDATE_RECORD &&
   870         options.pin2) {
   871       Buf.writeString(options.pin2);
   872     } else {
   873       Buf.writeString(null);
   874     }
   876     if (!this.v5Legacy) {
   877       Buf.writeString(options.aid || this.aid);
   878     }
   879     Buf.sendParcel();
   880   },
   882   /**
   883    * Get IMSI.
   884    *
   885    * @param [optional] aid
   886    *        AID value.
   887    */
   888   getIMSI: function(aid) {
   889     let Buf = this.context.Buf;
   890     if (this.v5Legacy) {
   891       Buf.simpleRequest(REQUEST_GET_IMSI);
   892       return;
   893     }
   894     Buf.newParcel(REQUEST_GET_IMSI);
   895     Buf.writeInt32(1);
   896     Buf.writeString(aid || this.aid);
   897     Buf.sendParcel();
   898   },
   900   /**
   901    * Read UICC Phonebook contacts.
   902    *
   903    * @param contactType
   904    *        "adn" or "fdn".
   905    * @param requestId
   906    *        Request id from RadioInterfaceLayer.
   907    */
   908   readICCContacts: function(options) {
   909     if (!this.appType) {
   910       options.errorMsg = CONTACT_ERR_REQUEST_NOT_SUPPORTED;
   911       this.sendChromeMessage(options);
   912       return;
   913     }
   915     this.context.ICCContactHelper.readICCContacts(
   916       this.appType,
   917       options.contactType,
   918       function onsuccess(contacts) {
   919         for (let i = 0; i < contacts.length; i++) {
   920           let contact = contacts[i];
   921           let pbrIndex = contact.pbrIndex || 0;
   922           let recordIndex = pbrIndex * ICC_MAX_LINEAR_FIXED_RECORDS + contact.recordId;
   923           contact.contactId = this.iccInfo.iccid + recordIndex;
   924         }
   925         // Reuse 'options' to get 'requestId' and 'contactType'.
   926         options.contacts = contacts;
   927         this.sendChromeMessage(options);
   928       }.bind(this),
   929       function onerror(errorMsg) {
   930         options.errorMsg = errorMsg;
   931         this.sendChromeMessage(options);
   932       }.bind(this));
   933   },
   935   /**
   936    * Update UICC Phonebook.
   937    *
   938    * @param contactType   "adn" or "fdn".
   939    * @param contact       The contact will be updated.
   940    * @param pin2          PIN2 is required for updating FDN.
   941    * @param requestId     Request id from RadioInterfaceLayer.
   942    */
   943   updateICCContact: function(options) {
   944     let onsuccess = function onsuccess() {
   945       let recordIndex =
   946         contact.pbrIndex * ICC_MAX_LINEAR_FIXED_RECORDS + contact.recordId;
   947       contact.contactId = this.iccInfo.iccid + recordIndex;
   948       // Reuse 'options' to get 'requestId' and 'contactType'.
   949       this.sendChromeMessage(options);
   950     }.bind(this);
   952     let onerror = function onerror(errorMsg) {
   953       options.errorMsg = errorMsg;
   954       this.sendChromeMessage(options);
   955     }.bind(this);
   957     if (!this.appType || !options.contact) {
   958       onerror(CONTACT_ERR_REQUEST_NOT_SUPPORTED );
   959       return;
   960     }
   962     let contact = options.contact;
   963     let iccid = this.iccInfo.iccid;
   964     let isValidRecordId = false;
   965     if (typeof contact.contactId === "string" &&
   966         contact.contactId.startsWith(iccid)) {
   967       let recordIndex = contact.contactId.substring(iccid.length);
   968       contact.pbrIndex = Math.floor(recordIndex / ICC_MAX_LINEAR_FIXED_RECORDS);
   969       contact.recordId = recordIndex % ICC_MAX_LINEAR_FIXED_RECORDS;
   970       isValidRecordId = contact.recordId > 0 && contact.recordId < 0xff;
   971     }
   973     if (DEBUG) {
   974       this.context.debug("Update ICC Contact " + JSON.stringify(contact));
   975     }
   977     let ICCContactHelper = this.context.ICCContactHelper;
   978     // If contact has 'recordId' property, updates corresponding record.
   979     // If not, inserts the contact into a free record.
   980     if (isValidRecordId) {
   981       ICCContactHelper.updateICCContact(
   982         this.appType, options.contactType, contact, options.pin2, onsuccess, onerror);
   983     } else {
   984       ICCContactHelper.addICCContact(
   985         this.appType, options.contactType, contact, options.pin2, onsuccess, onerror);
   986     }
   987   },
   989   /**
   990    * Request the phone's radio to be enabled or disabled.
   991    *
   992    * @param enabled
   993    *        Boolean indicating the desired state.
   994    */
   995   setRadioEnabled: function(options) {
   996     let Buf = this.context.Buf;
   997     Buf.newParcel(REQUEST_RADIO_POWER, options);
   998     Buf.writeInt32(1);
   999     Buf.writeInt32(options.enabled ? 1 : 0);
  1000     Buf.sendParcel();
  1001   },
  1003   /**
  1004    * Query call waiting status via MMI.
  1005    */
  1006   _handleQueryMMICallWaiting: function(options) {
  1007     let Buf = this.context.Buf;
  1009     function callback(options) {
  1010       options.length = Buf.readInt32();
  1011       options.enabled = (Buf.readInt32() === 1);
  1012       let services = Buf.readInt32();
  1013       if (options.enabled) {
  1014         options.statusMessage = MMI_SM_KS_SERVICE_ENABLED_FOR;
  1015         let serviceClass = [];
  1016         for (let serviceClassMask = 1;
  1017              serviceClassMask <= ICC_SERVICE_CLASS_MAX;
  1018              serviceClassMask <<= 1) {
  1019           if ((serviceClassMask & services) !== 0) {
  1020             serviceClass.push(MMI_KS_SERVICE_CLASS_MAPPING[serviceClassMask]);
  1023         options.additionalInformation = serviceClass;
  1024       } else {
  1025         options.statusMessage = MMI_SM_KS_SERVICE_DISABLED;
  1028       // Prevent DataCloneError when sending chrome messages.
  1029       delete options.callback;
  1030       this.sendChromeMessage(options);
  1033     options.callback = callback;
  1034     this.queryCallWaiting(options);
  1035   },
  1037   /**
  1038    * Set call waiting status via MMI.
  1039    */
  1040   _handleSetMMICallWaiting: function(options) {
  1041     function callback(options) {
  1042       if (options.enabled) {
  1043         options.statusMessage = MMI_SM_KS_SERVICE_ENABLED;
  1044       } else {
  1045         options.statusMessage = MMI_SM_KS_SERVICE_DISABLED;
  1048       // Prevent DataCloneError when sending chrome messages.
  1049       delete options.callback;
  1050       this.sendChromeMessage(options);
  1053     options.callback = callback;
  1054     this.setCallWaiting(options);
  1055   },
  1057   /**
  1058    * Query call waiting status.
  1060    */
  1061   queryCallWaiting: function(options) {
  1062     let Buf = this.context.Buf;
  1063     Buf.newParcel(REQUEST_QUERY_CALL_WAITING, options);
  1064     Buf.writeInt32(1);
  1065     // As per 3GPP TS 24.083, section 1.6 UE doesn't need to send service
  1066     // class parameter in call waiting interrogation  to network
  1067     Buf.writeInt32(ICC_SERVICE_CLASS_NONE);
  1068     Buf.sendParcel();
  1069   },
  1071   /**
  1072    * Set call waiting status.
  1074    * @param on
  1075    *        Boolean indicating the desired waiting status.
  1076    */
  1077   setCallWaiting: function(options) {
  1078     let Buf = this.context.Buf;
  1079     Buf.newParcel(REQUEST_SET_CALL_WAITING, options);
  1080     Buf.writeInt32(2);
  1081     Buf.writeInt32(options.enabled ? 1 : 0);
  1082     Buf.writeInt32(options.serviceClass !== undefined ?
  1083                     options.serviceClass : ICC_SERVICE_CLASS_VOICE);
  1084     Buf.sendParcel();
  1085   },
  1087   /**
  1088    * Queries current CLIP status.
  1090    * (MMI request for code "*#30#")
  1092    */
  1093   queryCLIP: function(options) {
  1094     this.context.Buf.simpleRequest(REQUEST_QUERY_CLIP, options);
  1095   },
  1097   /**
  1098    * Queries current CLIR status.
  1100    */
  1101   getCLIR: function(options) {
  1102     this.context.Buf.simpleRequest(REQUEST_GET_CLIR, options);
  1103   },
  1105   /**
  1106    * Enables or disables the presentation of the calling line identity (CLI) to
  1107    * the called party when originating a call.
  1109    * @param options.clirMode
  1110    *        Is one of the CLIR_* constants in
  1111    *        nsIDOMMozMobileConnection interface.
  1112    */
  1113   setCLIR: function(options) {
  1114     if (options) {
  1115       this.clirMode = options.clirMode;
  1117     let Buf = this.context.Buf;
  1118     Buf.newParcel(REQUEST_SET_CLIR, options);
  1119     Buf.writeInt32(1);
  1120     Buf.writeInt32(this.clirMode);
  1121     Buf.sendParcel();
  1122   },
  1124   /**
  1125    * Set screen state.
  1127    * @param on
  1128    *        Boolean indicating whether the screen should be on or off.
  1129    */
  1130   setScreenState: function(options) {
  1131     let Buf = this.context.Buf;
  1132     Buf.newParcel(REQUEST_SCREEN_STATE);
  1133     Buf.writeInt32(1);
  1134     Buf.writeInt32(options.on ? 1 : 0);
  1135     Buf.sendParcel();
  1136   },
  1138   getVoiceRegistrationState: function() {
  1139     this.context.Buf.simpleRequest(REQUEST_VOICE_REGISTRATION_STATE);
  1140   },
  1142   getVoiceRadioTechnology: function() {
  1143     this.context.Buf.simpleRequest(REQUEST_VOICE_RADIO_TECH);
  1144   },
  1146   getDataRegistrationState: function() {
  1147     this.context.Buf.simpleRequest(REQUEST_DATA_REGISTRATION_STATE);
  1148   },
  1150   getOperator: function() {
  1151     this.context.Buf.simpleRequest(REQUEST_OPERATOR);
  1152   },
  1154   /**
  1155    * Set the preferred network type.
  1157    * @param options An object contains a valid index of
  1158    *                RIL_PREFERRED_NETWORK_TYPE_TO_GECKO as its `networkType`
  1159    *                attribute, or undefined to set current preferred network
  1160    *                type.
  1161    */
  1162   setPreferredNetworkType: function(options) {
  1163     if (options) {
  1164       this.preferredNetworkType = options.networkType;
  1166     if (this.preferredNetworkType == null) {
  1167       return;
  1170     let Buf = this.context.Buf;
  1171     Buf.newParcel(REQUEST_SET_PREFERRED_NETWORK_TYPE, options);
  1172     Buf.writeInt32(1);
  1173     Buf.writeInt32(this.preferredNetworkType);
  1174     Buf.sendParcel();
  1175   },
  1177   /**
  1178    * Get the preferred network type.
  1179    */
  1180   getPreferredNetworkType: function(options) {
  1181     this.context.Buf.simpleRequest(REQUEST_GET_PREFERRED_NETWORK_TYPE, options);
  1182   },
  1184   /**
  1185    * Request various states about the network.
  1186    */
  1187   requestNetworkInfo: function() {
  1188     if (this._processingNetworkInfo) {
  1189       if (DEBUG) {
  1190         this.context.debug("Network info requested, but we're already " +
  1191                            "requesting network info.");
  1193       this._needRepollNetworkInfo = true;
  1194       return;
  1197     if (DEBUG) this.context.debug("Requesting network info");
  1199     this._processingNetworkInfo = true;
  1200     this.getVoiceRegistrationState();
  1201     this.getDataRegistrationState(); //TODO only GSM
  1202     this.getOperator();
  1203     this.getNetworkSelectionMode();
  1204     this.getSignalStrength();
  1205   },
  1207   /**
  1208    * Get the available networks
  1209    */
  1210   getAvailableNetworks: function(options) {
  1211     if (DEBUG) this.context.debug("Getting available networks");
  1212     let Buf = this.context.Buf;
  1213     Buf.newParcel(REQUEST_QUERY_AVAILABLE_NETWORKS, options);
  1214     Buf.sendParcel();
  1215   },
  1217   /**
  1218    * Request the radio's network selection mode
  1219    */
  1220   getNetworkSelectionMode: function() {
  1221     if (DEBUG) this.context.debug("Getting network selection mode");
  1222     this.context.Buf.simpleRequest(REQUEST_QUERY_NETWORK_SELECTION_MODE);
  1223   },
  1225   /**
  1226    * Tell the radio to automatically choose a voice/data network
  1227    */
  1228   selectNetworkAuto: function(options) {
  1229     if (DEBUG) this.context.debug("Setting automatic network selection");
  1230     this.context.Buf.simpleRequest(REQUEST_SET_NETWORK_SELECTION_AUTOMATIC, options);
  1231   },
  1233   /**
  1234    * Set the roaming preference mode
  1235    */
  1236   setRoamingPreference: function(options) {
  1237     let roamingMode = CDMA_ROAMING_PREFERENCE_TO_GECKO.indexOf(options.mode);
  1239     if (roamingMode === -1) {
  1240       options.errorMsg = GECKO_ERROR_INVALID_PARAMETER;
  1241       this.sendChromeMessage(options);
  1242       return;
  1245     let Buf = this.context.Buf;
  1246     Buf.newParcel(REQUEST_CDMA_SET_ROAMING_PREFERENCE, options);
  1247     Buf.writeInt32(1);
  1248     Buf.writeInt32(roamingMode);
  1249     Buf.sendParcel();
  1250   },
  1252   /**
  1253    * Get the roaming preference mode
  1254    */
  1255   queryRoamingPreference: function(options) {
  1256     this.context.Buf.simpleRequest(REQUEST_CDMA_QUERY_ROAMING_PREFERENCE, options);
  1257   },
  1259   /**
  1260    * Set the voice privacy mode
  1261    */
  1262   setVoicePrivacyMode: function(options) {
  1263     let Buf = this.context.Buf;
  1264     Buf.newParcel(REQUEST_CDMA_SET_PREFERRED_VOICE_PRIVACY_MODE, options);
  1265     Buf.writeInt32(1);
  1266     Buf.writeInt32(options.enabled ? 1 : 0);
  1267     Buf.sendParcel();
  1268   },
  1270   /**
  1271    * Get the voice privacy mode
  1272    */
  1273   queryVoicePrivacyMode: function(options) {
  1274     this.context.Buf.simpleRequest(REQUEST_CDMA_QUERY_PREFERRED_VOICE_PRIVACY_MODE, options);
  1275   },
  1277   /**
  1278    * Open Logical UICC channel (aid) for Secure Element access
  1279    */
  1280   iccOpenChannel: function(options) {
  1281     if (DEBUG) {
  1282       this.context.debug("iccOpenChannel: " + JSON.stringify(options));
  1285     let Buf = this.context.Buf;
  1286     Buf.newParcel(REQUEST_SIM_OPEN_CHANNEL, options);
  1287     Buf.writeString(options.aid);
  1288     Buf.sendParcel();
  1289   },
  1291 /**
  1292    * Exchange APDU data on an open Logical UICC channel
  1293    */
  1294   iccExchangeAPDU: function(options) {
  1295     if (DEBUG) this.context.debug("iccExchangeAPDU: " + JSON.stringify(options));
  1297     let cla = options.apdu.cla;
  1298     let command = options.apdu.command;
  1299     let channel = options.channel;
  1300     let path = options.apdu.path || "";
  1301     let data = options.apdu.data || "";
  1302     let data2 = options.apdu.data2 || "";
  1304     let p1 = options.apdu.p1;
  1305     let p2 = options.apdu.p2;
  1306     let p3 = options.apdu.p3; // Extra
  1308     let Buf = this.context.Buf;
  1309     Buf.newParcel(REQUEST_SIM_ACCESS_CHANNEL, options);
  1310     Buf.writeInt32(cla);
  1311     Buf.writeInt32(command);
  1312     Buf.writeInt32(channel);
  1313     Buf.writeString(path); // path
  1314     Buf.writeInt32(p1);
  1315     Buf.writeInt32(p2);
  1316     Buf.writeInt32(p3);
  1317     Buf.writeString(data); // generic data field.
  1318     Buf.writeString(data2);
  1320     Buf.sendParcel();
  1321   },
  1323   /**
  1324    * Close Logical UICC channel
  1325    */
  1326   iccCloseChannel: function(options) {
  1327     if (DEBUG) this.context.debug("iccCloseChannel: " + JSON.stringify(options));
  1329     let Buf = this.context.Buf;
  1330     Buf.newParcel(REQUEST_SIM_CLOSE_CHANNEL, options);
  1331     Buf.writeInt32(1);
  1332     Buf.writeInt32(options.channel);
  1333     Buf.sendParcel();
  1334   },
  1336   /**
  1337    * Tell the radio to choose a specific voice/data network
  1338    */
  1339   selectNetwork: function(options) {
  1340     if (DEBUG) {
  1341       this.context.debug("Setting manual network selection: " +
  1342                          options.mcc + ", " + options.mnc);
  1345     let numeric = (options.mcc && options.mnc) ? options.mcc + options.mnc : null;
  1346     let Buf = this.context.Buf;
  1347     Buf.newParcel(REQUEST_SET_NETWORK_SELECTION_MANUAL, options);
  1348     Buf.writeString(numeric);
  1349     Buf.sendParcel();
  1350   },
  1352   /**
  1353    * Get current calls.
  1354    */
  1355   getCurrentCalls: function() {
  1356     this.context.Buf.simpleRequest(REQUEST_GET_CURRENT_CALLS);
  1357   },
  1359   /**
  1360    * Get the signal strength.
  1361    */
  1362   getSignalStrength: function() {
  1363     this.context.Buf.simpleRequest(REQUEST_SIGNAL_STRENGTH);
  1364   },
  1366   getIMEI: function(options) {
  1367     this.context.Buf.simpleRequest(REQUEST_GET_IMEI, options);
  1368   },
  1370   getIMEISV: function() {
  1371     this.context.Buf.simpleRequest(REQUEST_GET_IMEISV);
  1372   },
  1374   getDeviceIdentity: function() {
  1375     this.context.Buf.simpleRequest(REQUEST_DEVICE_IDENTITY);
  1376   },
  1378   getBasebandVersion: function() {
  1379     this.context.Buf.simpleRequest(REQUEST_BASEBAND_VERSION);
  1380   },
  1382   sendExitEmergencyCbModeRequest: function(options) {
  1383     this.context.Buf.simpleRequest(REQUEST_EXIT_EMERGENCY_CALLBACK_MODE, options);
  1384   },
  1386   getCdmaSubscription: function() {
  1387     this.context.Buf.simpleRequest(REQUEST_CDMA_SUBSCRIPTION);
  1388   },
  1390   exitEmergencyCbMode: function(options) {
  1391     // The function could be called by an API from RadioInterfaceLayer or by
  1392     // ril_worker itself. From ril_worker, we won't pass the parameter
  1393     // 'options'. In this case, it is marked as internal.
  1394     if (!options) {
  1395       options = {internal: true};
  1397     this._cancelEmergencyCbModeTimeout();
  1398     this.sendExitEmergencyCbModeRequest(options);
  1399   },
  1401   /**
  1402    * Cache the request for making an emergency call when radio is off. The
  1403    * request shall include two types of callback functions. 'callback' is
  1404    * called when radio is ready, and 'onerror' is called when turning radio
  1405    * on fails.
  1406    */
  1407   cachedDialRequest : null,
  1409   /**
  1410    * Dial the phone.
  1412    * @param number
  1413    *        String containing the number to dial.
  1414    * @param clirMode
  1415    *        Integer for showing/hidding the caller Id to the called party.
  1416    * @param uusInfo
  1417    *        Integer doing something XXX TODO
  1418    */
  1419   dial: function(options) {
  1420     let onerror = (function onerror(options, errorMsg) {
  1421       options.success = false;
  1422       options.errorMsg = errorMsg;
  1423       this.sendChromeMessage(options);
  1424     }).bind(this, options);
  1426     if (this._isEmergencyNumber(options.number)) {
  1427       this.dialEmergencyNumber(options, onerror);
  1428     } else {
  1429       if (!this._isCdma) {
  1430         // TODO: Both dial() and sendMMI() functions should be unified at some
  1431         // point in the future. In the mean time we handle temporary CLIR MMI
  1432         // commands through the dial() function. Please see bug 889737.
  1433         let mmi = this._parseMMI(options.number);
  1434         if (mmi && this._isTemporaryModeCLIR(mmi)) {
  1435           options.number = mmi.dialNumber;
  1436           // In temporary mode, MMI_PROCEDURE_ACTIVATION means allowing CLI
  1437           // presentation, i.e. CLIR_SUPPRESSION. See TS 22.030, Annex B.
  1438           options.clirMode = mmi.procedure == MMI_PROCEDURE_ACTIVATION ?
  1439                              CLIR_SUPPRESSION : CLIR_INVOCATION;
  1442       this.dialNonEmergencyNumber(options, onerror);
  1444   },
  1446   dialNonEmergencyNumber: function(options, onerror) {
  1447     if (this.radioState == GECKO_RADIOSTATE_OFF) {
  1448       // Notify error in establishing the call without radio.
  1449       onerror(GECKO_ERROR_RADIO_NOT_AVAILABLE);
  1450       return;
  1453     if (this.voiceRegistrationState.emergencyCallsOnly ||
  1454         options.isDialEmergency) {
  1455       onerror(RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[CALL_FAIL_UNOBTAINABLE_NUMBER]);
  1456       return;
  1459     // Exit emergency callback mode when user dial a non-emergency call.
  1460     if (this._isInEmergencyCbMode) {
  1461       this.exitEmergencyCbMode();
  1464     if (this._isCdma && Object.keys(this.currentCalls).length == 1) {
  1465       // Make a Cdma 3way call.
  1466       options.featureStr = options.number;
  1467       this.sendCdmaFlashCommand(options);
  1468     } else {
  1469       options.request = REQUEST_DIAL;
  1470       this.sendDialRequest(options);
  1472   },
  1474   dialEmergencyNumber: function(options, onerror) {
  1475     options.request = RILQUIRKS_REQUEST_USE_DIAL_EMERGENCY_CALL ?
  1476                       REQUEST_DIAL_EMERGENCY_CALL : REQUEST_DIAL;
  1477     if (this.radioState == GECKO_RADIOSTATE_OFF) {
  1478       if (DEBUG) {
  1479         this.context.debug("Automatically enable radio for an emergency call.");
  1482       if (!this.cachedDialRequest) {
  1483         this.cachedDialRequest = {};
  1485       this.cachedDialRequest.onerror = onerror;
  1486       this.cachedDialRequest.callback = this.sendDialRequest.bind(this, options);
  1487       this.setRadioEnabled({enabled: true});
  1488       return;
  1491     if (this._isCdma && Object.keys(this.currentCalls).length == 1) {
  1492       // Make a Cdma 3way call.
  1493       options.featureStr = options.number;
  1494       this.sendCdmaFlashCommand(options);
  1495     } else {
  1496       this.sendDialRequest(options);
  1498   },
  1500   sendDialRequest: function(options) {
  1501     // Always succeed.
  1502     options.success = true;
  1503     this.sendChromeMessage(options);
  1504     this._createPendingOutgoingCall(options);
  1506     let Buf = this.context.Buf;
  1507     Buf.newParcel(options.request, options);
  1508     Buf.writeString(options.number);
  1509     Buf.writeInt32(options.clirMode || 0);
  1510     Buf.writeInt32(options.uusInfo || 0);
  1511     // TODO Why do we need this extra 0? It was put it in to make this
  1512     // match the format of the binary message.
  1513     Buf.writeInt32(0);
  1514     Buf.sendParcel();
  1515   },
  1517   sendCdmaFlashCommand: function(options) {
  1518     let Buf = this.context.Buf;
  1519     options.isCdma = true;
  1520     options.request = REQUEST_CDMA_FLASH;
  1521     Buf.newParcel(options.request, options);
  1522     Buf.writeString(options.featureStr);
  1523     Buf.sendParcel();
  1524   },
  1526   /**
  1527    * Hang up all calls
  1528    */
  1529   hangUpAll: function() {
  1530     for (let callIndex in this.currentCalls) {
  1531       this.hangUp({callIndex: callIndex});
  1533   },
  1535   /**
  1536    * Hang up the phone.
  1538    * @param callIndex
  1539    *        Call index (1-based) as reported by REQUEST_GET_CURRENT_CALLS.
  1540    */
  1541   hangUp: function(options) {
  1542     let call = this.currentCalls[options.callIndex];
  1543     if (!call) {
  1544       return;
  1547     let callIndex = call.callIndex;
  1548     if (callIndex === OUTGOING_PLACEHOLDER_CALL_INDEX) {
  1549       if (DEBUG) this.context.debug("Hang up pending outgoing call.");
  1550       this._removeVoiceCall(call, GECKO_CALL_ERROR_NORMAL_CALL_CLEARING);
  1551       return;
  1554     call.hangUpLocal = true;
  1556     if (call.state === CALL_STATE_HOLDING) {
  1557       this.sendHangUpBackgroundRequest(callIndex);
  1558     } else {
  1559       this.sendHangUpRequest(callIndex);
  1561   },
  1563   sendHangUpRequest: function(callIndex) {
  1564     let Buf = this.context.Buf;
  1565     Buf.newParcel(REQUEST_HANGUP);
  1566     Buf.writeInt32(1);
  1567     Buf.writeInt32(callIndex);
  1568     Buf.sendParcel();
  1569   },
  1571   sendHangUpBackgroundRequest: function(callIndex) {
  1572     let Buf = this.context.Buf;
  1573     Buf.simpleRequest(REQUEST_HANGUP_WAITING_OR_BACKGROUND);
  1574   },
  1576   /**
  1577    * Mute or unmute the radio.
  1579    * @param mute
  1580    *        Boolean to indicate whether to mute or unmute the radio.
  1581    */
  1582   setMute: function(options) {
  1583     let Buf = this.context.Buf;
  1584     Buf.newParcel(REQUEST_SET_MUTE);
  1585     Buf.writeInt32(1);
  1586     Buf.writeInt32(options.muted ? 1 : 0);
  1587     Buf.sendParcel();
  1588   },
  1590   /**
  1591    * Answer an incoming/waiting call.
  1593    * @param callIndex
  1594    *        Call index of the call to answer.
  1595    */
  1596   answerCall: function(options) {
  1597     // Check for races. Since we dispatched the incoming/waiting call
  1598     // notification the incoming/waiting call may have changed. The main
  1599     // thread thinks that it is answering the call with the given index,
  1600     // so only answer if that is still incoming/waiting.
  1601     let call = this.currentCalls[options.callIndex];
  1602     if (!call) {
  1603       return;
  1606     let Buf = this.context.Buf;
  1607     switch (call.state) {
  1608       case CALL_STATE_INCOMING:
  1609         Buf.simpleRequest(REQUEST_ANSWER);
  1610         break;
  1611       case CALL_STATE_WAITING:
  1612         // Answer the waiting (second) call, and hold the first call.
  1613         Buf.simpleRequest(REQUEST_SWITCH_WAITING_OR_HOLDING_AND_ACTIVE);
  1614         break;
  1616   },
  1618   /**
  1619    * Reject an incoming/waiting call.
  1621    * @param callIndex
  1622    *        Call index of the call to reject.
  1623    */
  1624   rejectCall: function(options) {
  1625     // Check for races. Since we dispatched the incoming/waiting call
  1626     // notification the incoming/waiting call may have changed. The main
  1627     // thread thinks that it is rejecting the call with the given index,
  1628     // so only reject if that is still incoming/waiting.
  1629     let call = this.currentCalls[options.callIndex];
  1630     if (!call) {
  1631       return;
  1634     call.hangUpLocal = true;
  1636     let Buf = this.context.Buf;
  1637     if (this._isCdma) {
  1638       // AT+CHLD=0 means "release held or UDUB."
  1639       Buf.simpleRequest(REQUEST_HANGUP_WAITING_OR_BACKGROUND);
  1640       return;
  1643     switch (call.state) {
  1644       case CALL_STATE_INCOMING:
  1645         Buf.simpleRequest(REQUEST_UDUB);
  1646         break;
  1647       case CALL_STATE_WAITING:
  1648         // Reject the waiting (second) call, and remain the first call.
  1649         Buf.simpleRequest(REQUEST_HANGUP_WAITING_OR_BACKGROUND);
  1650         break;
  1652   },
  1654   holdCall: function(options) {
  1655     let call = this.currentCalls[options.callIndex];
  1656     if (!call) {
  1657       options.errorMsg = GECKO_ERROR_GENERIC_FAILURE;
  1658       options.success = false;
  1659       this.sendChromeMessage(options);
  1660       return;
  1663     let Buf = this.context.Buf;
  1664     if (this._isCdma) {
  1665       options.featureStr = "";
  1666       this.sendCdmaFlashCommand(options);
  1667     } else if (call.state == CALL_STATE_ACTIVE) {
  1668       Buf.simpleRequest(REQUEST_SWITCH_WAITING_OR_HOLDING_AND_ACTIVE, options);
  1670  },
  1672   resumeCall: function(options) {
  1673     let call = this.currentCalls[options.callIndex];
  1674     if (!call) {
  1675       options.errorMsg = GECKO_ERROR_GENERIC_FAILURE;
  1676       options.success = false;
  1677       this.sendChromeMessage(options);
  1678       return;
  1681     let Buf = this.context.Buf;
  1682     if (this._isCdma) {
  1683       options.featureStr = "";
  1684       this.sendCdmaFlashCommand(options);
  1685     } else if (call.state == CALL_STATE_HOLDING) {
  1686       Buf.simpleRequest(REQUEST_SWITCH_WAITING_OR_HOLDING_AND_ACTIVE, options);
  1688   },
  1690   // Flag indicating whether user has requested making a conference call.
  1691   _hasConferenceRequest: false,
  1693   conferenceCall: function(options) {
  1694     let Buf = this.context.Buf;
  1695     if (this._isCdma) {
  1696       options.featureStr = "";
  1697       this.sendCdmaFlashCommand(options);
  1698     } else {
  1699       this._hasConferenceRequest = true;
  1700       Buf.simpleRequest(REQUEST_CONFERENCE, options);
  1702   },
  1704   separateCall: function(options) {
  1705     let call = this.currentCalls[options.callIndex];
  1706     if (!call) {
  1707       options.errorName = "removeError";
  1708       options.errorMsg = GECKO_ERROR_GENERIC_FAILURE;
  1709       options.success = false;
  1710       this.sendChromeMessage(options);
  1711       return;
  1714     let Buf = this.context.Buf;
  1715     if (this._isCdma) {
  1716       options.featureStr = "";
  1717       this.sendCdmaFlashCommand(options);
  1718     } else {
  1719       Buf.newParcel(REQUEST_SEPARATE_CONNECTION, options);
  1720       Buf.writeInt32(1);
  1721       Buf.writeInt32(options.callIndex);
  1722       Buf.sendParcel();
  1724  },
  1726   holdConference: function() {
  1727     if (this._isCdma) {
  1728       return;
  1731     this.context.Buf.simpleRequest(REQUEST_SWITCH_WAITING_OR_HOLDING_AND_ACTIVE);
  1732   },
  1734   resumeConference: function() {
  1735     if (this._isCdma) {
  1736       return;
  1739     this.context.Buf.simpleRequest(REQUEST_SWITCH_WAITING_OR_HOLDING_AND_ACTIVE);
  1740   },
  1742   /**
  1743    * Send an SMS.
  1745    * The `options` parameter object should contain the following attributes:
  1747    * @param number
  1748    *        String containing the recipient number.
  1749    * @param body
  1750    *        String containing the message text.
  1751    * @param envelopeId
  1752    *        Numeric value identifying the sms request.
  1753    */
  1754   sendSMS: function(options) {
  1755     options.langIndex = options.langIndex || PDU_NL_IDENTIFIER_DEFAULT;
  1756     options.langShiftIndex = options.langShiftIndex || PDU_NL_IDENTIFIER_DEFAULT;
  1758     if (!options.retryCount) {
  1759       options.retryCount = 0;
  1762     if (!options.segmentSeq) {
  1763       // Fist segment to send
  1764       options.segmentSeq = 1;
  1765       options.body = options.segments[0].body;
  1766       options.encodedBodyLength = options.segments[0].encodedBodyLength;
  1769     let Buf = this.context.Buf;
  1770     if (this._isCdma) {
  1771       Buf.newParcel(REQUEST_CDMA_SEND_SMS, options);
  1772       this.context.CdmaPDUHelper.writeMessage(options);
  1773     } else {
  1774       Buf.newParcel(REQUEST_SEND_SMS, options);
  1775       Buf.writeInt32(2);
  1776       Buf.writeString(options.SMSC);
  1777       this.context.GsmPDUHelper.writeMessage(options);
  1779     Buf.sendParcel();
  1780   },
  1782   /**
  1783    * Acknowledge the receipt and handling of an SMS.
  1785    * @param success
  1786    *        Boolean indicating whether the message was successfuly handled.
  1787    * @param cause
  1788    *        SMS_* constant indicating the reason for unsuccessful handling.
  1789    */
  1790   acknowledgeGsmSms: function(success, cause) {
  1791     let Buf = this.context.Buf;
  1792     Buf.newParcel(REQUEST_SMS_ACKNOWLEDGE);
  1793     Buf.writeInt32(2);
  1794     Buf.writeInt32(success ? 1 : 0);
  1795     Buf.writeInt32(cause);
  1796     Buf.sendParcel();
  1797   },
  1799   /**
  1800    * Acknowledge the receipt and handling of an SMS.
  1802    * @param success
  1803    *        Boolean indicating whether the message was successfuly handled.
  1804    */
  1805   ackSMS: function(options) {
  1806     if (options.result == PDU_FCS_RESERVED) {
  1807       return;
  1809     if (this._isCdma) {
  1810       this.acknowledgeCdmaSms(options.result == PDU_FCS_OK, options.result);
  1811     } else {
  1812       this.acknowledgeGsmSms(options.result == PDU_FCS_OK, options.result);
  1814   },
  1816   /**
  1817    * Acknowledge the receipt and handling of a CDMA SMS.
  1819    * @param success
  1820    *        Boolean indicating whether the message was successfuly handled.
  1821    * @param cause
  1822    *        SMS_* constant indicating the reason for unsuccessful handling.
  1823    */
  1824   acknowledgeCdmaSms: function(success, cause) {
  1825     let Buf = this.context.Buf;
  1826     Buf.newParcel(REQUEST_CDMA_SMS_ACKNOWLEDGE);
  1827     Buf.writeInt32(success ? 0 : 1);
  1828     Buf.writeInt32(cause);
  1829     Buf.sendParcel();
  1830   },
  1832   /**
  1833    * Update received MWI into EF_MWIS.
  1834    */
  1835   updateMwis: function(options) {
  1836     if (this.context.ICCUtilsHelper.isICCServiceAvailable("MWIS")) {
  1837       this.context.SimRecordHelper.updateMWIS(options.mwi);
  1839   },
  1841   setCellBroadcastDisabled: function(options) {
  1842     this.cellBroadcastDisabled = options.disabled;
  1844     // If |this.mergedCellBroadcastConfig| is null, either we haven't finished
  1845     // reading required SIM files, or no any channel is ever configured.  In
  1846     // the former case, we'll call |this.updateCellBroadcastConfig()| later
  1847     // with correct configs; in the latter case, we don't bother resetting CB
  1848     // to disabled again.
  1849     if (this.mergedCellBroadcastConfig) {
  1850       this.updateCellBroadcastConfig();
  1852   },
  1854   setCellBroadcastSearchList: function(options) {
  1855     let getSearchListStr = function(aSearchList) {
  1856       if (typeof aSearchList === "string" || aSearchList instanceof String) {
  1857         return aSearchList;
  1860       // TODO: Set search list for CDMA/GSM individually. Bug 990926
  1861       let prop = this._isCdma ? "cdma" : "gsm";
  1863       return aSearchList && aSearchList[prop];
  1864     }.bind(this);
  1866     try {
  1867       let str = getSearchListStr(options.searchList);
  1868       this.cellBroadcastConfigs.MMI = this._convertCellBroadcastSearchList(str);
  1869       options.success = true;
  1870     } catch (e) {
  1871       if (DEBUG) {
  1872         this.context.debug("Invalid Cell Broadcast search list: " + e);
  1874       options.success = false;
  1877     this.sendChromeMessage(options);
  1878     if (!options.success) {
  1879       return;
  1882     this._mergeAllCellBroadcastConfigs();
  1883   },
  1885   updateCellBroadcastConfig: function() {
  1886     let activate = !this.cellBroadcastDisabled &&
  1887                    (this.mergedCellBroadcastConfig != null) &&
  1888                    (this.mergedCellBroadcastConfig.length > 0);
  1889     if (activate) {
  1890       this.setSmsBroadcastConfig(this.mergedCellBroadcastConfig);
  1891     } else {
  1892       // It's unnecessary to set config first if we're deactivating.
  1893       this.setSmsBroadcastActivation(false);
  1895   },
  1897   setGsmSmsBroadcastConfig: function(config) {
  1898     let Buf = this.context.Buf;
  1899     Buf.newParcel(REQUEST_GSM_SET_BROADCAST_SMS_CONFIG);
  1901     let numConfigs = config ? config.length / 2 : 0;
  1902     Buf.writeInt32(numConfigs);
  1903     for (let i = 0; i < config.length;) {
  1904       Buf.writeInt32(config[i++]);
  1905       Buf.writeInt32(config[i++]);
  1906       Buf.writeInt32(0x00);
  1907       Buf.writeInt32(0xFF);
  1908       Buf.writeInt32(1);
  1911     Buf.sendParcel();
  1912   },
  1914   /**
  1915    * Send CDMA SMS broadcast config.
  1917    * @see 3GPP2 C.R1001 Sec. 9.2 and 9.3
  1918    */
  1919   setCdmaSmsBroadcastConfig: function(config) {
  1920     let Buf = this.context.Buf;
  1921     // |config| is an array of half-closed range: [[from, to), [from, to), ...].
  1922     // It will be further decomposed, ex: [1, 4) => 1, 2, 3.
  1923     Buf.newParcel(REQUEST_CDMA_SET_BROADCAST_SMS_CONFIG);
  1925     let numConfigs = 0;
  1926     for (let i = 0; i < config.length; i += 2) {
  1927       numConfigs += (config[i+1] - config[i]);
  1930     Buf.writeInt32(numConfigs);
  1931     for (let i = 0; i < config.length;) {
  1932       let begin = config[i++];
  1933       let end = config[i++];
  1935       for (let j = begin; j < end; ++j) {
  1936         Buf.writeInt32(j);
  1937         Buf.writeInt32(0);  // Language Indicator: Unknown or unspecified.
  1938         Buf.writeInt32(1);
  1942     Buf.sendParcel();
  1943   },
  1945   setSmsBroadcastConfig: function(config) {
  1946     if (this._isCdma) {
  1947       this.setCdmaSmsBroadcastConfig(config);
  1948     } else {
  1949       this.setGsmSmsBroadcastConfig(config);
  1951   },
  1953   setSmsBroadcastActivation: function(activate) {
  1954     let parcelType = this._isCdma ? REQUEST_CDMA_SMS_BROADCAST_ACTIVATION :
  1955                                     REQUEST_GSM_SMS_BROADCAST_ACTIVATION;
  1956     let Buf = this.context.Buf;
  1957     Buf.newParcel(parcelType);
  1958     Buf.writeInt32(1);
  1959     // See hardware/ril/include/telephony/ril.h, 0 - Activate, 1 - Turn off.
  1960     Buf.writeInt32(activate ? 0 : 1);
  1961     Buf.sendParcel();
  1962   },
  1964   /**
  1965    * Start a DTMF Tone.
  1967    * @param dtmfChar
  1968    *        DTMF signal to send, 0-9, *, +
  1969    */
  1970   startTone: function(options) {
  1971     let Buf = this.context.Buf;
  1972     Buf.newParcel(REQUEST_DTMF_START);
  1973     Buf.writeString(options.dtmfChar);
  1974     Buf.sendParcel();
  1975   },
  1977   stopTone: function() {
  1978     this.context.Buf.simpleRequest(REQUEST_DTMF_STOP);
  1979   },
  1981   /**
  1982    * Send a DTMF tone.
  1984    * @param dtmfChar
  1985    *        DTMF signal to send, 0-9, *, +
  1986    */
  1987   sendTone: function(options) {
  1988     let Buf = this.context.Buf;
  1989     Buf.newParcel(REQUEST_DTMF);
  1990     Buf.writeString(options.dtmfChar);
  1991     Buf.sendParcel();
  1992   },
  1994   /**
  1995    * Get the Short Message Service Center address.
  1996    */
  1997   getSmscAddress: function(options) {
  1998     if (!this.SMSC) {
  1999       this.context.Buf.simpleRequest(REQUEST_GET_SMSC_ADDRESS, options);
  2000       return;
  2003     if (!options || options.rilMessageType !== "getSmscAddress") {
  2004       return;
  2007     options.smscAddress = this.SMSC;
  2008     options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError];
  2009     this.sendChromeMessage(options);
  2010   },
  2012   /**
  2013    * Set the Short Message Service Center address.
  2015    * @param smscAddress
  2016    *        Short Message Service Center address in PDU format.
  2017    */
  2018   setSmscAddress: function(options) {
  2019     let Buf = this.context.Buf;
  2020     Buf.newParcel(REQUEST_SET_SMSC_ADDRESS, options);
  2021     Buf.writeString(options.smscAddress);
  2022     Buf.sendParcel();
  2023   },
  2025   /**
  2026    * Setup a data call.
  2028    * @param radioTech
  2029    *        Integer to indicate radio technology.
  2030    *        DATACALL_RADIOTECHNOLOGY_CDMA => CDMA.
  2031    *        DATACALL_RADIOTECHNOLOGY_GSM  => GSM.
  2032    * @param apn
  2033    *        String containing the name of the APN to connect to.
  2034    * @param user
  2035    *        String containing the username for the APN.
  2036    * @param passwd
  2037    *        String containing the password for the APN.
  2038    * @param chappap
  2039    *        Integer containing CHAP/PAP auth type.
  2040    *        DATACALL_AUTH_NONE        => PAP and CHAP is never performed.
  2041    *        DATACALL_AUTH_PAP         => PAP may be performed.
  2042    *        DATACALL_AUTH_CHAP        => CHAP may be performed.
  2043    *        DATACALL_AUTH_PAP_OR_CHAP => PAP / CHAP may be performed.
  2044    * @param pdptype
  2045    *        String containing PDP type to request. ("IP", "IPV6", ...)
  2046    */
  2047   setupDataCall: function(options) {
  2048     // From ./hardware/ril/include/telephony/ril.h:
  2049     // ((const char **)data)[0] Radio technology to use: 0-CDMA, 1-GSM/UMTS, 2...
  2050     // for values above 2 this is RIL_RadioTechnology + 2.
  2051     //
  2052     // From frameworks/base/telephony/java/com/android/internal/telephony/DataConnection.java:
  2053     // if the mRilVersion < 6, radio technology must be GSM/UMTS or CDMA.
  2054     // Otherwise, it must be + 2
  2055     //
  2056     // See also bug 901232 and 867873
  2057     let radioTech;
  2058     if (this.v5Legacy) {
  2059       radioTech = this._isCdma ? DATACALL_RADIOTECHNOLOGY_CDMA
  2060                                : DATACALL_RADIOTECHNOLOGY_GSM;
  2061     } else {
  2062       radioTech = options.radioTech + 2;
  2064     let Buf = this.context.Buf;
  2065     let token = Buf.newParcel(REQUEST_SETUP_DATA_CALL, options);
  2066     Buf.writeInt32(7);
  2067     Buf.writeString(radioTech.toString());
  2068     Buf.writeString(DATACALL_PROFILE_DEFAULT.toString());
  2069     Buf.writeString(options.apn);
  2070     Buf.writeString(options.user);
  2071     Buf.writeString(options.passwd);
  2072     Buf.writeString(options.chappap.toString());
  2073     Buf.writeString(options.pdptype);
  2074     Buf.sendParcel();
  2075     return token;
  2076   },
  2078   /**
  2079    * Deactivate a data call.
  2081    * @param cid
  2082    *        String containing CID.
  2083    * @param reason
  2084    *        One of DATACALL_DEACTIVATE_* constants.
  2085    */
  2086   deactivateDataCall: function(options) {
  2087     let datacall = this.currentDataCalls[options.cid];
  2088     if (!datacall) {
  2089       return;
  2092     let Buf = this.context.Buf;
  2093     Buf.newParcel(REQUEST_DEACTIVATE_DATA_CALL, options);
  2094     Buf.writeInt32(2);
  2095     Buf.writeString(options.cid);
  2096     Buf.writeString(options.reason || DATACALL_DEACTIVATE_NO_REASON);
  2097     Buf.sendParcel();
  2099     datacall.state = GECKO_NETWORK_STATE_DISCONNECTING;
  2100     this.sendChromeMessage(datacall);
  2101   },
  2103   /**
  2104    * Get a list of data calls.
  2105    */
  2106   getDataCallList: function() {
  2107     this.context.Buf.simpleRequest(REQUEST_DATA_CALL_LIST);
  2108   },
  2110   _attachDataRegistration: false,
  2111   /**
  2112    * Manually attach/detach data registration.
  2114    * @param attach
  2115    *        Boolean value indicating attach or detach.
  2116    */
  2117   setDataRegistration: function(options) {
  2118     let request = options.attach ? RIL_REQUEST_GPRS_ATTACH :
  2119                                    RIL_REQUEST_GPRS_DETACH;
  2120     this._attachDataRegistration = options.attach;
  2121     this.context.Buf.simpleRequest(request);
  2122   },
  2124   /**
  2125    * Get failure casue code for the most recently failed PDP context.
  2126    */
  2127   getFailCauseCode: function(callback) {
  2128     this.context.Buf.simpleRequest(REQUEST_LAST_CALL_FAIL_CAUSE,
  2129                                    {callback: callback});
  2130   },
  2132   /**
  2133    * Helper to parse MMI/USSD string. TS.22.030 Figure 3.5.3.2.
  2134    */
  2135   _parseMMI: function(mmiString) {
  2136     if (!mmiString || !mmiString.length) {
  2137       return null;
  2140     let matches = this._matchMMIRegexp(mmiString);
  2141     if (matches) {
  2142       // After successfully executing the regular expresion over the MMI string,
  2143       // the following match groups should contain:
  2144       // 1 = full MMI string that might be used as a USSD request.
  2145       // 2 = MMI procedure.
  2146       // 3 = Service code.
  2147       // 5 = SIA.
  2148       // 7 = SIB.
  2149       // 9 = SIC.
  2150       // 11 = Password registration.
  2151       // 12 = Dialing number.
  2152       return {
  2153         fullMMI: matches[MMI_MATCH_GROUP_FULL_MMI],
  2154         procedure: matches[MMI_MATCH_GROUP_MMI_PROCEDURE],
  2155         serviceCode: matches[MMI_MATCH_GROUP_SERVICE_CODE],
  2156         sia: matches[MMI_MATCH_GROUP_SIA],
  2157         sib: matches[MMI_MATCH_GROUP_SIB],
  2158         sic: matches[MMI_MATCH_GROUP_SIC],
  2159         pwd: matches[MMI_MATCH_GROUP_PWD_CONFIRM],
  2160         dialNumber: matches[MMI_MATCH_GROUP_DIALING_NUMBER]
  2161       };
  2164     if (this._isPoundString(mmiString) ||
  2165         this._isMMIShortString(mmiString)) {
  2166       return {
  2167         fullMMI: mmiString
  2168       };
  2171     return null;
  2172   },
  2174   /**
  2175    * Helper to parse MMI string via regular expression. TS.22.030 Figure
  2176    * 3.5.3.2.
  2177    */
  2178   _matchMMIRegexp: function(mmiString) {
  2179     // Regexp to parse and process the MMI code.
  2180     if (this._mmiRegExp == null) {
  2181       // The first group of the regexp takes the whole MMI string.
  2182       // The second group takes the MMI procedure that can be:
  2183       //    - Activation (*SC*SI#).
  2184       //    - Deactivation (#SC*SI#).
  2185       //    - Interrogation (*#SC*SI#).
  2186       //    - Registration (**SC*SI#).
  2187       //    - Erasure (##SC*SI#).
  2188       //  where SC = Service Code (2 or 3 digits) and SI = Supplementary Info
  2189       //  (variable length).
  2190       let pattern = "((\\*[*#]?|##?)";
  2192       // Third group of the regexp looks for the MMI Service code, which is a
  2193       // 2 or 3 digits that uniquely specifies the Supplementary Service
  2194       // associated with the MMI code.
  2195       pattern += "(\\d{2,3})";
  2197       // Groups from 4 to 9 looks for the MMI Supplementary Information SIA,
  2198       // SIB and SIC. SIA may comprise e.g. a PIN code or Directory Number,
  2199       // SIB may be used to specify the tele or bearer service and SIC to
  2200       // specify the value of the "No Reply Condition Timer". Where a particular
  2201       // service request does not require any SI, "*SI" is not entered. The use
  2202       // of SIA, SIB and SIC is optional and shall be entered in any of the
  2203       // following formats:
  2204       //    - *SIA*SIB*SIC#
  2205       //    - *SIA*SIB#
  2206       //    - *SIA**SIC#
  2207       //    - *SIA#
  2208       //    - **SIB*SIC#
  2209       //    - ***SISC#
  2210       pattern += "(\\*([^*#]*)(\\*([^*#]*)(\\*([^*#]*)";
  2212       // The eleventh group takes the password for the case of a password
  2213       // registration procedure.
  2214       pattern += "(\\*([^*#]*))?)?)?)?#)";
  2216       // The last group takes the dial string after the #.
  2217       pattern += "([^#]*)";
  2219       this._mmiRegExp = new RegExp(pattern);
  2222     // Regex only applys for those well-defined MMI strings (refer to TS.22.030
  2223     // Annex B), otherwise, null should be the expected return value.
  2224     return this._mmiRegExp.exec(mmiString);
  2225   },
  2227   /**
  2228    * Helper to parse # string. TS.22.030 Figure 3.5.3.2.
  2229    */
  2230   _isPoundString: function(mmiString) {
  2231     return (mmiString.charAt(mmiString.length - 1) === MMI_END_OF_USSD);
  2232   },
  2234   /**
  2235    * Helper to parse short string. TS.22.030 Figure 3.5.3.2.
  2236    */
  2237   _isMMIShortString: function(mmiString) {
  2238     if (mmiString.length > 2) {
  2239       return false;
  2242     if (this._isEmergencyNumber(mmiString)) {
  2243       return false;
  2246     // In a call case.
  2247     if (Object.getOwnPropertyNames(this.currentCalls).length > 0) {
  2248       return true;
  2251     if ((mmiString.length != 2) || (mmiString.charAt(0) !== '1')) {
  2252       return true;
  2255     return false;
  2256   },
  2258   sendMMI: function(options) {
  2259     if (DEBUG) {
  2260       this.context.debug("SendMMI " + JSON.stringify(options));
  2262     let mmiString = options.mmi;
  2263     let mmi = this._parseMMI(mmiString);
  2265     let _sendMMIError = (function(errorMsg, mmiServiceCode) {
  2266       options.success = false;
  2267       options.errorMsg = errorMsg;
  2268       if (mmiServiceCode) {
  2269         options.mmiServiceCode = mmiServiceCode;
  2271       this.sendChromeMessage(options);
  2272     }).bind(this);
  2274     function _isValidPINPUKRequest(mmiServiceCode) {
  2275       // The only allowed MMI procedure for ICC PIN, PIN2, PUK and PUK2 handling
  2276       // is "Registration" (**).
  2277       if (!mmi.procedure || mmi.procedure != MMI_PROCEDURE_REGISTRATION ) {
  2278         _sendMMIError(MMI_ERROR_KS_INVALID_ACTION, mmiServiceCode);
  2279         return false;
  2282       if (!mmi.sia || !mmi.sia.length || !mmi.sib || !mmi.sib.length ||
  2283           !mmi.sic || !mmi.sic.length) {
  2284         _sendMMIError(MMI_ERROR_KS_ERROR, mmiServiceCode);
  2285         return false;
  2288       if (mmi.sib != mmi.sic) {
  2289         _sendMMIError(MMI_ERROR_KS_MISMATCH_PIN, mmiServiceCode);
  2290         return false;
  2293       if (mmi.sia.length < 4 || mmi.sia.length > 8 ||
  2294           mmi.sib.length < 4 || mmi.sib.length > 8 ||
  2295           mmi.sic.length < 4 || mmi.sic.length > 8) {
  2296         _sendMMIError(MMI_ERROR_KS_INVALID_PIN, mmiServiceCode);
  2297         return false;
  2300       return true;
  2303     let _isRadioAvailable = (function(mmiServiceCode) {
  2304       if (this.radioState !== GECKO_RADIOSTATE_READY) {
  2305         _sendMMIError(GECKO_ERROR_RADIO_NOT_AVAILABLE, mmiServiceCode);
  2306         return false;
  2308       return true;
  2309     }).bind(this);
  2311     // If we couldn't parse the MMI code, we'll send it as an USSD request.
  2312     if (mmi === null) {
  2313       if (this._ussdSession) {
  2314         if (!_isRadioAvailable(MMI_KS_SC_USSD)) {
  2315           return;
  2317         options.ussd = mmiString;
  2318         this.sendUSSD(options);
  2319         return;
  2322       _sendMMIError(MMI_ERROR_KS_ERROR);
  2323       return;
  2326     if (DEBUG) {
  2327       this.context.debug("MMI " + JSON.stringify(mmi));
  2330     // We check if the MMI service code is supported and in that case we
  2331     // trigger the appropriate RIL request if possible.
  2332     let sc = mmi.serviceCode;
  2333     switch (sc) {
  2334       // Call forwarding
  2335       case MMI_SC_CFU:
  2336       case MMI_SC_CF_BUSY:
  2337       case MMI_SC_CF_NO_REPLY:
  2338       case MMI_SC_CF_NOT_REACHABLE:
  2339       case MMI_SC_CF_ALL:
  2340       case MMI_SC_CF_ALL_CONDITIONAL:
  2341         if (!_isRadioAvailable(MMI_KS_SC_CALL_FORWARDING)) {
  2342           return;
  2344         // Call forwarding requires at least an action, given by the MMI
  2345         // procedure, and a reason, given by the MMI service code, but there
  2346         // is no way that we get this far without a valid procedure or service
  2347         // code.
  2348         options.mmiServiceCode = MMI_KS_SC_CALL_FORWARDING;
  2349         options.action = MMI_PROC_TO_CF_ACTION[mmi.procedure];
  2350         options.reason = MMI_SC_TO_CF_REASON[sc];
  2351         options.number = mmi.sia;
  2352         options.serviceClass = this._siToServiceClass(mmi.sib);
  2353         if (options.action == CALL_FORWARD_ACTION_QUERY_STATUS) {
  2354           this.queryCallForwardStatus(options);
  2355           return;
  2358         options.isSetCallForward = true;
  2359         options.timeSeconds = mmi.sic;
  2360         this.setCallForward(options);
  2361         return;
  2363       // Change the current ICC PIN number.
  2364       case MMI_SC_PIN:
  2365         // As defined in TS.122.030 6.6.2 to change the ICC PIN we should expect
  2366         // an MMI code of the form **04*OLD_PIN*NEW_PIN*NEW_PIN#, where old PIN
  2367         // should be entered as the SIA parameter and the new PIN as SIB and
  2368         // SIC.
  2369         if (!_isRadioAvailable(MMI_KS_SC_PIN) ||
  2370             !_isValidPINPUKRequest(MMI_KS_SC_PIN)) {
  2371           return;
  2374         options.mmiServiceCode = MMI_KS_SC_PIN;
  2375         options.pin = mmi.sia;
  2376         options.newPin = mmi.sib;
  2377         this.changeICCPIN(options);
  2378         return;
  2380       // Change the current ICC PIN2 number.
  2381       case MMI_SC_PIN2:
  2382         // As defined in TS.122.030 6.6.2 to change the ICC PIN2 we should
  2383         // enter and MMI code of the form **042*OLD_PIN2*NEW_PIN2*NEW_PIN2#,
  2384         // where the old PIN2 should be entered as the SIA parameter and the
  2385         // new PIN2 as SIB and SIC.
  2386         if (!_isRadioAvailable(MMI_KS_SC_PIN2) ||
  2387             !_isValidPINPUKRequest(MMI_KS_SC_PIN2)) {
  2388           return;
  2391         options.mmiServiceCode = MMI_KS_SC_PIN2;
  2392         options.pin = mmi.sia;
  2393         options.newPin = mmi.sib;
  2394         this.changeICCPIN2(options);
  2395         return;
  2397       // Unblock ICC PIN.
  2398       case MMI_SC_PUK:
  2399         // As defined in TS.122.030 6.6.3 to unblock the ICC PIN we should
  2400         // enter an MMI code of the form **05*PUK*NEW_PIN*NEW_PIN#, where PUK
  2401         // should be entered as the SIA parameter and the new PIN as SIB and
  2402         // SIC.
  2403         if (!_isRadioAvailable(MMI_KS_SC_PUK) ||
  2404             !_isValidPINPUKRequest(MMI_KS_SC_PUK)) {
  2405           return;
  2408         options.mmiServiceCode = MMI_KS_SC_PUK;
  2409         options.puk = mmi.sia;
  2410         options.newPin = mmi.sib;
  2411         this.enterICCPUK(options);
  2412         return;
  2414       // Unblock ICC PIN2.
  2415       case MMI_SC_PUK2:
  2416         // As defined in TS.122.030 6.6.3 to unblock the ICC PIN2 we should
  2417         // enter an MMI code of the form **052*PUK2*NEW_PIN2*NEW_PIN2#, where
  2418         // PUK2 should be entered as the SIA parameter and the new PIN2 as SIB
  2419         // and SIC.
  2420         if (!_isRadioAvailable(MMI_KS_SC_PUK2) ||
  2421             !_isValidPINPUKRequest(MMI_KS_SC_PUK2)) {
  2422           return;
  2425         options.mmiServiceCode = MMI_KS_SC_PUK2;
  2426         options.puk = mmi.sia;
  2427         options.newPin = mmi.sib;
  2428         this.enterICCPUK2(options);
  2429         return;
  2431       // IMEI
  2432       case MMI_SC_IMEI:
  2433         // A device's IMEI can't change, so we only need to request it once.
  2434         if (this.IMEI == null) {
  2435           this.getIMEI(options);
  2436           return;
  2438         // If we already had the device's IMEI, we just send it to chrome.
  2439         options.mmiServiceCode = MMI_KS_SC_IMEI;
  2440         options.success = true;
  2441         options.statusMessage = this.IMEI;
  2442         this.sendChromeMessage(options);
  2443         return;
  2445       // CLIP
  2446       case MMI_SC_CLIP:
  2447         options.mmiServiceCode = MMI_KS_SC_CLIP;
  2448         options.procedure = mmi.procedure;
  2449         if (options.procedure === MMI_PROCEDURE_INTERROGATION) {
  2450           this.queryCLIP(options);
  2451         } else {
  2452           _sendMMIError(MMI_ERROR_KS_NOT_SUPPORTED, MMI_KS_SC_CLIP);
  2454         return;
  2456       // CLIR (non-temporary ones)
  2457       // TODO: Both dial() and sendMMI() functions should be unified at some
  2458       // point in the future. In the mean time we handle temporary CLIR MMI
  2459       // commands through the dial() function. Please see bug 889737.
  2460       case MMI_SC_CLIR:
  2461         options.mmiServiceCode = MMI_KS_SC_CLIR;
  2462         options.procedure = mmi.procedure;
  2463         switch (options.procedure) {
  2464           case MMI_PROCEDURE_INTERROGATION:
  2465             this.getCLIR(options);
  2466             return;
  2467           case MMI_PROCEDURE_ACTIVATION:
  2468             options.clirMode = CLIR_INVOCATION;
  2469             break;
  2470           case MMI_PROCEDURE_DEACTIVATION:
  2471             options.clirMode = CLIR_SUPPRESSION;
  2472             break;
  2473           default:
  2474             _sendMMIError(MMI_ERROR_KS_NOT_SUPPORTED, MMI_KS_SC_CLIR);
  2475             return;
  2477         options.isSetCLIR = true;
  2478         this.setCLIR(options);
  2479         return;
  2481       // Call barring
  2482       case MMI_SC_BAOC:
  2483       case MMI_SC_BAOIC:
  2484       case MMI_SC_BAOICxH:
  2485       case MMI_SC_BAIC:
  2486       case MMI_SC_BAICr:
  2487       case MMI_SC_BA_ALL:
  2488       case MMI_SC_BA_MO:
  2489       case MMI_SC_BA_MT:
  2490         options.mmiServiceCode = MMI_KS_SC_CALL_BARRING;
  2491         options.password = mmi.sia || "";
  2492         options.serviceClass = this._siToServiceClass(mmi.sib);
  2493         options.facility = MMI_SC_TO_CB_FACILITY[sc];
  2494         options.procedure = mmi.procedure;
  2495         if (mmi.procedure === MMI_PROCEDURE_INTERROGATION) {
  2496           this.queryICCFacilityLock(options);
  2497           return;
  2499         if (mmi.procedure === MMI_PROCEDURE_ACTIVATION) {
  2500           options.enabled = 1;
  2501         } else if (mmi.procedure === MMI_PROCEDURE_DEACTIVATION) {
  2502           options.enabled = 0;
  2503         } else {
  2504           _sendMMIError(MMI_ERROR_KS_NOT_SUPPORTED, MMI_KS_SC_CALL_BARRING);
  2505           return;
  2507         this.setICCFacilityLock(options);
  2508         return;
  2510       // Call waiting
  2511       case MMI_SC_CALL_WAITING:
  2512         if (!_isRadioAvailable(MMI_KS_SC_CALL_WAITING)) {
  2513           return;
  2516         options.mmiServiceCode = MMI_KS_SC_CALL_WAITING;
  2518         if (mmi.procedure === MMI_PROCEDURE_INTERROGATION) {
  2519           this._handleQueryMMICallWaiting(options);
  2520           return;
  2523         if (mmi.procedure === MMI_PROCEDURE_ACTIVATION) {
  2524           options.enabled = true;
  2525         } else if (mmi.procedure === MMI_PROCEDURE_DEACTIVATION) {
  2526           options.enabled = false;
  2527         } else {
  2528           _sendMMIError(MMI_ERROR_KS_NOT_SUPPORTED, MMI_KS_SC_CALL_WAITING);
  2529           return;
  2532         options.serviceClass = this._siToServiceClass(mmi.sia);
  2533         this._handleSetMMICallWaiting(options);
  2534         return;
  2537     // If the MMI code is not a known code and is a recognized USSD request,
  2538     // it shall still be sent as a USSD request.
  2539     if (mmi.fullMMI) {
  2540       if (!_isRadioAvailable(MMI_KS_SC_USSD)) {
  2541         return;
  2544       options.ussd = mmi.fullMMI;
  2545       options.mmiServiceCode = MMI_KS_SC_USSD;
  2546       this.sendUSSD(options);
  2547       return;
  2550     // At this point, the MMI string is considered as not valid MMI code and
  2551     // not valid USSD code.
  2552     _sendMMIError(MMI_ERROR_KS_ERROR);
  2553   },
  2555   /**
  2556    * Send USSD.
  2558    * @param ussd
  2559    *        String containing the USSD code.
  2561    */
  2562    sendUSSD: function(options) {
  2563      let Buf = this.context.Buf;
  2564      Buf.newParcel(REQUEST_SEND_USSD, options);
  2565      Buf.writeString(options.ussd);
  2566      Buf.sendParcel();
  2567    },
  2569   /**
  2570    * Cancel pending USSD.
  2571    */
  2572    cancelUSSD: function(options) {
  2573      options.mmiServiceCode = MMI_KS_SC_USSD;
  2574      this.context.Buf.simpleRequest(REQUEST_CANCEL_USSD, options);
  2575    },
  2577   /**
  2578    * Queries current call forward rules.
  2580    * @param reason
  2581    *        One of nsIDOMMozMobileCFInfo.CALL_FORWARD_REASON_* constants.
  2582    * @param serviceClass
  2583    *        One of ICC_SERVICE_CLASS_* constants.
  2584    * @param number
  2585    *        Phone number of forwarding address.
  2586    */
  2587   queryCallForwardStatus: function(options) {
  2588     let Buf = this.context.Buf;
  2589     let number = options.number || "";
  2590     Buf.newParcel(REQUEST_QUERY_CALL_FORWARD_STATUS, options);
  2591     Buf.writeInt32(CALL_FORWARD_ACTION_QUERY_STATUS);
  2592     Buf.writeInt32(options.reason);
  2593     Buf.writeInt32(options.serviceClass || ICC_SERVICE_CLASS_NONE);
  2594     Buf.writeInt32(this._toaFromString(number));
  2595     Buf.writeString(number);
  2596     Buf.writeInt32(0);
  2597     Buf.sendParcel();
  2598   },
  2600   /**
  2601    * Configures call forward rule.
  2603    * @param action
  2604    *        One of nsIDOMMozMobileCFInfo.CALL_FORWARD_ACTION_* constants.
  2605    * @param reason
  2606    *        One of nsIDOMMozMobileCFInfo.CALL_FORWARD_REASON_* constants.
  2607    * @param serviceClass
  2608    *        One of ICC_SERVICE_CLASS_* constants.
  2609    * @param number
  2610    *        Phone number of forwarding address.
  2611    * @param timeSeconds
  2612    *        Time in seconds to wait beforec all is forwarded.
  2613    */
  2614   setCallForward: function(options) {
  2615     let Buf = this.context.Buf;
  2616     Buf.newParcel(REQUEST_SET_CALL_FORWARD, options);
  2617     Buf.writeInt32(options.action);
  2618     Buf.writeInt32(options.reason);
  2619     Buf.writeInt32(options.serviceClass);
  2620     Buf.writeInt32(this._toaFromString(options.number));
  2621     Buf.writeString(options.number);
  2622     Buf.writeInt32(options.timeSeconds);
  2623     Buf.sendParcel();
  2624   },
  2626   /**
  2627    * Queries current call barring rules.
  2629    * @param program
  2630    *        One of nsIDOMMozMobileConnection.CALL_BARRING_PROGRAM_* constants.
  2631    * @param serviceClass
  2632    *        One of ICC_SERVICE_CLASS_* constants.
  2633    */
  2634   queryCallBarringStatus: function(options) {
  2635     options.facility = CALL_BARRING_PROGRAM_TO_FACILITY[options.program];
  2636     options.password = ""; // For query no need to provide it.
  2637     this.queryICCFacilityLock(options);
  2638   },
  2640   /**
  2641    * Configures call barring rule.
  2643    * @param program
  2644    *        One of nsIDOMMozMobileConnection.CALL_BARRING_PROGRAM_* constants.
  2645    * @param enabled
  2646    *        Enable or disable the call barring.
  2647    * @param password
  2648    *        Barring password.
  2649    * @param serviceClass
  2650    *        One of ICC_SERVICE_CLASS_* constants.
  2651    */
  2652   setCallBarring: function(options) {
  2653     options.facility = CALL_BARRING_PROGRAM_TO_FACILITY[options.program];
  2654     this.setICCFacilityLock(options);
  2655   },
  2657   /**
  2658    * Change call barring facility password.
  2660    * @param pin
  2661    *        Old password.
  2662    * @param newPin
  2663    *        New password.
  2664    */
  2665   changeCallBarringPassword: function(options) {
  2666     let Buf = this.context.Buf;
  2667     Buf.newParcel(REQUEST_CHANGE_BARRING_PASSWORD, options);
  2668     Buf.writeInt32(3);
  2669     // Set facility to ICC_CB_FACILITY_BA_ALL by following TS.22.030 clause
  2670     // 6.5.4 and Table B.1.
  2671     Buf.writeString(ICC_CB_FACILITY_BA_ALL);
  2672     Buf.writeString(options.pin);
  2673     Buf.writeString(options.newPin);
  2674     Buf.sendParcel();
  2675   },
  2677   /**
  2678    * Handle STK CALL_SET_UP request.
  2680    * @param hasConfirmed
  2681    *        Does use have confirmed the call requested from ICC?
  2682    */
  2683   stkHandleCallSetup: function(options) {
  2684      let Buf = this.context.Buf;
  2685      Buf.newParcel(REQUEST_STK_HANDLE_CALL_SETUP_REQUESTED_FROM_SIM);
  2686      Buf.writeInt32(1);
  2687      Buf.writeInt32(options.hasConfirmed ? 1 : 0);
  2688      Buf.sendParcel();
  2689   },
  2691   /**
  2692    * Send STK Profile Download.
  2694    * @param profile Profile supported by ME.
  2695    */
  2696   sendStkTerminalProfile: function(profile) {
  2697     let Buf = this.context.Buf;
  2698     let GsmPDUHelper = this.context.GsmPDUHelper;
  2700     Buf.newParcel(REQUEST_STK_SET_PROFILE);
  2701     Buf.writeInt32(profile.length * 2);
  2702     for (let i = 0; i < profile.length; i++) {
  2703       GsmPDUHelper.writeHexOctet(profile[i]);
  2705     Buf.writeInt32(0);
  2706     Buf.sendParcel();
  2707   },
  2709   /**
  2710    * Send STK terminal response.
  2712    * @param command
  2713    * @param deviceIdentities
  2714    * @param resultCode
  2715    * @param [optional] itemIdentifier
  2716    * @param [optional] input
  2717    * @param [optional] isYesNo
  2718    * @param [optional] hasConfirmed
  2719    * @param [optional] localInfo
  2720    * @param [optional] timer
  2721    */
  2722   sendStkTerminalResponse: function(response) {
  2723     if (response.hasConfirmed !== undefined) {
  2724       this.stkHandleCallSetup(response);
  2725       return;
  2728     let Buf = this.context.Buf;
  2729     let ComprehensionTlvHelper = this.context.ComprehensionTlvHelper;
  2730     let GsmPDUHelper = this.context.GsmPDUHelper;
  2732     let command = response.command;
  2733     Buf.newParcel(REQUEST_STK_SEND_TERMINAL_RESPONSE);
  2735     // 1st mark for Parcel size
  2736     Buf.startCalOutgoingSize(function(size) {
  2737       // Parcel size is in string length, which costs 2 uint8 per char.
  2738       Buf.writeInt32(size / 2);
  2739     });
  2741     // Command Details
  2742     GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_COMMAND_DETAILS |
  2743                                COMPREHENSIONTLV_FLAG_CR);
  2744     GsmPDUHelper.writeHexOctet(3);
  2745     if (response.command) {
  2746       GsmPDUHelper.writeHexOctet(command.commandNumber);
  2747       GsmPDUHelper.writeHexOctet(command.typeOfCommand);
  2748       GsmPDUHelper.writeHexOctet(command.commandQualifier);
  2749     } else {
  2750       GsmPDUHelper.writeHexOctet(0x00);
  2751       GsmPDUHelper.writeHexOctet(0x00);
  2752       GsmPDUHelper.writeHexOctet(0x00);
  2755     // Device Identifier
  2756     // According to TS102.223/TS31.111 section 6.8 Structure of
  2757     // TERMINAL RESPONSE, "For all SIMPLE-TLV objects with Min=N,
  2758     // the ME should set the CR(comprehension required) flag to
  2759     // comprehension not required.(CR=0)"
  2760     // Since DEVICE_IDENTITIES and DURATION TLVs have Min=N,
  2761     // the CR flag is not set.
  2762     GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_DEVICE_ID);
  2763     GsmPDUHelper.writeHexOctet(2);
  2764     GsmPDUHelper.writeHexOctet(STK_DEVICE_ID_ME);
  2765     GsmPDUHelper.writeHexOctet(STK_DEVICE_ID_SIM);
  2767     // Result
  2768     GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_RESULT |
  2769                                COMPREHENSIONTLV_FLAG_CR);
  2770     GsmPDUHelper.writeHexOctet(1);
  2771     GsmPDUHelper.writeHexOctet(response.resultCode);
  2773     // Item Identifier
  2774     if (response.itemIdentifier != null) {
  2775       GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_ITEM_ID |
  2776                                  COMPREHENSIONTLV_FLAG_CR);
  2777       GsmPDUHelper.writeHexOctet(1);
  2778       GsmPDUHelper.writeHexOctet(response.itemIdentifier);
  2781     // No need to process Text data if user requests help information.
  2782     if (response.resultCode != STK_RESULT_HELP_INFO_REQUIRED) {
  2783       let text;
  2784       if (response.isYesNo !== undefined) {
  2785         // GET_INKEY
  2786         // When the ME issues a successful TERMINAL RESPONSE for a GET INKEY
  2787         // ("Yes/No") command with command qualifier set to "Yes/No", it shall
  2788         // supply the value '01' when the answer is "positive" and the value
  2789         // '00' when the answer is "negative" in the Text string data object.
  2790         text = response.isYesNo ? 0x01 : 0x00;
  2791       } else {
  2792         text = response.input;
  2795       if (text) {
  2796         GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_TEXT_STRING |
  2797                                    COMPREHENSIONTLV_FLAG_CR);
  2799         // 2nd mark for text length
  2800         Buf.startCalOutgoingSize(function(size) {
  2801           // Text length is in number of hexOctets, which costs 4 uint8 per hexOctet.
  2802           GsmPDUHelper.writeHexOctet(size / 4);
  2803         });
  2805         let coding = command.options.isUCS2 ?
  2806                        STK_TEXT_CODING_UCS2 :
  2807                        (command.options.isPacked ?
  2808                           STK_TEXT_CODING_GSM_7BIT_PACKED :
  2809                           STK_TEXT_CODING_GSM_8BIT);
  2810         GsmPDUHelper.writeHexOctet(coding);
  2812         // Write Text String.
  2813         switch (coding) {
  2814           case STK_TEXT_CODING_UCS2:
  2815             GsmPDUHelper.writeUCS2String(text);
  2816             break;
  2817           case STK_TEXT_CODING_GSM_7BIT_PACKED:
  2818             GsmPDUHelper.writeStringAsSeptets(text, 0, 0, 0);
  2819             break;
  2820           case STK_TEXT_CODING_GSM_8BIT:
  2821             for (let i = 0; i < text.length; i++) {
  2822               GsmPDUHelper.writeHexOctet(text.charCodeAt(i));
  2824             break;
  2827         // Calculate and write text length to 2nd mark
  2828         Buf.stopCalOutgoingSize();
  2832     // Local Information
  2833     if (response.localInfo) {
  2834       let localInfo = response.localInfo;
  2836       // Location Infomation
  2837       if (localInfo.locationInfo) {
  2838         ComprehensionTlvHelper.writeLocationInfoTlv(localInfo.locationInfo);
  2841       // IMEI
  2842       if (localInfo.imei != null) {
  2843         let imei = localInfo.imei;
  2844         if (imei.length == 15) {
  2845           imei = imei + "0";
  2848         GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_IMEI);
  2849         GsmPDUHelper.writeHexOctet(8);
  2850         for (let i = 0; i < imei.length / 2; i++) {
  2851           GsmPDUHelper.writeHexOctet(parseInt(imei.substr(i * 2, 2), 16));
  2855       // Date and Time Zone
  2856       if (localInfo.date != null) {
  2857         ComprehensionTlvHelper.writeDateTimeZoneTlv(localInfo.date);
  2860       // Language
  2861       if (localInfo.language) {
  2862         ComprehensionTlvHelper.writeLanguageTlv(localInfo.language);
  2866     // Timer
  2867     if (response.timer) {
  2868       let timer = response.timer;
  2870       if (timer.timerId) {
  2871         GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_TIMER_IDENTIFIER);
  2872         GsmPDUHelper.writeHexOctet(1);
  2873         GsmPDUHelper.writeHexOctet(timer.timerId);
  2876       if (timer.timerValue) {
  2877         ComprehensionTlvHelper.writeTimerValueTlv(timer.timerValue, false);
  2881     // Calculate and write Parcel size to 1st mark
  2882     Buf.stopCalOutgoingSize();
  2884     Buf.writeInt32(0);
  2885     Buf.sendParcel();
  2886   },
  2888   /**
  2889    * Send STK Envelope(Menu Selection) command.
  2891    * @param itemIdentifier
  2892    * @param helpRequested
  2893    */
  2894   sendStkMenuSelection: function(command) {
  2895     command.tag = BER_MENU_SELECTION_TAG;
  2896     command.deviceId = {
  2897       sourceId :STK_DEVICE_ID_KEYPAD,
  2898       destinationId: STK_DEVICE_ID_SIM
  2899     };
  2900     this.sendICCEnvelopeCommand(command);
  2901   },
  2903   /**
  2904    * Send STK Envelope(Timer Expiration) command.
  2906    * @param timer
  2907    */
  2908   sendStkTimerExpiration: function(command) {
  2909     command.tag = BER_TIMER_EXPIRATION_TAG;
  2910     command.deviceId = {
  2911       sourceId: STK_DEVICE_ID_ME,
  2912       destinationId: STK_DEVICE_ID_SIM
  2913     };
  2914     command.timerId = command.timer.timerId;
  2915     command.timerValue = command.timer.timerValue;
  2916     this.sendICCEnvelopeCommand(command);
  2917   },
  2919   /**
  2920    * Send STK Envelope(Event Download) command.
  2921    * @param event
  2922    */
  2923   sendStkEventDownload: function(command) {
  2924     command.tag = BER_EVENT_DOWNLOAD_TAG;
  2925     command.eventList = command.event.eventType;
  2926     switch (command.eventList) {
  2927       case STK_EVENT_TYPE_LOCATION_STATUS:
  2928         command.deviceId = {
  2929           sourceId :STK_DEVICE_ID_ME,
  2930           destinationId: STK_DEVICE_ID_SIM
  2931         };
  2932         command.locationStatus = command.event.locationStatus;
  2933         // Location info should only be provided when locationStatus is normal.
  2934         if (command.locationStatus == STK_SERVICE_STATE_NORMAL) {
  2935           command.locationInfo = command.event.locationInfo;
  2937         break;
  2938       case STK_EVENT_TYPE_MT_CALL:
  2939         command.deviceId = {
  2940           sourceId: STK_DEVICE_ID_NETWORK,
  2941           destinationId: STK_DEVICE_ID_SIM
  2942         };
  2943         command.transactionId = 0;
  2944         command.address = command.event.number;
  2945         break;
  2946       case STK_EVENT_TYPE_CALL_DISCONNECTED:
  2947         command.cause = command.event.error;
  2948         // Fall through.
  2949       case STK_EVENT_TYPE_CALL_CONNECTED:
  2950         command.deviceId = {
  2951           sourceId: (command.event.isIssuedByRemote ?
  2952                      STK_DEVICE_ID_NETWORK : STK_DEVICE_ID_ME),
  2953           destinationId: STK_DEVICE_ID_SIM
  2954         };
  2955         command.transactionId = 0;
  2956         break;
  2957       case STK_EVENT_TYPE_USER_ACTIVITY:
  2958         command.deviceId = {
  2959           sourceId: STK_DEVICE_ID_ME,
  2960           destinationId: STK_DEVICE_ID_SIM
  2961         };
  2962         break;
  2963       case STK_EVENT_TYPE_IDLE_SCREEN_AVAILABLE:
  2964         command.deviceId = {
  2965           sourceId: STK_DEVICE_ID_DISPLAY,
  2966           destinationId: STK_DEVICE_ID_SIM
  2967         };
  2968         break;
  2969       case STK_EVENT_TYPE_LANGUAGE_SELECTION:
  2970         command.deviceId = {
  2971           sourceId: STK_DEVICE_ID_ME,
  2972           destinationId: STK_DEVICE_ID_SIM
  2973         };
  2974         command.language = command.event.language;
  2975         break;
  2976       case STK_EVENT_TYPE_BROWSER_TERMINATION:
  2977         command.deviceId = {
  2978           sourceId: STK_DEVICE_ID_ME,
  2979           destinationId: STK_DEVICE_ID_SIM
  2980         };
  2981         command.terminationCause = command.event.terminationCause;
  2982         break;
  2984     this.sendICCEnvelopeCommand(command);
  2985   },
  2987   /**
  2988    * Send REQUEST_STK_SEND_ENVELOPE_COMMAND to ICC.
  2990    * @param tag
  2991    * @patam deviceId
  2992    * @param [optioanl] itemIdentifier
  2993    * @param [optional] helpRequested
  2994    * @param [optional] eventList
  2995    * @param [optional] locationStatus
  2996    * @param [optional] locationInfo
  2997    * @param [optional] address
  2998    * @param [optional] transactionId
  2999    * @param [optional] cause
  3000    * @param [optional] timerId
  3001    * @param [optional] timerValue
  3002    * @param [optional] terminationCause
  3003    */
  3004   sendICCEnvelopeCommand: function(options) {
  3005     if (DEBUG) {
  3006       this.context.debug("Stk Envelope " + JSON.stringify(options));
  3009     let Buf = this.context.Buf;
  3010     let ComprehensionTlvHelper = this.context.ComprehensionTlvHelper;
  3011     let GsmPDUHelper = this.context.GsmPDUHelper;
  3013     Buf.newParcel(REQUEST_STK_SEND_ENVELOPE_COMMAND);
  3015     // 1st mark for Parcel size
  3016     Buf.startCalOutgoingSize(function(size) {
  3017       // Parcel size is in string length, which costs 2 uint8 per char.
  3018       Buf.writeInt32(size / 2);
  3019     });
  3021     // Write a BER-TLV
  3022     GsmPDUHelper.writeHexOctet(options.tag);
  3023     // 2nd mark for BER length
  3024     Buf.startCalOutgoingSize(function(size) {
  3025       // BER length is in number of hexOctets, which costs 4 uint8 per hexOctet.
  3026       GsmPDUHelper.writeHexOctet(size / 4);
  3027     });
  3029     // Device Identifies
  3030     GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_DEVICE_ID |
  3031                                COMPREHENSIONTLV_FLAG_CR);
  3032     GsmPDUHelper.writeHexOctet(2);
  3033     GsmPDUHelper.writeHexOctet(options.deviceId.sourceId);
  3034     GsmPDUHelper.writeHexOctet(options.deviceId.destinationId);
  3036     // Item Identifier
  3037     if (options.itemIdentifier != null) {
  3038       GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_ITEM_ID |
  3039                                  COMPREHENSIONTLV_FLAG_CR);
  3040       GsmPDUHelper.writeHexOctet(1);
  3041       GsmPDUHelper.writeHexOctet(options.itemIdentifier);
  3044     // Help Request
  3045     if (options.helpRequested) {
  3046       GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_HELP_REQUEST |
  3047                                  COMPREHENSIONTLV_FLAG_CR);
  3048       GsmPDUHelper.writeHexOctet(0);
  3049       // Help Request doesn't have value
  3052     // Event List
  3053     if (options.eventList != null) {
  3054       GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_EVENT_LIST |
  3055                                  COMPREHENSIONTLV_FLAG_CR);
  3056       GsmPDUHelper.writeHexOctet(1);
  3057       GsmPDUHelper.writeHexOctet(options.eventList);
  3060     // Location Status
  3061     if (options.locationStatus != null) {
  3062       let len = options.locationStatus.length;
  3063       GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_LOCATION_STATUS |
  3064                                  COMPREHENSIONTLV_FLAG_CR);
  3065       GsmPDUHelper.writeHexOctet(1);
  3066       GsmPDUHelper.writeHexOctet(options.locationStatus);
  3069     // Location Info
  3070     if (options.locationInfo) {
  3071       ComprehensionTlvHelper.writeLocationInfoTlv(options.locationInfo);
  3074     // Transaction Id
  3075     if (options.transactionId != null) {
  3076       GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_TRANSACTION_ID |
  3077                                  COMPREHENSIONTLV_FLAG_CR);
  3078       GsmPDUHelper.writeHexOctet(1);
  3079       GsmPDUHelper.writeHexOctet(options.transactionId);
  3082     // Address
  3083     if (options.address) {
  3084       GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_ADDRESS |
  3085                                  COMPREHENSIONTLV_FLAG_CR);
  3086       ComprehensionTlvHelper.writeLength(
  3087         Math.ceil(options.address.length/2) + 1 // address BCD + TON
  3088       );
  3089       this.context.ICCPDUHelper.writeDiallingNumber(options.address);
  3092     // Cause of disconnection.
  3093     if (options.cause != null) {
  3094       ComprehensionTlvHelper.writeCauseTlv(options.cause);
  3097     // Timer Identifier
  3098     if (options.timerId != null) {
  3099         GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_TIMER_IDENTIFIER |
  3100                                    COMPREHENSIONTLV_FLAG_CR);
  3101         GsmPDUHelper.writeHexOctet(1);
  3102         GsmPDUHelper.writeHexOctet(options.timerId);
  3105     // Timer Value
  3106     if (options.timerValue != null) {
  3107         ComprehensionTlvHelper.writeTimerValueTlv(options.timerValue, true);
  3110     // Language
  3111     if (options.language) {
  3112       ComprehensionTlvHelper.writeLanguageTlv(options.language);
  3115     // Browser Termination
  3116     if (options.terminationCause != null) {
  3117       GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_BROWSER_TERMINATION_CAUSE |
  3118                                  COMPREHENSIONTLV_FLAG_CR);
  3119       GsmPDUHelper.writeHexOctet(1);
  3120       GsmPDUHelper.writeHexOctet(options.terminationCause);
  3123     // Calculate and write BER length to 2nd mark
  3124     Buf.stopCalOutgoingSize();
  3126     // Calculate and write Parcel size to 1st mark
  3127     Buf.stopCalOutgoingSize();
  3129     Buf.writeInt32(0);
  3130     Buf.sendParcel();
  3131   },
  3133   /**
  3134    * Check a given number against the list of emergency numbers provided by the RIL.
  3136    * @param number
  3137    *        The number to look up.
  3138    */
  3139    _isEmergencyNumber: function(number) {
  3140      // Check ril provided numbers first.
  3141      let numbers = RIL_EMERGENCY_NUMBERS;
  3143      if (numbers) {
  3144        numbers = numbers.split(",");
  3145      } else {
  3146        // No ecclist system property, so use our own list.
  3147        numbers = DEFAULT_EMERGENCY_NUMBERS;
  3150      return numbers.indexOf(number) != -1;
  3151    },
  3153    /**
  3154     * Checks whether to temporarily suppress caller id for the call.
  3156     * @param mmi
  3157     *        MMI full object.
  3158     */
  3159    _isTemporaryModeCLIR: function(mmi) {
  3160      return (mmi &&
  3161              mmi.serviceCode == MMI_SC_CLIR &&
  3162              mmi.dialNumber &&
  3163              (mmi.procedure == MMI_PROCEDURE_ACTIVATION ||
  3164               mmi.procedure == MMI_PROCEDURE_DEACTIVATION));
  3165    },
  3167   /**
  3168    * Report STK Service is running.
  3169    */
  3170   reportStkServiceIsRunning: function() {
  3171     this.context.Buf.simpleRequest(REQUEST_REPORT_STK_SERVICE_IS_RUNNING);
  3172   },
  3174   /**
  3175    * Process ICC status.
  3176    */
  3177   _processICCStatus: function(iccStatus) {
  3178     // If |_waitingRadioTech| is true, we should not get app information because
  3179     // the |_isCdma| flag is not ready yet. Otherwise we may use wrong index to
  3180     // get app information, especially for the case that icc card has both cdma
  3181     // and gsm subscription.
  3182     if (this._waitingRadioTech) {
  3183       return;
  3186     this.iccStatus = iccStatus;
  3187     let newCardState;
  3188     let index = this._isCdma ? iccStatus.cdmaSubscriptionAppIndex :
  3189                                iccStatus.gsmUmtsSubscriptionAppIndex;
  3190     let app = iccStatus.apps[index];
  3192     // When |iccStatus.cardState| is not CARD_STATE_PRESENT or have incorrect
  3193     // app information, we can not get iccId. So treat ICC as undetected.
  3194     if (iccStatus.cardState !== CARD_STATE_PRESENT || !app) {
  3195       if (this.cardState !== GECKO_CARDSTATE_UNDETECTED) {
  3196         this.operator = null;
  3197         // We should send |cardstatechange| before |iccinfochange|, otherwise we
  3198         // may lost cardstatechange event when icc card becomes undetected.
  3199         this.cardState = GECKO_CARDSTATE_UNDETECTED;
  3200         this.sendChromeMessage({rilMessageType: "cardstatechange",
  3201                                 cardState: this.cardState});
  3203         this.iccInfo = {iccType: null};
  3204         this.context.ICCUtilsHelper.handleICCInfoChange();
  3206       return;
  3209     let ICCRecordHelper = this.context.ICCRecordHelper;
  3210     // fetchICCRecords will need to read aid, so read aid here.
  3211     this.aid = app.aid;
  3212     this.appType = app.app_type;
  3213     this.iccInfo.iccType = GECKO_CARD_TYPE[this.appType];
  3214     // Try to get iccId only when cardState left GECKO_CARDSTATE_UNDETECTED.
  3215     if (iccStatus.cardState === CARD_STATE_PRESENT &&
  3216         (this.cardState === GECKO_CARDSTATE_UNINITIALIZED ||
  3217          this.cardState === GECKO_CARDSTATE_UNDETECTED)) {
  3218       ICCRecordHelper.readICCID();
  3221     switch (app.app_state) {
  3222       case CARD_APPSTATE_ILLEGAL:
  3223         newCardState = GECKO_CARDSTATE_ILLEGAL;
  3224         break;
  3225       case CARD_APPSTATE_PIN:
  3226         newCardState = GECKO_CARDSTATE_PIN_REQUIRED;
  3227         break;
  3228       case CARD_APPSTATE_PUK:
  3229         newCardState = GECKO_CARDSTATE_PUK_REQUIRED;
  3230         break;
  3231       case CARD_APPSTATE_SUBSCRIPTION_PERSO:
  3232         newCardState = PERSONSUBSTATE[app.perso_substate];
  3233         break;
  3234       case CARD_APPSTATE_READY:
  3235         newCardState = GECKO_CARDSTATE_READY;
  3236         break;
  3237       case CARD_APPSTATE_UNKNOWN:
  3238       case CARD_APPSTATE_DETECTED:
  3239         // Fall through.
  3240       default:
  3241         newCardState = GECKO_CARDSTATE_UNKNOWN;
  3244     let pin1State = app.pin1_replaced ? iccStatus.universalPINState :
  3245                                         app.pin1;
  3246     if (pin1State === CARD_PINSTATE_ENABLED_PERM_BLOCKED) {
  3247       newCardState = GECKO_CARDSTATE_PERMANENT_BLOCKED;
  3250     if (this.cardState == newCardState) {
  3251       return;
  3254     // This was moved down from CARD_APPSTATE_READY
  3255     this.requestNetworkInfo();
  3256     if (newCardState == GECKO_CARDSTATE_READY) {
  3257       // For type SIM, we need to check EF_phase first.
  3258       // Other types of ICC we can send Terminal_Profile immediately.
  3259       if (this.appType == CARD_APPTYPE_SIM) {
  3260         this.context.SimRecordHelper.readSimPhase();
  3261       } else if (RILQUIRKS_SEND_STK_PROFILE_DOWNLOAD) {
  3262         this.sendStkTerminalProfile(STK_SUPPORTED_TERMINAL_PROFILE);
  3265       ICCRecordHelper.fetchICCRecords();
  3268     this.cardState = newCardState;
  3269     this.sendChromeMessage({rilMessageType: "cardstatechange",
  3270                             cardState: this.cardState});
  3271   },
  3273    /**
  3274    * Helper for processing responses of functions such as enterICC* and changeICC*.
  3275    */
  3276   _processEnterAndChangeICCResponses: function(length, options) {
  3277     options.success = (options.rilRequestError === 0);
  3278     if (!options.success) {
  3279       options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError];
  3281     options.retryCount = length ? this.context.Buf.readInt32List()[0] : -1;
  3282     if (options.rilMessageType != "sendMMI") {
  3283       this.sendChromeMessage(options);
  3284       return;
  3287     let mmiServiceCode = options.mmiServiceCode;
  3289     if (options.success) {
  3290       switch (mmiServiceCode) {
  3291         case MMI_KS_SC_PIN:
  3292           options.statusMessage = MMI_SM_KS_PIN_CHANGED;
  3293           break;
  3294         case MMI_KS_SC_PIN2:
  3295           options.statusMessage = MMI_SM_KS_PIN2_CHANGED;
  3296           break;
  3297         case MMI_KS_SC_PUK:
  3298           options.statusMessage = MMI_SM_KS_PIN_UNBLOCKED;
  3299           break;
  3300         case MMI_KS_SC_PUK2:
  3301           options.statusMessage = MMI_SM_KS_PIN2_UNBLOCKED;
  3302           break;
  3304     } else {
  3305       if (options.retryCount <= 0) {
  3306         if (mmiServiceCode === MMI_KS_SC_PUK) {
  3307           options.errorMsg = MMI_ERROR_KS_SIM_BLOCKED;
  3308         } else if (mmiServiceCode === MMI_KS_SC_PIN) {
  3309           options.errorMsg = MMI_ERROR_KS_NEEDS_PUK;
  3311       } else {
  3312         if (mmiServiceCode === MMI_KS_SC_PIN ||
  3313             mmiServiceCode === MMI_KS_SC_PIN2) {
  3314           options.errorMsg = MMI_ERROR_KS_BAD_PIN;
  3315         } else if (mmiServiceCode === MMI_KS_SC_PUK ||
  3316                    mmiServiceCode === MMI_KS_SC_PUK2) {
  3317           options.errorMsg = MMI_ERROR_KS_BAD_PUK;
  3319         if (options.retryCount !== undefined) {
  3320           options.additionalInformation = options.retryCount;
  3325     this.sendChromeMessage(options);
  3326   },
  3328   // We combine all of the NETWORK_INFO_MESSAGE_TYPES into one "networkinfochange"
  3329   // message to the RadioInterfaceLayer, so we can avoid sending multiple
  3330   // VoiceInfoChanged events for both operator / voice_data_registration
  3331   //
  3332   // State management here is a little tricky. We need to know both:
  3333   // 1. Whether or not a response was received for each of the
  3334   //    NETWORK_INFO_MESSAGE_TYPES
  3335   // 2. The outbound message that corresponds with that response -- but this
  3336   //    only happens when internal state changes (i.e. it isn't guaranteed)
  3337   //
  3338   // To collect this state, each message response function first calls
  3339   // _receivedNetworkInfo, to mark the response as received. When the
  3340   // final response is received, a call to _sendPendingNetworkInfo is placed
  3341   // on the next tick of the worker thread.
  3342   //
  3343   // Since the original call to _receivedNetworkInfo happens at the top
  3344   // of the response handler, this gives the final handler a chance to
  3345   // queue up it's "changed" message by calling _sendNetworkInfoMessage if/when
  3346   // the internal state has actually changed.
  3347   _sendNetworkInfoMessage: function(type, message) {
  3348     if (!this._processingNetworkInfo) {
  3349       // We only combine these messages in the case of the combined request
  3350       // in requestNetworkInfo()
  3351       this.sendChromeMessage(message);
  3352       return;
  3355     if (DEBUG) {
  3356       this.context.debug("Queuing " + type + " network info message: " +
  3357                          JSON.stringify(message));
  3359     this._pendingNetworkInfo[type] = message;
  3360   },
  3362   _receivedNetworkInfo: function(type) {
  3363     if (DEBUG) this.context.debug("Received " + type + " network info.");
  3364     if (!this._processingNetworkInfo) {
  3365       return;
  3368     let pending = this._pendingNetworkInfo;
  3370     // We still need to track states for events that aren't fired.
  3371     if (!(type in pending)) {
  3372       pending[type] = this.pendingNetworkType;
  3375     // Pending network info is ready to be sent when no more messages
  3376     // are waiting for responses, but the combined payload hasn't been sent.
  3377     for (let i = 0; i < NETWORK_INFO_MESSAGE_TYPES.length; i++) {
  3378       let msgType = NETWORK_INFO_MESSAGE_TYPES[i];
  3379       if (!(msgType in pending)) {
  3380         if (DEBUG) {
  3381           this.context.debug("Still missing some more network info, not " +
  3382                              "notifying main thread.");
  3384         return;
  3388     // Do a pass to clean up the processed messages that didn't create
  3389     // a response message, so we don't have unused keys in the outbound
  3390     // networkinfochanged message.
  3391     for (let key in pending) {
  3392       if (pending[key] == this.pendingNetworkType) {
  3393         delete pending[key];
  3397     if (DEBUG) {
  3398       this.context.debug("All pending network info has been received: " +
  3399                          JSON.stringify(pending));
  3402     // Send the message on the next tick of the worker's loop, so we give the
  3403     // last message a chance to call _sendNetworkInfoMessage first.
  3404     setTimeout(this._sendPendingNetworkInfo.bind(this), 0);
  3405   },
  3407   _sendPendingNetworkInfo: function() {
  3408     this.sendChromeMessage(this._pendingNetworkInfo);
  3410     this._processingNetworkInfo = false;
  3411     for (let i = 0; i < NETWORK_INFO_MESSAGE_TYPES.length; i++) {
  3412       delete this._pendingNetworkInfo[NETWORK_INFO_MESSAGE_TYPES[i]];
  3415     if (this._needRepollNetworkInfo) {
  3416       this._needRepollNetworkInfo = false;
  3417       this.requestNetworkInfo();
  3419   },
  3421   /**
  3422    * Normalize the signal strength in dBm to the signal level from 0 to 100.
  3424    * @param signal
  3425    *        The signal strength in dBm to normalize.
  3426    * @param min
  3427    *        The signal strength in dBm maps to level 0.
  3428    * @param max
  3429    *        The signal strength in dBm maps to level 100.
  3431    * @return level
  3432    *         The signal level from 0 to 100.
  3433    */
  3434   _processSignalLevel: function(signal, min, max) {
  3435     if (signal <= min) {
  3436       return 0;
  3439     if (signal >= max) {
  3440       return 100;
  3443     return Math.floor((signal - min) * 100 / (max - min));
  3444   },
  3446   /**
  3447    * Process LTE signal strength to the signal info object.
  3449    * @param signal
  3450    *        The signal object reported from RIL/modem.
  3452    * @return The object of signal strength info.
  3453    *         Or null if invalid signal input.
  3454    */
  3455   _processLteSignal: function(signal) {
  3456     // Valid values are 0-63 as defined in TS 27.007 clause 8.69.
  3457     if (signal.lteSignalStrength === undefined ||
  3458         signal.lteSignalStrength < 0 ||
  3459         signal.lteSignalStrength > 63) {
  3460       return null;
  3463     let info = {
  3464       voice: {
  3465         signalStrength:    null,
  3466         relSignalStrength: null
  3467       },
  3468       data: {
  3469         signalStrength:    null,
  3470         relSignalStrength: null
  3472     };
  3474     // TODO: Bug 982013: reconsider signalStrength/relSignalStrength APIs for
  3475     //       GSM/CDMA/LTE, and take rsrp/rssnr into account for LTE case then.
  3476     let signalStrength = -111 + signal.lteSignalStrength;
  3477     info.voice.signalStrength = info.data.signalStrength = signalStrength;
  3478     // 0 and 12 are referred to AOSP's implementation. These values are not
  3479     // constants and can be customized based on different requirements.
  3480     let signalLevel = this._processSignalLevel(signal.lteSignalStrength, 0, 12);
  3481     info.voice.relSignalStrength = info.data.relSignalStrength = signalLevel;
  3483     return info;
  3484   },
  3486   _processSignalStrength: function(signal) {
  3487     let info = {
  3488       voice: {
  3489         signalStrength:    null,
  3490         relSignalStrength: null
  3491       },
  3492       data: {
  3493         signalStrength:    null,
  3494         relSignalStrength: null
  3496     };
  3498     // During startup, |radioTech| is not yet defined, so we need to
  3499     // check it separately.
  3500     if (("radioTech" in this.voiceRegistrationState) &&
  3501         !this._isGsmTechGroup(this.voiceRegistrationState.radioTech)) {
  3502       // CDMA RSSI.
  3503       // Valid values are positive integers. This value is the actual RSSI value
  3504       // multiplied by -1. Example: If the actual RSSI is -75, then this
  3505       // response value will be 75.
  3506       if (signal.cdmaDBM && signal.cdmaDBM > 0) {
  3507         let signalStrength = -1 * signal.cdmaDBM;
  3508         info.voice.signalStrength = signalStrength;
  3510         // -105 and -70 are referred to AOSP's implementation. These values are
  3511         // not constants and can be customized based on different requirement.
  3512         let signalLevel = this._processSignalLevel(signalStrength, -105, -70);
  3513         info.voice.relSignalStrength = signalLevel;
  3516       // EVDO RSSI.
  3517       // Valid values are positive integers. This value is the actual RSSI value
  3518       // multiplied by -1. Example: If the actual RSSI is -75, then this
  3519       // response value will be 75.
  3520       if (signal.evdoDBM && signal.evdoDBM > 0) {
  3521         let signalStrength = -1 * signal.evdoDBM;
  3522         info.data.signalStrength = signalStrength;
  3524         // -105 and -70 are referred to AOSP's implementation. These values are
  3525         // not constants and can be customized based on different requirement.
  3526         let signalLevel = this._processSignalLevel(signalStrength, -105, -70);
  3527         info.data.relSignalStrength = signalLevel;
  3529     } else {
  3530       // Check LTE level first, and check GSM/UMTS level next if LTE one is not
  3531       // valid.
  3532       let lteInfo = this._processLteSignal(signal);
  3533       if (lteInfo) {
  3534         info = lteInfo;
  3535       } else {
  3536         // GSM signal strength.
  3537         // Valid values are 0-31 as defined in TS 27.007 8.5.
  3538         // 0     : -113 dBm or less
  3539         // 1     : -111 dBm
  3540         // 2...30: -109...-53 dBm
  3541         // 31    : -51 dBm
  3542         if (signal.gsmSignalStrength &&
  3543             signal.gsmSignalStrength >= 0 &&
  3544             signal.gsmSignalStrength <= 31) {
  3545           let signalStrength = -113 + 2 * signal.gsmSignalStrength;
  3546           info.voice.signalStrength = info.data.signalStrength = signalStrength;
  3548           // -115 and -85 are referred to AOSP's implementation. These values are
  3549           // not constants and can be customized based on different requirement.
  3550           let signalLevel = this._processSignalLevel(signalStrength, -110, -85);
  3551           info.voice.relSignalStrength = info.data.relSignalStrength = signalLevel;
  3556     info.rilMessageType = "signalstrengthchange";
  3557     this._sendNetworkInfoMessage(NETWORK_INFO_SIGNAL, info);
  3559     if (this.cachedDialRequest && info.voice.signalStrength) {
  3560       // Radio is ready for making the cached emergency call.
  3561       this.cachedDialRequest.callback();
  3562       this.cachedDialRequest = null;
  3564   },
  3566   /**
  3567    * Process the network registration flags.
  3569    * @return true if the state changed, false otherwise.
  3570    */
  3571   _processCREG: function(curState, newState) {
  3572     let changed = false;
  3574     let regState = this.parseInt(newState[0], NETWORK_CREG_STATE_UNKNOWN);
  3575     if (curState.regState === undefined || curState.regState !== regState) {
  3576       changed = true;
  3577       curState.regState = regState;
  3579       curState.state = NETWORK_CREG_TO_GECKO_MOBILE_CONNECTION_STATE[regState];
  3580       curState.connected = regState == NETWORK_CREG_STATE_REGISTERED_HOME ||
  3581                            regState == NETWORK_CREG_STATE_REGISTERED_ROAMING;
  3582       curState.roaming = regState == NETWORK_CREG_STATE_REGISTERED_ROAMING;
  3583       curState.emergencyCallsOnly = !curState.connected;
  3586     if (!curState.cell) {
  3587       curState.cell = {};
  3590     // From TS 23.003, 0000 and 0xfffe are indicated that no valid LAI exists
  3591     // in MS. So we still need to report the '0000' as well.
  3592     let lac = this.parseInt(newState[1], -1, 16);
  3593     if (curState.cell.gsmLocationAreaCode === undefined ||
  3594         curState.cell.gsmLocationAreaCode !== lac) {
  3595       curState.cell.gsmLocationAreaCode = lac;
  3596       changed = true;
  3599     let cid = this.parseInt(newState[2], -1, 16);
  3600     if (curState.cell.gsmCellId === undefined ||
  3601         curState.cell.gsmCellId !== cid) {
  3602       curState.cell.gsmCellId = cid;
  3603       changed = true;
  3606     let radioTech = (newState[3] === undefined ?
  3607                      NETWORK_CREG_TECH_UNKNOWN :
  3608                      this.parseInt(newState[3], NETWORK_CREG_TECH_UNKNOWN));
  3609     if (curState.radioTech === undefined || curState.radioTech !== radioTech) {
  3610       changed = true;
  3611       curState.radioTech = radioTech;
  3612       curState.type = GECKO_RADIO_TECH[radioTech] || null;
  3614     return changed;
  3615   },
  3617   _processVoiceRegistrationState: function(state) {
  3618     let rs = this.voiceRegistrationState;
  3619     let stateChanged = this._processCREG(rs, state);
  3620     if (stateChanged && rs.connected) {
  3621       this.getSmscAddress();
  3624     let cell = rs.cell;
  3625     if (this._isCdma) {
  3626       // Some variables below are not used. Comment them instead of removing to
  3627       // keep the information about state[x].
  3628       let cdmaBaseStationId = this.parseInt(state[4], -1);
  3629       let cdmaBaseStationLatitude = this.parseInt(state[5], -2147483648);
  3630       let cdmaBaseStationLongitude = this.parseInt(state[6], -2147483648);
  3631       // let cssIndicator = this.parseInt(state[7]);
  3632       let cdmaSystemId = this.parseInt(state[8], -1);
  3633       let cdmaNetworkId = this.parseInt(state[9], -1);
  3634       // let roamingIndicator = this.parseInt(state[10]);
  3635       // let systemIsInPRL = this.parseInt(state[11]);
  3636       // let defaultRoamingIndicator = this.parseInt(state[12]);
  3637       // let reasonForDenial = this.parseInt(state[13]);
  3639       if (cell.cdmaBaseStationId !== cdmaBaseStationId ||
  3640           cell.cdmaBaseStationLatitude !== cdmaBaseStationLatitude ||
  3641           cell.cdmaBaseStationLongitude !== cdmaBaseStationLongitude ||
  3642           cell.cdmaSystemId !== cdmaSystemId ||
  3643           cell.cdmaNetworkId !== cdmaNetworkId) {
  3644         stateChanged = true;
  3645         cell.cdmaBaseStationId = cdmaBaseStationId;
  3646         cell.cdmaBaseStationLatitude = cdmaBaseStationLatitude;
  3647         cell.cdmaBaseStationLongitude = cdmaBaseStationLongitude;
  3648         cell.cdmaSystemId = cdmaSystemId;
  3649         cell.cdmaNetworkId = cdmaNetworkId;
  3653     if (stateChanged) {
  3654       rs.rilMessageType = "voiceregistrationstatechange";
  3655       this._sendNetworkInfoMessage(NETWORK_INFO_VOICE_REGISTRATION_STATE, rs);
  3657   },
  3659   _processDataRegistrationState: function(state) {
  3660     let rs = this.dataRegistrationState;
  3661     let stateChanged = this._processCREG(rs, state);
  3662     if (stateChanged) {
  3663       rs.rilMessageType = "dataregistrationstatechange";
  3664       this._sendNetworkInfoMessage(NETWORK_INFO_DATA_REGISTRATION_STATE, rs);
  3666   },
  3668   _processOperator: function(operatorData) {
  3669     if (operatorData.length < 3) {
  3670       if (DEBUG) {
  3671         this.context.debug("Expected at least 3 strings for operator.");
  3675     if (!this.operator) {
  3676       this.operator = {
  3677         rilMessageType: "operatorchange",
  3678         longName: null,
  3679         shortName: null
  3680       };
  3683     let [longName, shortName, networkTuple] = operatorData;
  3684     let thisTuple = (this.operator.mcc || "") + (this.operator.mnc || "");
  3686     if (this.operator.longName !== longName ||
  3687         this.operator.shortName !== shortName ||
  3688         thisTuple !== networkTuple) {
  3690       this.operator.mcc = null;
  3691       this.operator.mnc = null;
  3693       if (networkTuple) {
  3694         try {
  3695           this._processNetworkTuple(networkTuple, this.operator);
  3696         } catch (e) {
  3697           if (DEBUG) this.context.debug("Error processing operator tuple: " + e);
  3699       } else {
  3700         // According to ril.h, the operator fields will be NULL when the operator
  3701         // is not currently registered. We can avoid trying to parse the numeric
  3702         // tuple in that case.
  3703         if (DEBUG) {
  3704           this.context.debug("Operator is currently unregistered");
  3708       let ICCUtilsHelper = this.context.ICCUtilsHelper;
  3709       let networkName;
  3710       // We won't get network name using PNN and OPL if voice registration isn't ready
  3711       if (this.voiceRegistrationState.cell &&
  3712           this.voiceRegistrationState.cell.gsmLocationAreaCode != -1) {
  3713         networkName = ICCUtilsHelper.getNetworkNameFromICC(
  3714           this.operator.mcc,
  3715           this.operator.mnc,
  3716           this.voiceRegistrationState.cell.gsmLocationAreaCode);
  3719       if (networkName) {
  3720         if (DEBUG) {
  3721           this.context.debug("Operator names will be overriden: " +
  3722                              "longName = " + networkName.fullName + ", " +
  3723                              "shortName = " + networkName.shortName);
  3726         this.operator.longName = networkName.fullName;
  3727         this.operator.shortName = networkName.shortName;
  3728       } else {
  3729         this.operator.longName = longName;
  3730         this.operator.shortName = shortName;
  3733       if (ICCUtilsHelper.updateDisplayCondition()) {
  3734         ICCUtilsHelper.handleICCInfoChange();
  3736       this._sendNetworkInfoMessage(NETWORK_INFO_OPERATOR, this.operator);
  3738   },
  3740   /**
  3741    * Helpers for processing call state and handle the active call.
  3742    */
  3743   _processCalls: function(newCalls) {
  3744     let conferenceChanged = false;
  3745     let clearConferenceRequest = false;
  3746     let pendingOutgoingCall = null;
  3748     // Go through the calls we currently have on file and see if any of them
  3749     // changed state. Remove them from the newCalls map as we deal with them
  3750     // so that only new calls remain in the map after we're done.
  3751     for each (let currentCall in this.currentCalls) {
  3752       if (currentCall.callIndex == OUTGOING_PLACEHOLDER_CALL_INDEX) {
  3753         pendingOutgoingCall = currentCall;
  3754         continue;
  3757       let newCall;
  3758       if (newCalls) {
  3759         newCall = newCalls[currentCall.callIndex];
  3760         delete newCalls[currentCall.callIndex];
  3763       // Call is no longer reported by the radio. Remove from our map and send
  3764       // disconnected state change.
  3765       if (!newCall) {
  3766         if (this.currentConference.participants[currentCall.callIndex]) {
  3767           conferenceChanged = true;
  3769         this._removeVoiceCall(currentCall,
  3770                               currentCall.hangUpLocal ?
  3771                                 GECKO_CALL_ERROR_NORMAL_CALL_CLEARING : null);
  3772         continue;
  3775       // Call is still valid.
  3776       if (newCall.state == currentCall.state &&
  3777           newCall.isMpty == currentCall.isMpty) {
  3778         continue;
  3781       // State has changed.
  3782       if (newCall.state == CALL_STATE_INCOMING &&
  3783           currentCall.state == CALL_STATE_WAITING) {
  3784         // Update the call internally but we don't notify chrome since these two
  3785         // states are viewed as the same one there.
  3786         currentCall.state = newCall.state;
  3787         continue;
  3790       if (!currentCall.started && newCall.state == CALL_STATE_ACTIVE) {
  3791         currentCall.started = new Date().getTime();
  3794       if (currentCall.isMpty == newCall.isMpty &&
  3795           newCall.state != currentCall.state) {
  3796         currentCall.state = newCall.state;
  3797         if (currentCall.isConference) {
  3798           conferenceChanged = true;
  3800         this._handleChangedCallState(currentCall);
  3801         continue;
  3804       // '.isMpty' becomes false when the conference call is put on hold.
  3805       // We need to introduce additional 'isConference' to correctly record the
  3806       // real conference status
  3808       // Update a possible conference participant when .isMpty changes.
  3809       if (!currentCall.isMpty && newCall.isMpty) {
  3810         if (this._hasConferenceRequest) {
  3811           conferenceChanged = true;
  3812           clearConferenceRequest = true;
  3813           currentCall.state = newCall.state;
  3814           currentCall.isMpty = newCall.isMpty;
  3815           currentCall.isConference = true;
  3816           this.currentConference.participants[currentCall.callIndex] = currentCall;
  3817           this._handleChangedCallState(currentCall);
  3818         } else if (currentCall.isConference) {
  3819           // The case happens when resuming a held conference call.
  3820           conferenceChanged = true;
  3821           currentCall.state = newCall.state;
  3822           currentCall.isMpty = newCall.isMpty;
  3823           this.currentConference.participants[currentCall.callIndex] = currentCall;
  3824           this._handleChangedCallState(currentCall);
  3825         } else {
  3826           // Weird. This sometimes happens when we switch two calls, but it is
  3827           // not a conference call.
  3828           currentCall.state = newCall.state;
  3829           this._handleChangedCallState(currentCall);
  3831       } else if (currentCall.isMpty && !newCall.isMpty) {
  3832         if (!this.currentConference.participants[newCall.callIndex]) {
  3833           continue;
  3836         // '.isMpty' of a conference participant is set to false by rild when
  3837         // the conference call is put on hold. We don't actually know if the call
  3838         // still attends the conference until updating all calls finishes. We
  3839         // cache it for further determination.
  3840         if (newCall.state != CALL_STATE_HOLDING) {
  3841           delete this.currentConference.participants[newCall.callIndex];
  3842           currentCall.state = newCall.state;
  3843           currentCall.isMpty = newCall.isMpty;
  3844           currentCall.isConference = false;
  3845           conferenceChanged = true;
  3846           this._handleChangedCallState(currentCall);
  3847           continue;
  3850         if (!this.currentConference.cache) {
  3851           this.currentConference.cache = {};
  3853         this.currentConference.cache[currentCall.callIndex] = newCall;
  3854         currentCall.state = newCall.state;
  3855         currentCall.isMpty = newCall.isMpty;
  3856         conferenceChanged = true;
  3860     if (pendingOutgoingCall) {
  3861       if (!newCalls || Object.keys(newCalls).length === 0) {
  3862         // We don't get a successful call for pendingOutgoingCall.
  3863         this._removePendingOutgoingCall(GECKO_CALL_ERROR_UNSPECIFIED);
  3864       } else {
  3865         // Only remove it from currentCalls map. Will use the new call to
  3866         // replace the placeholder.
  3867         delete this.currentCalls[OUTGOING_PLACEHOLDER_CALL_INDEX];
  3871     // Go through any remaining calls that are new to us.
  3872     for each (let newCall in newCalls) {
  3873       if (newCall.isVoice) {
  3874         if (newCall.isMpty) {
  3875           conferenceChanged = true;
  3877         if (!pendingOutgoingCall &&
  3878             (newCall.state === CALL_STATE_DIALING ||
  3879              newCall.state === CALL_STATE_ALERTING)) {
  3880           // Receive a new outgoing call which is already hung up by user.
  3881           if (DEBUG) this.context.debug("Pending outgoing call is hung up by user.");
  3882           this.sendHangUpRequest(newCall.callIndex);
  3883         } else {
  3884           this._addNewVoiceCall(newCall);
  3889     if (clearConferenceRequest) {
  3890       this._hasConferenceRequest = false;
  3892     if (conferenceChanged) {
  3893       this._ensureConference();
  3895   },
  3897   _addNewVoiceCall: function(newCall) {
  3898     // Format international numbers appropriately.
  3899     if (newCall.number && newCall.toa == TOA_INTERNATIONAL &&
  3900         newCall.number[0] != "+") {
  3901       newCall.number = "+" + newCall.number;
  3904     if (newCall.state == CALL_STATE_INCOMING) {
  3905       newCall.isOutgoing = false;
  3906     } else if (newCall.state == CALL_STATE_DIALING) {
  3907       newCall.isOutgoing = true;
  3910     // Set flag for outgoing emergency call.
  3911     newCall.isEmergency = newCall.isOutgoing &&
  3912       this._isEmergencyNumber(newCall.number);
  3914     // Set flag for conference.
  3915     newCall.isConference = newCall.isMpty ? true : false;
  3917     // Add to our map.
  3918     if (newCall.isMpty) {
  3919       this.currentConference.participants[newCall.callIndex] = newCall;
  3921     this._handleChangedCallState(newCall);
  3922     this.currentCalls[newCall.callIndex] = newCall;
  3923   },
  3925   _removeVoiceCall: function(removedCall, failCause) {
  3926     if (this.currentConference.participants[removedCall.callIndex]) {
  3927       removedCall.isConference = false;
  3928       delete this.currentConference.participants[removedCall.callIndex];
  3929       delete this.currentCalls[removedCall.callIndex];
  3930       // We don't query the fail cause here as it triggers another asynchrouns
  3931       // request that leads to a problem of updating all conferece participants
  3932       // in one task.
  3933       this._handleDisconnectedCall(removedCall);
  3934     } else {
  3935       delete this.currentCalls[removedCall.callIndex];
  3936       if (failCause) {
  3937         removedCall.failCause = failCause;
  3938         this._handleDisconnectedCall(removedCall);
  3939       } else {
  3940         this.getFailCauseCode((function(call, failCause) {
  3941           call.failCause = failCause;
  3942           this._handleDisconnectedCall(call);
  3943         }).bind(this, removedCall));
  3946   },
  3948   _createPendingOutgoingCall: function(options) {
  3949     if (DEBUG) this.context.debug("Create a pending outgoing call.");
  3950     this._addNewVoiceCall({
  3951       number: options.number,
  3952       state: CALL_STATE_DIALING,
  3953       callIndex: OUTGOING_PLACEHOLDER_CALL_INDEX
  3954     });
  3955   },
  3957   _removePendingOutgoingCall: function(failCause) {
  3958     let call = this.currentCalls[OUTGOING_PLACEHOLDER_CALL_INDEX];
  3959     if (!call) {
  3960       return;
  3963     if (DEBUG) this.context.debug("Remove pending outgoing call.");
  3964     this._removeVoiceCall(pendingOutgoingCall, failCause);
  3965   },
  3967   _ensureConference: function() {
  3968     let oldState = this.currentConference.state;
  3969     let remaining = Object.keys(this.currentConference.participants);
  3971     if (remaining.length == 1) {
  3972       // Remove that if only does one remain in a conference call.
  3973       let call = this.currentCalls[remaining[0]];
  3974       call.isConference = false;
  3975       this._handleChangedCallState(call);
  3976       delete this.currentConference.participants[call.callIndex];
  3977     } else if (remaining.length > 1) {
  3978       for each (let call in this.currentConference.cache) {
  3979         call.isConference = true;
  3980         this.currentConference.participants[call.callIndex] = call;
  3981         this.currentCalls[call.callIndex] = call;
  3982         this._handleChangedCallState(call);
  3985     delete this.currentConference.cache;
  3987     // Update the conference call's state.
  3988     let state = CALL_STATE_UNKNOWN;
  3989     for each (let call in this.currentConference.participants) {
  3990       if (state != CALL_STATE_UNKNOWN && state != call.state) {
  3991         // Each participant should have the same state, otherwise something
  3992         // wrong happens.
  3993         state = CALL_STATE_UNKNOWN;
  3994         break;
  3996       state = call.state;
  3998     if (oldState != state) {
  3999       this.currentConference.state = state;
  4000       let message = {rilMessageType: "conferenceCallStateChanged",
  4001                      state: state};
  4002       this.sendChromeMessage(message);
  4004   },
  4006   _handleChangedCallState: function(changedCall) {
  4007     let message = {rilMessageType: "callStateChange",
  4008                    call: changedCall};
  4009     this.sendChromeMessage(message);
  4010   },
  4012   _handleDisconnectedCall: function(disconnectedCall) {
  4013     let message = {rilMessageType: "callDisconnected",
  4014                    call: disconnectedCall};
  4015     this.sendChromeMessage(message);
  4016   },
  4018   _sendDataCallError: function(message, errorCode) {
  4019     // Should not include token for unsolicited response.
  4020     delete message.rilMessageToken;
  4021     message.rilMessageType = "datacallerror";
  4022     if (errorCode == ERROR_GENERIC_FAILURE) {
  4023       message.errorMsg = RIL_ERROR_TO_GECKO_ERROR[errorCode];
  4024     } else {
  4025       message.errorMsg = RIL_DATACALL_FAILCAUSE_TO_GECKO_DATACALL_ERROR[errorCode];
  4027     this.sendChromeMessage(message);
  4028   },
  4030   /**
  4031    * @return "deactivate" if <ifname> changes or one of the currentDataCall
  4032    *         addresses is missing in updatedDataCall, or "identical" if no
  4033    *         changes found, or "changed" otherwise.
  4034    */
  4035   _compareDataCallLink: function(updatedDataCall, currentDataCall) {
  4036     // If network interface is changed, report as "deactivate".
  4037     if (updatedDataCall.ifname != currentDataCall.ifname) {
  4038       return "deactivate";
  4041     // If any existing address is missing, report as "deactivate".
  4042     for (let i = 0; i < currentDataCall.addresses.length; i++) {
  4043       let address = currentDataCall.addresses[i];
  4044       if (updatedDataCall.addresses.indexOf(address) < 0) {
  4045         return "deactivate";
  4049     if (currentDataCall.addresses.length != updatedDataCall.addresses.length) {
  4050       // Since now all |currentDataCall.addresses| are found in
  4051       // |updatedDataCall.addresses|, this means one or more new addresses are
  4052       // reported.
  4053       return "changed";
  4056     let fields = ["gateways", "dnses"];
  4057     for (let i = 0; i < fields.length; i++) {
  4058       // Compare <datacall>.<field>.
  4059       let field = fields[i];
  4060       let lhs = updatedDataCall[field], rhs = currentDataCall[field];
  4061       if (lhs.length != rhs.length) {
  4062         return "changed";
  4064       for (let i = 0; i < lhs.length; i++) {
  4065         if (lhs[i] != rhs[i]) {
  4066           return "changed";
  4071     return "identical";
  4072   },
  4074   _processDataCallList: function(datacalls, newDataCallOptions) {
  4075     // Check for possible PDP errors: We check earlier because the datacall
  4076     // can be removed if is the same as the current one.
  4077     for each (let newDataCall in datacalls) {
  4078       if (newDataCall.status != DATACALL_FAIL_NONE) {
  4079         if (newDataCallOptions) {
  4080           newDataCall.apn = newDataCallOptions.apn;
  4082         this._sendDataCallError(newDataCall, newDataCall.status);
  4086     for each (let currentDataCall in this.currentDataCalls) {
  4087       let updatedDataCall;
  4088       if (datacalls) {
  4089         updatedDataCall = datacalls[currentDataCall.cid];
  4090         delete datacalls[currentDataCall.cid];
  4093       if (!updatedDataCall) {
  4094         // If datacalls list is coming from REQUEST_SETUP_DATA_CALL response,
  4095         // we do not change state for any currentDataCalls not in datacalls list.
  4096         if (!newDataCallOptions) {
  4097           delete this.currentDataCalls[currentDataCall.cid];
  4098           currentDataCall.state = GECKO_NETWORK_STATE_DISCONNECTED;
  4099           currentDataCall.rilMessageType = "datacallstatechange";
  4100           this.sendChromeMessage(currentDataCall);
  4102         continue;
  4105       if (updatedDataCall && !updatedDataCall.ifname) {
  4106         delete this.currentDataCalls[currentDataCall.cid];
  4107         currentDataCall.state = GECKO_NETWORK_STATE_UNKNOWN;
  4108         currentDataCall.rilMessageType = "datacallstatechange";
  4109         this.sendChromeMessage(currentDataCall);
  4110         continue;
  4113       this._setDataCallGeckoState(updatedDataCall);
  4114       if (updatedDataCall.state != currentDataCall.state) {
  4115         if (updatedDataCall.state == GECKO_NETWORK_STATE_DISCONNECTED) {
  4116           delete this.currentDataCalls[currentDataCall.cid];
  4118         currentDataCall.status = updatedDataCall.status;
  4119         currentDataCall.active = updatedDataCall.active;
  4120         currentDataCall.state = updatedDataCall.state;
  4121         currentDataCall.rilMessageType = "datacallstatechange";
  4122         this.sendChromeMessage(currentDataCall);
  4123         continue;
  4126       // State not changed, now check links.
  4127       let result =
  4128         this._compareDataCallLink(updatedDataCall, currentDataCall);
  4129       if (result == "identical") {
  4130         if (DEBUG) this.context.debug("No changes in data call.");
  4131         continue;
  4133       if (result == "deactivate") {
  4134         if (DEBUG) this.context.debug("Data link changed, cleanup.");
  4135         this.deactivateDataCall(currentDataCall);
  4136         continue;
  4138       // Minor change, just update and notify.
  4139       if (DEBUG) {
  4140         this.context.debug("Data link minor change, just update and notify.");
  4142       currentDataCall.addresses = updatedDataCall.addresses.slice();
  4143       currentDataCall.dnses = updatedDataCall.dnses.slice();
  4144       currentDataCall.gateways = updatedDataCall.gateways.slice();
  4145       currentDataCall.rilMessageType = "datacallstatechange";
  4146       this.sendChromeMessage(currentDataCall);
  4149     for each (let newDataCall in datacalls) {
  4150       if (!newDataCall.ifname) {
  4151         continue;
  4154       if (!newDataCallOptions) {
  4155         if (DEBUG) {
  4156           this.context.debug("Unexpected new data call: " +
  4157                              JSON.stringify(newDataCall));
  4159         continue;
  4162       this.currentDataCalls[newDataCall.cid] = newDataCall;
  4163       this._setDataCallGeckoState(newDataCall);
  4165       newDataCall.radioTech = newDataCallOptions.radioTech;
  4166       newDataCall.apn = newDataCallOptions.apn;
  4167       newDataCall.user = newDataCallOptions.user;
  4168       newDataCall.passwd = newDataCallOptions.passwd;
  4169       newDataCall.chappap = newDataCallOptions.chappap;
  4170       newDataCall.pdptype = newDataCallOptions.pdptype;
  4171       newDataCallOptions = null;
  4173       newDataCall.rilMessageType = "datacallstatechange";
  4174       this.sendChromeMessage(newDataCall);
  4176   },
  4178   _setDataCallGeckoState: function(datacall) {
  4179     switch (datacall.active) {
  4180       case DATACALL_INACTIVE:
  4181         datacall.state = GECKO_NETWORK_STATE_DISCONNECTED;
  4182         break;
  4183       case DATACALL_ACTIVE_DOWN:
  4184       case DATACALL_ACTIVE_UP:
  4185         datacall.state = GECKO_NETWORK_STATE_CONNECTED;
  4186         break;
  4188   },
  4190   _processSuppSvcNotification: function(info) {
  4191     if (DEBUG) {
  4192       this.context.debug("handle supp svc notification: " + JSON.stringify(info));
  4195     if (info.notificationType !== 1) {
  4196       // We haven't supported MO intermediate result code, i.e.
  4197       // info.notificationType === 0, which refers to code1 defined in 3GPP
  4198       // 27.007 7.17. We only support partial MT unsolicited result code,
  4199       // referring to code2, for now.
  4200       return;
  4203     let notification = null;
  4204     let callIndex = -1;
  4206     switch (info.code) {
  4207       case SUPP_SVC_NOTIFICATION_CODE2_PUT_ON_HOLD:
  4208       case SUPP_SVC_NOTIFICATION_CODE2_RETRIEVED:
  4209         notification = GECKO_SUPP_SVC_NOTIFICATION_FROM_CODE2[info.code];
  4210         break;
  4211       default:
  4212         // Notification type not supported.
  4213         return;
  4216     // Get the target call object for this notification.
  4217     let currentCallIndexes = Object.keys(this.currentCalls);
  4218     if (currentCallIndexes.length === 1) {
  4219       // Only one call exists. This should be the target.
  4220       callIndex = currentCallIndexes[0];
  4221     } else {
  4222       // Find the call in |currentCalls| by the given number.
  4223       if (info.number) {
  4224         for each (let currentCall in this.currentCalls) {
  4225           if (currentCall.number == info.number) {
  4226             callIndex = currentCall.callIndex;
  4227             break;
  4233     let message = {rilMessageType: "suppSvcNotification",
  4234                    notification: notification,
  4235                    callIndex: callIndex};
  4236     this.sendChromeMessage(message);
  4237   },
  4239   _cancelEmergencyCbModeTimeout: function() {
  4240     if (this._exitEmergencyCbModeTimeoutID) {
  4241       clearTimeout(this._exitEmergencyCbModeTimeoutID);
  4242       this._exitEmergencyCbModeTimeoutID = null;
  4244   },
  4246   _handleChangedEmergencyCbMode: function(active) {
  4247     this._isInEmergencyCbMode = active;
  4249     // Clear the existed timeout event.
  4250     this._cancelEmergencyCbModeTimeout();
  4252     // Start a new timeout event when entering the mode.
  4253     if (active) {
  4254       this._exitEmergencyCbModeTimeoutID = setTimeout(
  4255           this.exitEmergencyCbMode.bind(this), EMERGENCY_CB_MODE_TIMEOUT_MS);
  4258     let message = {rilMessageType: "emergencyCbModeChange",
  4259                    active: active,
  4260                    timeoutMs: EMERGENCY_CB_MODE_TIMEOUT_MS};
  4261     this.sendChromeMessage(message);
  4262   },
  4264   _processNetworks: function() {
  4265     let strings = this.context.Buf.readStringList();
  4266     let networks = [];
  4268     for (let i = 0; i < strings.length; i += 4) {
  4269       let network = {
  4270         longName: strings[i],
  4271         shortName: strings[i + 1],
  4272         mcc: null,
  4273         mnc: null,
  4274         state: null
  4275       };
  4277       let networkTuple = strings[i + 2];
  4278       try {
  4279         this._processNetworkTuple(networkTuple, network);
  4280       } catch (e) {
  4281         if (DEBUG) this.context.debug("Error processing operator tuple: " + e);
  4284       let state = strings[i + 3];
  4285       if (state === NETWORK_STATE_UNKNOWN) {
  4286         // TODO: looks like this might conflict in style with
  4287         // GECKO_NETWORK_STYLE_UNKNOWN / nsINetworkManager
  4288         state = GECKO_QAN_STATE_UNKNOWN;
  4291       network.state = state;
  4292       networks.push(network);
  4294     return networks;
  4295   },
  4297   /**
  4298    * The "numeric" portion of the operator info is a tuple
  4299    * containing MCC (country code) and MNC (network code).
  4300    * AFAICT, MCC should always be 3 digits, making the remaining
  4301    * portion the MNC.
  4302    */
  4303   _processNetworkTuple: function(networkTuple, network) {
  4304     let tupleLen = networkTuple.length;
  4306     if (tupleLen == 5 || tupleLen == 6) {
  4307       network.mcc = networkTuple.substr(0, 3);
  4308       network.mnc = networkTuple.substr(3);
  4309     } else {
  4310       network.mcc = null;
  4311       network.mnc = null;
  4313       throw new Error("Invalid network tuple (should be 5 or 6 digits): " + networkTuple);
  4315   },
  4317   /**
  4318    * Check if GSM radio access technology group.
  4319    */
  4320   _isGsmTechGroup: function(radioTech) {
  4321     if (!radioTech) {
  4322       return true;
  4325     switch(radioTech) {
  4326       case NETWORK_CREG_TECH_GPRS:
  4327       case NETWORK_CREG_TECH_EDGE:
  4328       case NETWORK_CREG_TECH_UMTS:
  4329       case NETWORK_CREG_TECH_HSDPA:
  4330       case NETWORK_CREG_TECH_HSUPA:
  4331       case NETWORK_CREG_TECH_HSPA:
  4332       case NETWORK_CREG_TECH_LTE:
  4333       case NETWORK_CREG_TECH_HSPAP:
  4334       case NETWORK_CREG_TECH_GSM:
  4335         return true;
  4338     return false;
  4339   },
  4341   /**
  4342    * Process radio technology change.
  4343    */
  4344   _processRadioTech: function(radioTech) {
  4345     let isCdma = !this._isGsmTechGroup(radioTech);
  4346     this.radioTech = radioTech;
  4348     if (DEBUG) {
  4349       this.context.debug("Radio tech is set to: " + GECKO_RADIO_TECH[radioTech] +
  4350                          ", it is a " + (isCdma?"cdma":"gsm") + " technology");
  4353     // We should request SIM information when
  4354     //  1. Radio state has been changed, so we are waiting for radioTech or
  4355     //  2. isCdma is different from this._isCdma.
  4356     if (this._waitingRadioTech || isCdma != this._isCdma) {
  4357       this._isCdma = isCdma;
  4358       this._waitingRadioTech = false;
  4359       if (this._isCdma) {
  4360         this.getDeviceIdentity();
  4361       } else {
  4362         this.getIMEI();
  4363         this.getIMEISV();
  4365       this.getICCStatus();
  4367   },
  4369   /**
  4370    * Helper for returning the TOA for the given dial string.
  4371    */
  4372   _toaFromString: function(number) {
  4373     let toa = TOA_UNKNOWN;
  4374     if (number && number.length > 0 && number[0] == '+') {
  4375       toa = TOA_INTERNATIONAL;
  4377     return toa;
  4378   },
  4380   /**
  4381    * Helper for translating basic service group to call forwarding service class
  4382    * parameter.
  4383    */
  4384   _siToServiceClass: function(si) {
  4385     if (!si) {
  4386       return ICC_SERVICE_CLASS_NONE;
  4389     let serviceCode = parseInt(si, 10);
  4390     switch (serviceCode) {
  4391       case 10:
  4392         return ICC_SERVICE_CLASS_SMS + ICC_SERVICE_CLASS_FAX  + ICC_SERVICE_CLASS_VOICE;
  4393       case 11:
  4394         return ICC_SERVICE_CLASS_VOICE;
  4395       case 12:
  4396         return ICC_SERVICE_CLASS_SMS + ICC_SERVICE_CLASS_FAX;
  4397       case 13:
  4398         return ICC_SERVICE_CLASS_FAX;
  4399       case 16:
  4400         return ICC_SERVICE_CLASS_SMS;
  4401       case 19:
  4402         return ICC_SERVICE_CLASS_FAX + ICC_SERVICE_CLASS_VOICE;
  4403       case 21:
  4404         return ICC_SERVICE_CLASS_PAD + ICC_SERVICE_CLASS_DATA_ASYNC;
  4405       case 22:
  4406         return ICC_SERVICE_CLASS_PACKET + ICC_SERVICE_CLASS_DATA_SYNC;
  4407       case 25:
  4408         return ICC_SERVICE_CLASS_DATA_ASYNC;
  4409       case 26:
  4410         return ICC_SERVICE_CLASS_DATA_SYNC + SERVICE_CLASS_VOICE;
  4411       case 99:
  4412         return ICC_SERVICE_CLASS_PACKET;
  4413       default:
  4414         return ICC_SERVICE_CLASS_NONE;
  4416   },
  4418   /**
  4419    * @param message A decoded SMS-DELIVER message.
  4421    * @see 3GPP TS 31.111 section 7.1.1
  4422    */
  4423   dataDownloadViaSMSPP: function(message) {
  4424     let Buf = this.context.Buf;
  4425     let GsmPDUHelper = this.context.GsmPDUHelper;
  4427     let options = {
  4428       pid: message.pid,
  4429       dcs: message.dcs,
  4430       encoding: message.encoding,
  4431     };
  4432     Buf.newParcel(REQUEST_STK_SEND_ENVELOPE_WITH_STATUS, options);
  4434     Buf.seekIncoming(-1 * (Buf.getCurrentParcelSize() - Buf.getReadAvailable()
  4435                            - 2 * Buf.UINT32_SIZE)); // Skip response_type & request_type.
  4436     let messageStringLength = Buf.readInt32(); // In semi-octets
  4437     let smscLength = GsmPDUHelper.readHexOctet(); // In octets, inclusive of TOA
  4438     let tpduLength = (messageStringLength / 2) - (smscLength + 1); // In octets
  4440     // Device identities: 4 bytes
  4441     // Address: 0 or (2 + smscLength)
  4442     // SMS TPDU: (2 or 3) + tpduLength
  4443     let berLen = 4 +
  4444                  (smscLength ? (2 + smscLength) : 0) +
  4445                  (tpduLength <= 127 ? 2 : 3) + tpduLength; // In octets
  4447     let parcelLength = (berLen <= 127 ? 2 : 3) + berLen; // In octets
  4448     Buf.writeInt32(parcelLength * 2); // In semi-octets
  4450     // Write a BER-TLV
  4451     GsmPDUHelper.writeHexOctet(BER_SMS_PP_DOWNLOAD_TAG);
  4452     if (berLen > 127) {
  4453       GsmPDUHelper.writeHexOctet(0x81);
  4455     GsmPDUHelper.writeHexOctet(berLen);
  4457     // Device Identifies-TLV
  4458     GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_DEVICE_ID |
  4459                                COMPREHENSIONTLV_FLAG_CR);
  4460     GsmPDUHelper.writeHexOctet(0x02);
  4461     GsmPDUHelper.writeHexOctet(STK_DEVICE_ID_NETWORK);
  4462     GsmPDUHelper.writeHexOctet(STK_DEVICE_ID_SIM);
  4464     // Address-TLV
  4465     if (smscLength) {
  4466       GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_ADDRESS);
  4467       GsmPDUHelper.writeHexOctet(smscLength);
  4468       Buf.copyIncomingToOutgoing(Buf.PDU_HEX_OCTET_SIZE * smscLength);
  4471     // SMS TPDU-TLV
  4472     GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_SMS_TPDU |
  4473                                COMPREHENSIONTLV_FLAG_CR);
  4474     if (tpduLength > 127) {
  4475       GsmPDUHelper.writeHexOctet(0x81);
  4477     GsmPDUHelper.writeHexOctet(tpduLength);
  4478     Buf.copyIncomingToOutgoing(Buf.PDU_HEX_OCTET_SIZE * tpduLength);
  4480     // Write 2 string delimitors for the total string length must be even.
  4481     Buf.writeStringDelimiter(0);
  4483     Buf.sendParcel();
  4484   },
  4486   /**
  4487    * @param success A boolean value indicating the result of previous
  4488    *                SMS-DELIVER message handling.
  4489    * @param responsePduLen ICC IO response PDU length in octets.
  4490    * @param options An object that contains four attributes: `pid`, `dcs`,
  4491    *                `encoding` and `responsePduLen`.
  4493    * @see 3GPP TS 23.040 section 9.2.2.1a
  4494    */
  4495   acknowledgeIncomingGsmSmsWithPDU: function(success, responsePduLen, options) {
  4496     let Buf = this.context.Buf;
  4497     let GsmPDUHelper = this.context.GsmPDUHelper;
  4499     Buf.newParcel(REQUEST_ACKNOWLEDGE_INCOMING_GSM_SMS_WITH_PDU);
  4501     // Two strings.
  4502     Buf.writeInt32(2);
  4504     // String 1: Success
  4505     Buf.writeString(success ? "1" : "0");
  4507     // String 2: RP-ACK/RP-ERROR PDU
  4508     Buf.writeInt32(2 * (responsePduLen + (success ? 5 : 6))); // In semi-octet
  4509     // 1. TP-MTI & TP-UDHI
  4510     GsmPDUHelper.writeHexOctet(PDU_MTI_SMS_DELIVER);
  4511     if (!success) {
  4512       // 2. TP-FCS
  4513       GsmPDUHelper.writeHexOctet(PDU_FCS_USIM_DATA_DOWNLOAD_ERROR);
  4515     // 3. TP-PI
  4516     GsmPDUHelper.writeHexOctet(PDU_PI_USER_DATA_LENGTH |
  4517                                PDU_PI_DATA_CODING_SCHEME |
  4518                                PDU_PI_PROTOCOL_IDENTIFIER);
  4519     // 4. TP-PID
  4520     GsmPDUHelper.writeHexOctet(options.pid);
  4521     // 5. TP-DCS
  4522     GsmPDUHelper.writeHexOctet(options.dcs);
  4523     // 6. TP-UDL
  4524     if (options.encoding == PDU_DCS_MSG_CODING_7BITS_ALPHABET) {
  4525       GsmPDUHelper.writeHexOctet(Math.floor(responsePduLen * 8 / 7));
  4526     } else {
  4527       GsmPDUHelper.writeHexOctet(responsePduLen);
  4529     // TP-UD
  4530     Buf.copyIncomingToOutgoing(Buf.PDU_HEX_OCTET_SIZE * responsePduLen);
  4531     // Write 2 string delimitors for the total string length must be even.
  4532     Buf.writeStringDelimiter(0);
  4534     Buf.sendParcel();
  4535   },
  4537   /**
  4538    * @param message A decoded SMS-DELIVER message.
  4539    */
  4540   writeSmsToSIM: function(message) {
  4541     let Buf = this.context.Buf;
  4542     let GsmPDUHelper = this.context.GsmPDUHelper;
  4544     Buf.newParcel(REQUEST_WRITE_SMS_TO_SIM);
  4546     // Write EFsms Status
  4547     Buf.writeInt32(EFSMS_STATUS_FREE);
  4549     Buf.seekIncoming(-1 * (Buf.getCurrentParcelSize() - Buf.getReadAvailable()
  4550                            - 2 * Buf.UINT32_SIZE)); // Skip response_type & request_type.
  4551     let messageStringLength = Buf.readInt32(); // In semi-octets
  4552     let smscLength = GsmPDUHelper.readHexOctet(); // In octets, inclusive of TOA
  4553     let pduLength = (messageStringLength / 2) - (smscLength + 1); // In octets
  4555     // 1. Write PDU first.
  4556     if (smscLength > 0) {
  4557       Buf.seekIncoming(smscLength * Buf.PDU_HEX_OCTET_SIZE);
  4559     // Write EFsms PDU string length
  4560     Buf.writeInt32(2 * pduLength); // In semi-octets
  4561     if (pduLength) {
  4562       Buf.copyIncomingToOutgoing(Buf.PDU_HEX_OCTET_SIZE * pduLength);
  4564     // Write 2 string delimitors for the total string length must be even.
  4565     Buf.writeStringDelimiter(0);
  4567     // 2. Write SMSC
  4568     // Write EFsms SMSC string length
  4569     Buf.writeInt32(2 * (smscLength + 1)); // Plus smscLength itself, in semi-octets
  4570     // Write smscLength
  4571     GsmPDUHelper.writeHexOctet(smscLength);
  4572     // Write TOA & SMSC Address
  4573     if (smscLength) {
  4574       Buf.seekIncoming(-1 * (Buf.getCurrentParcelSize() - Buf.getReadAvailable()
  4575                              - 2 * Buf.UINT32_SIZE // Skip response_type, request_type.
  4576                              - 2 * Buf.PDU_HEX_OCTET_SIZE)); // Skip messageStringLength & smscLength.
  4577       Buf.copyIncomingToOutgoing(Buf.PDU_HEX_OCTET_SIZE * smscLength);
  4579     // Write 2 string delimitors for the total string length must be even.
  4580     Buf.writeStringDelimiter(0);
  4582     Buf.sendParcel();
  4583   },
  4585   /**
  4586    * Helper to delegate the received sms segment to RadioInterface to process.
  4588    * @param message
  4589    *        Received sms message.
  4591    * @return MOZ_FCS_WAIT_FOR_EXPLICIT_ACK
  4592    */
  4593   _processSmsMultipart: function(message) {
  4594     message.rilMessageType = "sms-received";
  4596     this.sendChromeMessage(message);
  4598     return MOZ_FCS_WAIT_FOR_EXPLICIT_ACK;
  4599   },
  4601   /**
  4602    * Helper for processing SMS-STATUS-REPORT PDUs.
  4604    * @param length
  4605    *        Length of SMS string in the incoming parcel.
  4607    * @return A failure cause defined in 3GPP 23.040 clause 9.2.3.22.
  4608    */
  4609   _processSmsStatusReport: function(length) {
  4610     let [message, result] = this.context.GsmPDUHelper.processReceivedSms(length);
  4611     if (!message) {
  4612       if (DEBUG) this.context.debug("invalid SMS-STATUS-REPORT");
  4613       return PDU_FCS_UNSPECIFIED;
  4616     let options = this._pendingSentSmsMap[message.messageRef];
  4617     if (!options) {
  4618       if (DEBUG) this.context.debug("no pending SMS-SUBMIT message");
  4619       return PDU_FCS_OK;
  4622     let status = message.status;
  4624     // 3GPP TS 23.040 9.2.3.15 `The MS shall interpret any reserved values as
  4625     // "Service Rejected"(01100011) but shall store them exactly as received.`
  4626     if ((status >= 0x80)
  4627         || ((status >= PDU_ST_0_RESERVED_BEGIN)
  4628             && (status < PDU_ST_0_SC_SPECIFIC_BEGIN))
  4629         || ((status >= PDU_ST_1_RESERVED_BEGIN)
  4630             && (status < PDU_ST_1_SC_SPECIFIC_BEGIN))
  4631         || ((status >= PDU_ST_2_RESERVED_BEGIN)
  4632             && (status < PDU_ST_2_SC_SPECIFIC_BEGIN))
  4633         || ((status >= PDU_ST_3_RESERVED_BEGIN)
  4634             && (status < PDU_ST_3_SC_SPECIFIC_BEGIN))
  4635         ) {
  4636       status = PDU_ST_3_SERVICE_REJECTED;
  4639     // Pending. Waiting for next status report.
  4640     if ((status >>> 5) == 0x01) {
  4641       if (DEBUG) this.context.debug("SMS-STATUS-REPORT: delivery still pending");
  4642       return PDU_FCS_OK;
  4645     delete this._pendingSentSmsMap[message.messageRef];
  4647     let deliveryStatus = ((status >>> 5) === 0x00)
  4648                        ? GECKO_SMS_DELIVERY_STATUS_SUCCESS
  4649                        : GECKO_SMS_DELIVERY_STATUS_ERROR;
  4650     this.sendChromeMessage({
  4651       rilMessageType: options.rilMessageType,
  4652       rilMessageToken: options.rilMessageToken,
  4653       deliveryStatus: deliveryStatus
  4654     });
  4656     return PDU_FCS_OK;
  4657   },
  4659   /**
  4660    * Helper for processing CDMA SMS Delivery Acknowledgment Message
  4662    * @param message
  4663    *        decoded SMS Delivery ACK message from CdmaPDUHelper.
  4665    * @return A failure cause defined in 3GPP 23.040 clause 9.2.3.22.
  4666    */
  4667   _processCdmaSmsStatusReport: function(message) {
  4668     let options = this._pendingSentSmsMap[message.msgId];
  4669     if (!options) {
  4670       if (DEBUG) this.context.debug("no pending SMS-SUBMIT message");
  4671       return PDU_FCS_OK;
  4674     if (message.errorClass === 2) {
  4675       if (DEBUG) {
  4676         this.context.debug("SMS-STATUS-REPORT: delivery still pending, " +
  4677                            "msgStatus: " + message.msgStatus);
  4679       return PDU_FCS_OK;
  4682     delete this._pendingSentSmsMap[message.msgId];
  4684     if (message.errorClass === -1 && message.body) {
  4685       // Process as normal incoming SMS, if errorClass is invalid
  4686       // but message body is available.
  4687       return this._processSmsMultipart(message);
  4690     let deliveryStatus = (message.errorClass === 0)
  4691                        ? GECKO_SMS_DELIVERY_STATUS_SUCCESS
  4692                        : GECKO_SMS_DELIVERY_STATUS_ERROR;
  4693     this.sendChromeMessage({
  4694       rilMessageType: options.rilMessageType,
  4695       rilMessageToken: options.rilMessageToken,
  4696       deliveryStatus: deliveryStatus
  4697     });
  4699     return PDU_FCS_OK;
  4700   },
  4702   /**
  4703    * Helper for processing CDMA SMS WAP Push Message
  4705    * @param message
  4706    *        decoded WAP message from CdmaPDUHelper.
  4708    * @return A failure cause defined in 3GPP 23.040 clause 9.2.3.22.
  4709    */
  4710   _processCdmaSmsWapPush: function(message) {
  4711     if (!message.data) {
  4712       if (DEBUG) this.context.debug("no data inside WAP Push message.");
  4713       return PDU_FCS_OK;
  4716     // See 6.5. MAPPING OF WDP TO CDMA SMS in WAP-295-WDP.
  4717     //
  4718     // Field             | Length (bits)
  4719     // -----------------------------------------
  4720     // MSG_TYPE          | 8
  4721     // TOTAL_SEGMENTS    | 8
  4722     // SEGMENT_NUMBER    | 8
  4723     // DATAGRAM          | (NUM_FIELDS – 3) * 8
  4724     let index = 0;
  4725     if (message.data[index++] !== 0) {
  4726       if (DEBUG) this.context.debug("Ignore a WAP Message which is not WDP.");
  4727       return PDU_FCS_OK;
  4730     // 1. Originator Address in SMS-TL + Message_Id in SMS-TS are used to identify a unique WDP datagram.
  4731     // 2. TOTAL_SEGMENTS, SEGMENT_NUMBER are used to verify that a complete
  4732     //    datagram has been received and is ready to be passed to a higher layer.
  4733     message.header = {
  4734       segmentRef:     message.msgId,
  4735       segmentMaxSeq:  message.data[index++],
  4736       segmentSeq:     message.data[index++] + 1 // It's zero-based in CDMA WAP Push.
  4737     };
  4739     if (message.header.segmentSeq > message.header.segmentMaxSeq) {
  4740       if (DEBUG) this.context.debug("Wrong WDP segment info.");
  4741       return PDU_FCS_OK;
  4744     // Ports are only specified in 1st segment.
  4745     if (message.header.segmentSeq == 1) {
  4746       message.header.originatorPort = message.data[index++] << 8;
  4747       message.header.originatorPort |= message.data[index++];
  4748       message.header.destinationPort = message.data[index++] << 8;
  4749       message.header.destinationPort |= message.data[index++];
  4752     message.data = message.data.subarray(index);
  4754     return this._processSmsMultipart(message);
  4755   },
  4757   /**
  4758    * Helper for processing sent multipart SMS.
  4759    */
  4760   _processSentSmsSegment: function(options) {
  4761     // Setup attributes for sending next segment
  4762     let next = options.segmentSeq;
  4763     options.body = options.segments[next].body;
  4764     options.encodedBodyLength = options.segments[next].encodedBodyLength;
  4765     options.segmentSeq = next + 1;
  4767     this.sendSMS(options);
  4768   },
  4770   /**
  4771    * Helper for processing result of send SMS.
  4773    * @param length
  4774    *        Length of SMS string in the incoming parcel.
  4775    * @param options
  4776    *        Sms information.
  4777    */
  4778   _processSmsSendResult: function(length, options) {
  4779     if (options.rilRequestError) {
  4780       if (DEBUG) {
  4781         this.context.debug("_processSmsSendResult: rilRequestError = " +
  4782                            options.rilRequestError);
  4784       switch (options.rilRequestError) {
  4785         case ERROR_SMS_SEND_FAIL_RETRY:
  4786           if (options.retryCount < SMS_RETRY_MAX) {
  4787             options.retryCount++;
  4788             // TODO: bug 736702 TP-MR, retry interval, retry timeout
  4789             this.sendSMS(options);
  4790             break;
  4792           // Fallback to default error handling if it meets max retry count.
  4793           // Fall through.
  4794         default:
  4795           this.sendChromeMessage({
  4796             rilMessageType: options.rilMessageType,
  4797             rilMessageToken: options.rilMessageToken,
  4798             errorMsg: options.rilRequestError,
  4799           });
  4800           break;
  4802       return;
  4805     let Buf = this.context.Buf;
  4806     options.messageRef = Buf.readInt32();
  4807     options.ackPDU = Buf.readString();
  4808     options.errorCode = Buf.readInt32();
  4810     if ((options.segmentMaxSeq > 1)
  4811         && (options.segmentSeq < options.segmentMaxSeq)) {
  4812       // Not last segment
  4813       this._processSentSmsSegment(options);
  4814     } else {
  4815       // Last segment sent with success.
  4816       if (options.requestStatusReport) {
  4817         if (DEBUG) {
  4818           this.context.debug("waiting SMS-STATUS-REPORT for messageRef " +
  4819                              options.messageRef);
  4821         this._pendingSentSmsMap[options.messageRef] = options;
  4824       this.sendChromeMessage({
  4825         rilMessageType: options.rilMessageType,
  4826         rilMessageToken: options.rilMessageToken,
  4827       });
  4829   },
  4831   _processReceivedSmsCbPage: function(original) {
  4832     if (original.numPages <= 1) {
  4833       if (original.body) {
  4834         original.fullBody = original.body;
  4835         delete original.body;
  4836       } else if (original.data) {
  4837         original.fullData = original.data;
  4838         delete original.data;
  4840       return original;
  4843     // Hash = <serial>:<mcc>:<mnc>:<lac>:<cid>
  4844     let hash = original.serial + ":" + this.iccInfo.mcc + ":"
  4845                + this.iccInfo.mnc + ":";
  4846     switch (original.geographicalScope) {
  4847       case CB_GSM_GEOGRAPHICAL_SCOPE_CELL_WIDE_IMMEDIATE:
  4848       case CB_GSM_GEOGRAPHICAL_SCOPE_CELL_WIDE:
  4849         hash += this.voiceRegistrationState.cell.gsmLocationAreaCode + ":"
  4850              + this.voiceRegistrationState.cell.gsmCellId;
  4851         break;
  4852       case CB_GSM_GEOGRAPHICAL_SCOPE_LOCATION_AREA_WIDE:
  4853         hash += this.voiceRegistrationState.cell.gsmLocationAreaCode + ":";
  4854         break;
  4855       default:
  4856         hash += ":";
  4857         break;
  4860     let index = original.pageIndex;
  4862     let options = this._receivedSmsCbPagesMap[hash];
  4863     if (!options) {
  4864       options = original;
  4865       this._receivedSmsCbPagesMap[hash] = options;
  4867       options.receivedPages = 0;
  4868       options.pages = [];
  4869     } else if (options.pages[index]) {
  4870       // Duplicated page?
  4871       if (DEBUG) {
  4872         this.context.debug("Got duplicated page no." + index +
  4873                            " of a multipage SMSCB: " + JSON.stringify(original));
  4875       return null;
  4878     if (options.encoding == PDU_DCS_MSG_CODING_8BITS_ALPHABET) {
  4879       options.pages[index] = original.data;
  4880       delete original.data;
  4881     } else {
  4882       options.pages[index] = original.body;
  4883       delete original.body;
  4885     options.receivedPages++;
  4886     if (options.receivedPages < options.numPages) {
  4887       if (DEBUG) {
  4888         this.context.debug("Got page no." + index + " of a multipage SMSCB: " +
  4889                            JSON.stringify(options));
  4891       return null;
  4894     // Remove from map
  4895     delete this._receivedSmsCbPagesMap[hash];
  4897     // Rebuild full body
  4898     if (options.encoding == PDU_DCS_MSG_CODING_8BITS_ALPHABET) {
  4899       // Uint8Array doesn't have `concat`, so we have to merge all pages by hand.
  4900       let fullDataLen = 0;
  4901       for (let i = 1; i <= options.numPages; i++) {
  4902         fullDataLen += options.pages[i].length;
  4905       options.fullData = new Uint8Array(fullDataLen);
  4906       for (let d= 0, i = 1; i <= options.numPages; i++) {
  4907         let data = options.pages[i];
  4908         for (let j = 0; j < data.length; j++) {
  4909           options.fullData[d++] = data[j];
  4912     } else {
  4913       options.fullBody = options.pages.join("");
  4916     if (DEBUG) {
  4917       this.context.debug("Got full multipage SMSCB: " + JSON.stringify(options));
  4920     return options;
  4921   },
  4923   _mergeCellBroadcastConfigs: function(list, from, to) {
  4924     if (!list) {
  4925       return [from, to];
  4928     for (let i = 0, f1, t1; i < list.length;) {
  4929       f1 = list[i++];
  4930       t1 = list[i++];
  4931       if (to == f1) {
  4932         // ...[from]...[to|f1]...(t1)
  4933         list[i - 2] = from;
  4934         return list;
  4937       if (to < f1) {
  4938         // ...[from]...(to)...[f1] or ...[from]...(to)[f1]
  4939         if (i > 2) {
  4940           // Not the first range pair, merge three arrays.
  4941           return list.slice(0, i - 2).concat([from, to]).concat(list.slice(i - 2));
  4942         } else {
  4943           return [from, to].concat(list);
  4947       if (from > t1) {
  4948         // ...[f1]...(t1)[from] or ...[f1]...(t1)...[from]
  4949         continue;
  4952       // Have overlap or merge-able adjacency with [f1]...(t1). Replace it
  4953       // with [min(from, f1)]...(max(to, t1)).
  4955       let changed = false;
  4956       if (from < f1) {
  4957         // [from]...[f1]...(t1) or [from][f1]...(t1)
  4958         // Save minimum from value.
  4959         list[i - 2] = from;
  4960         changed = true;
  4963       if (to <= t1) {
  4964         // [from]...[to](t1) or [from]...(to|t1)
  4965         // Can't have further merge-able adjacency. Return.
  4966         return list;
  4969       // Try merging possible next adjacent range.
  4970       let j = i;
  4971       for (let f2, t2; j < list.length;) {
  4972         f2 = list[j++];
  4973         t2 = list[j++];
  4974         if (to > t2) {
  4975           // [from]...[f2]...[t2]...(to) or [from]...[f2]...[t2](to)
  4976           // Merge next adjacent range again.
  4977           continue;
  4980         if (to < t2) {
  4981           if (to < f2) {
  4982             // [from]...(to)[f2] or [from]...(to)...[f2]
  4983             // Roll back and give up.
  4984             j -= 2;
  4985           } else if (to < t2) {
  4986             // [from]...[to|f2]...(t2), or [from]...[f2]...[to](t2)
  4987             // Merge to [from]...(t2) and give up.
  4988             to = t2;
  4992         break;
  4995       // Save maximum to value.
  4996       list[i - 1] = to;
  4998       if (j != i) {
  4999         // Remove merged adjacent ranges.
  5000         let ret = list.slice(0, i);
  5001         if (j < list.length) {
  5002           ret = ret.concat(list.slice(j));
  5004         return ret;
  5007       return list;
  5010     // Append to the end.
  5011     list.push(from);
  5012     list.push(to);
  5014     return list;
  5015   },
  5017   _isCellBroadcastConfigReady: function() {
  5018     if (!("MMI" in this.cellBroadcastConfigs)) {
  5019       return false;
  5022     // CBMI should be ready in GSM.
  5023     if (!this._isCdma &&
  5024         (!("CBMI" in this.cellBroadcastConfigs) ||
  5025          !("CBMID" in this.cellBroadcastConfigs) ||
  5026          !("CBMIR" in this.cellBroadcastConfigs))) {
  5027       return false;
  5030     return true;
  5031   },
  5033   /**
  5034    * Merge all members of cellBroadcastConfigs into mergedCellBroadcastConfig.
  5035    */
  5036   _mergeAllCellBroadcastConfigs: function() {
  5037     if (!this._isCellBroadcastConfigReady()) {
  5038       if (DEBUG) {
  5039         this.context.debug("cell broadcast configs not ready, waiting ...");
  5041       return;
  5044     // Prepare cell broadcast config. CBMI* are only used in GSM.
  5045     let usedCellBroadcastConfigs = {MMI: this.cellBroadcastConfigs.MMI};
  5046     if (!this._isCdma) {
  5047       usedCellBroadcastConfigs.CBMI = this.cellBroadcastConfigs.CBMI;
  5048       usedCellBroadcastConfigs.CBMID = this.cellBroadcastConfigs.CBMID;
  5049       usedCellBroadcastConfigs.CBMIR = this.cellBroadcastConfigs.CBMIR;
  5052     if (DEBUG) {
  5053       this.context.debug("Cell Broadcast search lists: " +
  5054                          JSON.stringify(usedCellBroadcastConfigs));
  5057     let list = null;
  5058     for each (let ll in usedCellBroadcastConfigs) {
  5059       if (ll == null) {
  5060         continue;
  5063       for (let i = 0; i < ll.length; i += 2) {
  5064         list = this._mergeCellBroadcastConfigs(list, ll[i], ll[i + 1]);
  5068     if (DEBUG) {
  5069       this.context.debug("Cell Broadcast search lists(merged): " +
  5070                          JSON.stringify(list));
  5072     this.mergedCellBroadcastConfig = list;
  5073     this.updateCellBroadcastConfig();
  5074   },
  5076   /**
  5077    * Check whether search list from settings is settable by MMI, that is,
  5078    * whether the range is bounded in any entries of CB_NON_MMI_SETTABLE_RANGES.
  5079    */
  5080   _checkCellBroadcastMMISettable: function(from, to) {
  5081     if ((to <= from) || (from >= 65536) || (from < 0)) {
  5082       return false;
  5085     if (!this._isCdma) {
  5086       // GSM not settable ranges.
  5087       for (let i = 0, f, t; i < CB_NON_MMI_SETTABLE_RANGES.length;) {
  5088         f = CB_NON_MMI_SETTABLE_RANGES[i++];
  5089         t = CB_NON_MMI_SETTABLE_RANGES[i++];
  5090         if ((from < t) && (to > f)) {
  5091           // Have overlap.
  5092           return false;
  5097     return true;
  5098   },
  5100   /**
  5101    * Convert Cell Broadcast settings string into search list.
  5102    */
  5103   _convertCellBroadcastSearchList: function(searchListStr) {
  5104     let parts = searchListStr && searchListStr.split(",");
  5105     if (!parts) {
  5106       return null;
  5109     let list = null;
  5110     let result, from, to;
  5111     for (let range of parts) {
  5112       // Match "12" or "12-34". The result will be ["12", "12", null] or
  5113       // ["12-34", "12", "34"].
  5114       result = range.match(/^(\d+)(?:-(\d+))?$/);
  5115       if (!result) {
  5116         throw "Invalid format";
  5119       from = parseInt(result[1], 10);
  5120       to = (result[2]) ? parseInt(result[2], 10) + 1 : from + 1;
  5121       if (!this._checkCellBroadcastMMISettable(from, to)) {
  5122         throw "Invalid range";
  5125       if (list == null) {
  5126         list = [];
  5128       list.push(from);
  5129       list.push(to);
  5132     return list;
  5133   },
  5135   /**
  5136    * Handle incoming messages from the main UI thread.
  5138    * @param message
  5139    *        Object containing the message. Messages are supposed
  5140    */
  5141   handleChromeMessage: function(message) {
  5142     if (DEBUG) {
  5143       this.context.debug("Received chrome message " + JSON.stringify(message));
  5145     let method = this[message.rilMessageType];
  5146     if (typeof method != "function") {
  5147       if (DEBUG) {
  5148         this.context.debug("Don't know what to do with message " +
  5149                            JSON.stringify(message));
  5151       return;
  5153     method.call(this, message);
  5154   },
  5156   /**
  5157    * Get a list of current voice calls.
  5158    */
  5159   enumerateCalls: function(options) {
  5160     if (DEBUG) this.context.debug("Sending all current calls");
  5161     let calls = [];
  5162     for each (let call in this.currentCalls) {
  5163       calls.push(call);
  5165     options.calls = calls;
  5166     this.sendChromeMessage(options);
  5167   },
  5169   /**
  5170    * Process STK Proactive Command.
  5171    */
  5172   processStkProactiveCommand: function() {
  5173     let Buf = this.context.Buf;
  5174     let length = Buf.readInt32();
  5175     let berTlv;
  5176     try {
  5177       berTlv = this.context.BerTlvHelper.decode(length / 2);
  5178     } catch (e) {
  5179       if (DEBUG) this.context.debug("processStkProactiveCommand : " + e);
  5180       this.sendStkTerminalResponse({
  5181         resultCode: STK_RESULT_CMD_DATA_NOT_UNDERSTOOD});
  5182       return;
  5185     Buf.readStringDelimiter(length);
  5187     let ctlvs = berTlv.value;
  5188     let ctlv = this.context.StkProactiveCmdHelper.searchForTag(
  5189         COMPREHENSIONTLV_TAG_COMMAND_DETAILS, ctlvs);
  5190     if (!ctlv) {
  5191       this.sendStkTerminalResponse({
  5192         resultCode: STK_RESULT_CMD_DATA_NOT_UNDERSTOOD});
  5193       throw new Error("Can't find COMMAND_DETAILS ComprehensionTlv");
  5196     let cmdDetails = ctlv.value;
  5197     if (DEBUG) {
  5198       this.context.debug("commandNumber = " + cmdDetails.commandNumber +
  5199                          " typeOfCommand = " + cmdDetails.typeOfCommand.toString(16) +
  5200                          " commandQualifier = " + cmdDetails.commandQualifier);
  5203     // STK_CMD_MORE_TIME need not to propagate event to chrome.
  5204     if (cmdDetails.typeOfCommand == STK_CMD_MORE_TIME) {
  5205       this.sendStkTerminalResponse({
  5206         command: cmdDetails,
  5207         resultCode: STK_RESULT_OK});
  5208       return;
  5211     cmdDetails.rilMessageType = "stkcommand";
  5212     cmdDetails.options =
  5213       this.context.StkCommandParamsFactory.createParam(cmdDetails, ctlvs);
  5214     this.sendChromeMessage(cmdDetails);
  5215   },
  5217   /**
  5218    * Send messages to the main thread.
  5219    */
  5220   sendChromeMessage: function(message) {
  5221     message.rilMessageClientId = this.context.clientId;
  5222     postMessage(message);
  5223   },
  5225   /**
  5226    * Handle incoming requests from the RIL. We find the method that
  5227    * corresponds to the request type. Incidentally, the request type
  5228    * _is_ the method name, so that's easy.
  5229    */
  5231   handleParcel: function(request_type, length, options) {
  5232     let method = this[request_type];
  5233     if (typeof method == "function") {
  5234       if (DEBUG) this.context.debug("Handling parcel as " + method.name);
  5235       method.call(this, length, options);
  5238 };
  5240 RilObject.prototype[REQUEST_GET_SIM_STATUS] = function REQUEST_GET_SIM_STATUS(length, options) {
  5241   if (options.rilRequestError) {
  5242     return;
  5245   let iccStatus = {};
  5246   let Buf = this.context.Buf;
  5247   iccStatus.cardState = Buf.readInt32(); // CARD_STATE_*
  5248   iccStatus.universalPINState = Buf.readInt32(); // CARD_PINSTATE_*
  5249   iccStatus.gsmUmtsSubscriptionAppIndex = Buf.readInt32();
  5250   iccStatus.cdmaSubscriptionAppIndex = Buf.readInt32();
  5251   if (!this.v5Legacy) {
  5252     iccStatus.imsSubscriptionAppIndex = Buf.readInt32();
  5255   let apps_length = Buf.readInt32();
  5256   if (apps_length > CARD_MAX_APPS) {
  5257     apps_length = CARD_MAX_APPS;
  5260   iccStatus.apps = [];
  5261   for (let i = 0 ; i < apps_length ; i++) {
  5262     iccStatus.apps.push({
  5263       app_type:       Buf.readInt32(), // CARD_APPTYPE_*
  5264       app_state:      Buf.readInt32(), // CARD_APPSTATE_*
  5265       perso_substate: Buf.readInt32(), // CARD_PERSOSUBSTATE_*
  5266       aid:            Buf.readString(),
  5267       app_label:      Buf.readString(),
  5268       pin1_replaced:  Buf.readInt32(),
  5269       pin1:           Buf.readInt32(),
  5270       pin2:           Buf.readInt32()
  5271     });
  5272     if (RILQUIRKS_SIM_APP_STATE_EXTRA_FIELDS) {
  5273       Buf.readInt32();
  5274       Buf.readInt32();
  5275       Buf.readInt32();
  5276       Buf.readInt32();
  5280   if (DEBUG) this.context.debug("iccStatus: " + JSON.stringify(iccStatus));
  5281   this._processICCStatus(iccStatus);
  5282 };
  5283 RilObject.prototype[REQUEST_ENTER_SIM_PIN] = function REQUEST_ENTER_SIM_PIN(length, options) {
  5284   this._processEnterAndChangeICCResponses(length, options);
  5285 };
  5286 RilObject.prototype[REQUEST_ENTER_SIM_PUK] = function REQUEST_ENTER_SIM_PUK(length, options) {
  5287   this._processEnterAndChangeICCResponses(length, options);
  5288 };
  5289 RilObject.prototype[REQUEST_ENTER_SIM_PIN2] = function REQUEST_ENTER_SIM_PIN2(length, options) {
  5290   this._processEnterAndChangeICCResponses(length, options);
  5291 };
  5292 RilObject.prototype[REQUEST_ENTER_SIM_PUK2] = function REQUEST_ENTER_SIM_PUK(length, options) {
  5293   this._processEnterAndChangeICCResponses(length, options);
  5294 };
  5295 RilObject.prototype[REQUEST_CHANGE_SIM_PIN] = function REQUEST_CHANGE_SIM_PIN(length, options) {
  5296   this._processEnterAndChangeICCResponses(length, options);
  5297 };
  5298 RilObject.prototype[REQUEST_CHANGE_SIM_PIN2] = function REQUEST_CHANGE_SIM_PIN2(length, options) {
  5299   this._processEnterAndChangeICCResponses(length, options);
  5300 };
  5301 RilObject.prototype[REQUEST_ENTER_NETWORK_DEPERSONALIZATION_CODE] =
  5302   function REQUEST_ENTER_NETWORK_DEPERSONALIZATION_CODE(length, options) {
  5303   this._processEnterAndChangeICCResponses(length, options);
  5304 };
  5305 RilObject.prototype[REQUEST_GET_CURRENT_CALLS] = function REQUEST_GET_CURRENT_CALLS(length, options) {
  5306   if (options.rilRequestError) {
  5307     return;
  5310   let Buf = this.context.Buf;
  5311   let calls_length = 0;
  5312   // The RIL won't even send us the length integer if there are no active calls.
  5313   // So only read this integer if the parcel actually has it.
  5314   if (length) {
  5315     calls_length = Buf.readInt32();
  5317   if (!calls_length) {
  5318     this._processCalls(null);
  5319     return;
  5322   let calls = {};
  5323   for (let i = 0; i < calls_length; i++) {
  5324     let call = {};
  5326     // Extra uint32 field to get correct callIndex and rest of call data for
  5327     // call waiting feature.
  5328     if (RILQUIRKS_EXTRA_UINT32_2ND_CALL && i > 0) {
  5329       Buf.readInt32();
  5332     call.state          = Buf.readInt32(); // CALL_STATE_*
  5333     call.callIndex      = Buf.readInt32(); // GSM index (1-based)
  5334     call.toa            = Buf.readInt32();
  5335     call.isMpty         = Boolean(Buf.readInt32());
  5336     call.isMT           = Boolean(Buf.readInt32());
  5337     call.als            = Buf.readInt32();
  5338     call.isVoice        = Boolean(Buf.readInt32());
  5339     call.isVoicePrivacy = Boolean(Buf.readInt32());
  5340     if (RILQUIRKS_CALLSTATE_EXTRA_UINT32) {
  5341       Buf.readInt32();
  5343     call.number             = Buf.readString(); //TODO munge with TOA
  5344     call.numberPresentation = Buf.readInt32(); // CALL_PRESENTATION_*
  5345     call.name               = Buf.readString();
  5346     call.namePresentation   = Buf.readInt32();
  5348     call.uusInfo = null;
  5349     let uusInfoPresent = Buf.readInt32();
  5350     if (uusInfoPresent == 1) {
  5351       call.uusInfo = {
  5352         type:     Buf.readInt32(),
  5353         dcs:      Buf.readInt32(),
  5354         userData: null //XXX TODO byte array?!?
  5355       };
  5358     calls[call.callIndex] = call;
  5360   this._processCalls(calls);
  5361 };
  5362 RilObject.prototype[REQUEST_DIAL] = function REQUEST_DIAL(length, options) {
  5363   // We already return a successful response before. Don't respond it again!
  5364   if (options.rilRequestError) {
  5365     this.getFailCauseCode((function(failCause) {
  5366       this._removePendingOutgoingCall(failCause);
  5367     }).bind(this));
  5369 };
  5370 RilObject.prototype[REQUEST_DIAL_EMERGENCY_CALL] = RilObject.prototype[REQUEST_DIAL];
  5371 RilObject.prototype[REQUEST_GET_IMSI] = function REQUEST_GET_IMSI(length, options) {
  5372   if (options.rilRequestError) {
  5373     return;
  5376   this.iccInfoPrivate.imsi = this.context.Buf.readString();
  5377   if (DEBUG) {
  5378     this.context.debug("IMSI: " + this.iccInfoPrivate.imsi);
  5381   options.rilMessageType = "iccimsi";
  5382   options.imsi = this.iccInfoPrivate.imsi;
  5383   this.sendChromeMessage(options);
  5384 };
  5385 RilObject.prototype[REQUEST_HANGUP] = function REQUEST_HANGUP(length, options) {
  5386   if (options.rilRequestError) {
  5387     return;
  5390   this.getCurrentCalls();
  5391 };
  5392 RilObject.prototype[REQUEST_HANGUP_WAITING_OR_BACKGROUND] = function REQUEST_HANGUP_WAITING_OR_BACKGROUND(length, options) {
  5393   if (options.rilRequestError) {
  5394     return;
  5397   this.getCurrentCalls();
  5398 };
  5399 RilObject.prototype[REQUEST_HANGUP_FOREGROUND_RESUME_BACKGROUND] = function REQUEST_HANGUP_FOREGROUND_RESUME_BACKGROUND(length, options) {
  5400   if (options.rilRequestError) {
  5401     return;
  5404   this.getCurrentCalls();
  5405 };
  5406 RilObject.prototype[REQUEST_SWITCH_WAITING_OR_HOLDING_AND_ACTIVE] = function REQUEST_SWITCH_WAITING_OR_HOLDING_AND_ACTIVE(length, options) {
  5407   options.success = (options.rilRequestError === 0);
  5408   if (!options.success) {
  5409     options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError];
  5410     this.sendChromeMessage(options);
  5411     return;
  5414   this.sendChromeMessage(options);
  5415   this.getCurrentCalls();
  5416 };
  5417 RilObject.prototype[REQUEST_CONFERENCE] = function REQUEST_CONFERENCE(length, options) {
  5418   options.success = (options.rilRequestError === 0);
  5419   if (!options.success) {
  5420     this._hasConferenceRequest = false;
  5421     options.errorName = "addError";
  5422     options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError];
  5423     this.sendChromeMessage(options);
  5424     return;
  5427   this.sendChromeMessage(options);
  5428 };
  5429 RilObject.prototype[REQUEST_UDUB] = null;
  5430 RilObject.prototype[REQUEST_LAST_CALL_FAIL_CAUSE] = function REQUEST_LAST_CALL_FAIL_CAUSE(length, options) {
  5431   let Buf = this.context.Buf;
  5432   let num = length ? Buf.readInt32() : 0;
  5433   let failCause = num ? RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[Buf.readInt32()] : null;
  5434   if (options.callback) {
  5435     options.callback(failCause);
  5437 };
  5438 RilObject.prototype[REQUEST_SIGNAL_STRENGTH] = function REQUEST_SIGNAL_STRENGTH(length, options) {
  5439   this._receivedNetworkInfo(NETWORK_INFO_SIGNAL);
  5441   if (options.rilRequestError) {
  5442     return;
  5445   let Buf = this.context.Buf;
  5446   let signal = {
  5447     gsmSignalStrength: Buf.readInt32(),
  5448     gsmBitErrorRate:   Buf.readInt32(),
  5449     cdmaDBM:           Buf.readInt32(),
  5450     cdmaECIO:          Buf.readInt32(),
  5451     evdoDBM:           Buf.readInt32(),
  5452     evdoECIO:          Buf.readInt32(),
  5453     evdoSNR:           Buf.readInt32()
  5454   };
  5456   if (!this.v5Legacy) {
  5457     signal.lteSignalStrength = Buf.readInt32();
  5458     signal.lteRSRP =           Buf.readInt32();
  5459     signal.lteRSRQ =           Buf.readInt32();
  5460     signal.lteRSSNR =          Buf.readInt32();
  5461     signal.lteCQI =            Buf.readInt32();
  5464   if (DEBUG) this.context.debug("signal strength: " + JSON.stringify(signal));
  5466   this._processSignalStrength(signal);
  5467 };
  5468 RilObject.prototype[REQUEST_VOICE_REGISTRATION_STATE] = function REQUEST_VOICE_REGISTRATION_STATE(length, options) {
  5469   this._receivedNetworkInfo(NETWORK_INFO_VOICE_REGISTRATION_STATE);
  5471   if (options.rilRequestError) {
  5472     return;
  5475   let state = this.context.Buf.readStringList();
  5476   if (DEBUG) this.context.debug("voice registration state: " + state);
  5478   this._processVoiceRegistrationState(state);
  5480   if (this.cachedDialRequest &&
  5481        (this.voiceRegistrationState.emergencyCallsOnly ||
  5482         this.voiceRegistrationState.connected) &&
  5483       this.voiceRegistrationState.radioTech != NETWORK_CREG_TECH_UNKNOWN) {
  5484     // Radio is ready for making the cached emergency call.
  5485     this.cachedDialRequest.callback();
  5486     this.cachedDialRequest = null;
  5488 };
  5489 RilObject.prototype[REQUEST_DATA_REGISTRATION_STATE] = function REQUEST_DATA_REGISTRATION_STATE(length, options) {
  5490   this._receivedNetworkInfo(NETWORK_INFO_DATA_REGISTRATION_STATE);
  5492   if (options.rilRequestError) {
  5493     return;
  5496   let state = this.context.Buf.readStringList();
  5497   this._processDataRegistrationState(state);
  5498 };
  5499 RilObject.prototype[REQUEST_OPERATOR] = function REQUEST_OPERATOR(length, options) {
  5500   this._receivedNetworkInfo(NETWORK_INFO_OPERATOR);
  5502   if (options.rilRequestError) {
  5503     return;
  5506   let operatorData = this.context.Buf.readStringList();
  5507   if (DEBUG) this.context.debug("Operator: " + operatorData);
  5508   this._processOperator(operatorData);
  5509 };
  5510 RilObject.prototype[REQUEST_RADIO_POWER] = function REQUEST_RADIO_POWER(length, options) {
  5511   if (options.rilMessageType == null) {
  5512     // The request was made by ril_worker itself.
  5513     if (options.rilRequestError) {
  5514       if (this.cachedDialRequest && options.enabled) {
  5515         // Turning on radio fails. Notify the error of making an emergency call.
  5516         this.cachedDialRequest.onerror(GECKO_ERROR_RADIO_NOT_AVAILABLE);
  5517         this.cachedDialRequest = null;
  5520     return;
  5523   options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError];
  5524   this.sendChromeMessage(options);
  5525 };
  5526 RilObject.prototype[REQUEST_DTMF] = null;
  5527 RilObject.prototype[REQUEST_SEND_SMS] = function REQUEST_SEND_SMS(length, options) {
  5528   this._processSmsSendResult(length, options);
  5529 };
  5530 RilObject.prototype[REQUEST_SEND_SMS_EXPECT_MORE] = null;
  5532 RilObject.prototype.readSetupDataCall_v5 = function readSetupDataCall_v5(options) {
  5533   if (!options) {
  5534     options = {};
  5536   let [cid, ifname, addresses, dnses, gateways] = this.context.Buf.readStringList();
  5537   options.cid = cid;
  5538   options.ifname = ifname;
  5539   options.addresses = addresses ? [addresses] : [];
  5540   options.dnses = dnses ? [dnses] : [];
  5541   options.gateways = gateways ? [gateways] : [];
  5542   options.active = DATACALL_ACTIVE_UNKNOWN;
  5543   options.state = GECKO_NETWORK_STATE_CONNECTING;
  5544   return options;
  5545 };
  5547 RilObject.prototype[REQUEST_SETUP_DATA_CALL] = function REQUEST_SETUP_DATA_CALL(length, options) {
  5548   if (options.rilRequestError) {
  5549     // On Data Call generic errors, we shall notify caller
  5550     this._sendDataCallError(options, options.rilRequestError);
  5551     return;
  5554   if (this.v5Legacy) {
  5555     // Populate the `options` object with the data call information. That way
  5556     // we retain the APN and other info about how the data call was set up.
  5557     this.readSetupDataCall_v5(options);
  5558     this.currentDataCalls[options.cid] = options;
  5559     options.rilMessageType = "datacallstatechange";
  5560     this.sendChromeMessage(options);
  5561     // Let's get the list of data calls to ensure we know whether it's active
  5562     // or not.
  5563     this.getDataCallList();
  5564     return;
  5566   // Pass `options` along. That way we retain the APN and other info about
  5567   // how the data call was set up.
  5568   this[REQUEST_DATA_CALL_LIST](length, options);
  5569 };
  5570 RilObject.prototype[REQUEST_SIM_IO] = function REQUEST_SIM_IO(length, options) {
  5571   let ICCIOHelper = this.context.ICCIOHelper;
  5572   if (!length) {
  5573     ICCIOHelper.processICCIOError(options);
  5574     return;
  5577   // Don't need to read rilRequestError since we can know error status from
  5578   // sw1 and sw2.
  5579   let Buf = this.context.Buf;
  5580   options.sw1 = Buf.readInt32();
  5581   options.sw2 = Buf.readInt32();
  5582   if (options.sw1 != ICC_STATUS_NORMAL_ENDING) {
  5583     ICCIOHelper.processICCIOError(options);
  5584     return;
  5586   ICCIOHelper.processICCIO(options);
  5587 };
  5588 RilObject.prototype[REQUEST_SEND_USSD] = function REQUEST_SEND_USSD(length, options) {
  5589   if (DEBUG) {
  5590     this.context.debug("REQUEST_SEND_USSD " + JSON.stringify(options));
  5592   options.success = (this._ussdSession = options.rilRequestError === 0);
  5593   options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError];
  5594   this.sendChromeMessage(options);
  5595 };
  5596 RilObject.prototype[REQUEST_CANCEL_USSD] = function REQUEST_CANCEL_USSD(length, options) {
  5597   if (DEBUG) {
  5598     this.context.debug("REQUEST_CANCEL_USSD" + JSON.stringify(options));
  5600   options.success = (options.rilRequestError === 0);
  5601   this._ussdSession = !options.success;
  5602   options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError];
  5603   this.sendChromeMessage(options);
  5604 };
  5605 RilObject.prototype[REQUEST_GET_CLIR] = function REQUEST_GET_CLIR(length, options) {
  5606   options.success = (options.rilRequestError === 0);
  5607   if (!options.success) {
  5608     options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError];
  5609     this.sendChromeMessage(options);
  5610     return;
  5613   let Buf = this.context.Buf;
  5614   let bufLength = Buf.readInt32();
  5615   if (!bufLength || bufLength < 2) {
  5616     options.success = false;
  5617     options.errorMsg = GECKO_ERROR_GENERIC_FAILURE;
  5618     this.sendChromeMessage(options);
  5619     return;
  5622   options.n = Buf.readInt32(); // Will be TS 27.007 +CLIR parameter 'n'.
  5623   options.m = Buf.readInt32(); // Will be TS 27.007 +CLIR parameter 'm'.
  5625   if (options.rilMessageType === "sendMMI") {
  5626     // TS 27.007 +CLIR parameter 'm'.
  5627     switch (options.m) {
  5628       // CLIR not provisioned.
  5629       case 0:
  5630         options.statusMessage = MMI_SM_KS_SERVICE_NOT_PROVISIONED;
  5631         break;
  5632       // CLIR provisioned in permanent mode.
  5633       case 1:
  5634         options.statusMessage = MMI_SM_KS_CLIR_PERMANENT;
  5635         break;
  5636       // Unknown (e.g. no network, etc.).
  5637       case 2:
  5638         options.success = false;
  5639         options.errorMsg = MMI_ERROR_KS_ERROR;
  5640         break;
  5641       // CLIR temporary mode presentation restricted.
  5642       case 3:
  5643         // TS 27.007 +CLIR parameter 'n'.
  5644         switch (options.n) {
  5645           // Default.
  5646           case 0:
  5647           // CLIR invocation.
  5648           case 1:
  5649             options.statusMessage = MMI_SM_KS_CLIR_DEFAULT_ON_NEXT_CALL_ON;
  5650             break;
  5651           // CLIR suppression.
  5652           case 2:
  5653             options.statusMessage = MMI_SM_KS_CLIR_DEFAULT_ON_NEXT_CALL_OFF;
  5654             break;
  5655           default:
  5656             options.success = false;
  5657             options.errorMsg = GECKO_ERROR_GENERIC_FAILURE;
  5658             break;
  5660         break;
  5661       // CLIR temporary mode presentation allowed.
  5662       case 4:
  5663         // TS 27.007 +CLIR parameter 'n'.
  5664         switch (options.n) {
  5665           // Default.
  5666           case 0:
  5667           // CLIR suppression.
  5668           case 2:
  5669             options.statusMessage = MMI_SM_KS_CLIR_DEFAULT_OFF_NEXT_CALL_OFF;
  5670             break;
  5671           // CLIR invocation.
  5672           case 1:
  5673             options.statusMessage = MMI_SM_KS_CLIR_DEFAULT_OFF_NEXT_CALL_ON;
  5674             break;
  5675           default:
  5676             options.success = false;
  5677             options.errorMsg = GECKO_ERROR_GENERIC_FAILURE;
  5678             break;
  5680         break;
  5681       default:
  5682         options.success = false;
  5683         options.errorMsg = GECKO_ERROR_GENERIC_FAILURE;
  5684         break;
  5688   this.sendChromeMessage(options);
  5689 };
  5690 RilObject.prototype[REQUEST_SET_CLIR] = function REQUEST_SET_CLIR(length, options) {
  5691   if (options.rilMessageType == null) {
  5692     // The request was made by ril_worker itself automatically. Don't report.
  5693     return;
  5695   options.success = (options.rilRequestError === 0);
  5696   if (!options.success) {
  5697     options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError];
  5698   } else if (options.rilMessageType === "sendMMI") {
  5699     switch (options.procedure) {
  5700       case MMI_PROCEDURE_ACTIVATION:
  5701         options.statusMessage = MMI_SM_KS_SERVICE_ENABLED;
  5702         break;
  5703       case MMI_PROCEDURE_DEACTIVATION:
  5704         options.statusMessage = MMI_SM_KS_SERVICE_DISABLED;
  5705         break;
  5708   this.sendChromeMessage(options);
  5709 };
  5711 RilObject.prototype[REQUEST_QUERY_CALL_FORWARD_STATUS] =
  5712   function REQUEST_QUERY_CALL_FORWARD_STATUS(length, options) {
  5713   options.success = (options.rilRequestError === 0);
  5714   if (!options.success) {
  5715     options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError];
  5716     this.sendChromeMessage(options);
  5717     return;
  5720   let Buf = this.context.Buf;
  5721   let rulesLength = 0;
  5722   if (length) {
  5723     rulesLength = Buf.readInt32();
  5725   if (!rulesLength) {
  5726     options.success = false;
  5727     options.errorMsg = GECKO_ERROR_GENERIC_FAILURE;
  5728     this.sendChromeMessage(options);
  5729     return;
  5731   let rules = new Array(rulesLength);
  5732   for (let i = 0; i < rulesLength; i++) {
  5733     let rule = {};
  5734     rule.active       = Buf.readInt32() == 1; // CALL_FORWARD_STATUS_*
  5735     rule.reason       = Buf.readInt32(); // CALL_FORWARD_REASON_*
  5736     rule.serviceClass = Buf.readInt32();
  5737     rule.toa          = Buf.readInt32();
  5738     rule.number       = Buf.readString();
  5739     rule.timeSeconds  = Buf.readInt32();
  5740     rules[i] = rule;
  5742   options.rules = rules;
  5743   if (options.rilMessageType === "sendMMI") {
  5744     options.statusMessage = MMI_SM_KS_SERVICE_INTERROGATED;
  5745     // MMI query call forwarding options request returns a set of rules that
  5746     // will be exposed in the form of an array of nsIDOMMozMobileCFInfo
  5747     // instances.
  5748     options.additionalInformation = rules;
  5750   this.sendChromeMessage(options);
  5751 };
  5752 RilObject.prototype[REQUEST_SET_CALL_FORWARD] =
  5753   function REQUEST_SET_CALL_FORWARD(length, options) {
  5754   options.success = (options.rilRequestError === 0);
  5755   if (!options.success) {
  5756     options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError];
  5757   } else if (options.rilMessageType === "sendMMI") {
  5758     switch (options.action) {
  5759       case CALL_FORWARD_ACTION_ENABLE:
  5760         options.statusMessage = MMI_SM_KS_SERVICE_ENABLED;
  5761         break;
  5762       case CALL_FORWARD_ACTION_DISABLE:
  5763         options.statusMessage = MMI_SM_KS_SERVICE_DISABLED;
  5764         break;
  5765       case CALL_FORWARD_ACTION_REGISTRATION:
  5766         options.statusMessage = MMI_SM_KS_SERVICE_REGISTERED;
  5767         break;
  5768       case CALL_FORWARD_ACTION_ERASURE:
  5769         options.statusMessage = MMI_SM_KS_SERVICE_ERASED;
  5770         break;
  5773   this.sendChromeMessage(options);
  5774 };
  5775 RilObject.prototype[REQUEST_QUERY_CALL_WAITING] =
  5776   function REQUEST_QUERY_CALL_WAITING(length, options) {
  5777   options.success = (options.rilRequestError === 0);
  5778   if (!options.success) {
  5779     options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError];
  5780     this.sendChromeMessage(options);
  5781     return;
  5784   if (options.callback) {
  5785     options.callback.call(this, options);
  5786     return;
  5789   let Buf = this.context.Buf;
  5790   options.length = Buf.readInt32();
  5791   options.enabled = ((Buf.readInt32() == 1) &&
  5792                      ((Buf.readInt32() & ICC_SERVICE_CLASS_VOICE) == 0x01));
  5793   this.sendChromeMessage(options);
  5794 };
  5796 RilObject.prototype[REQUEST_SET_CALL_WAITING] = function REQUEST_SET_CALL_WAITING(length, options) {
  5797   options.success = (options.rilRequestError === 0);
  5798   if (!options.success) {
  5799     options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError];
  5800     this.sendChromeMessage(options);
  5801     return;
  5804   if (options.callback) {
  5805     options.callback.call(this, options);
  5806     return;
  5809   this.sendChromeMessage(options);
  5810 };
  5811 RilObject.prototype[REQUEST_SMS_ACKNOWLEDGE] = null;
  5812 RilObject.prototype[REQUEST_GET_IMEI] = function REQUEST_GET_IMEI(length, options) {
  5813   this.IMEI = this.context.Buf.readString();
  5814   let rilMessageType = options.rilMessageType;
  5815   // So far we only send the IMEI back to chrome if it was requested via MMI.
  5816   if (rilMessageType !== "sendMMI") {
  5817     return;
  5820   options.success = (options.rilRequestError === 0);
  5821   options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError];
  5822   if ((!options.success || this.IMEI == null) && !options.errorMsg) {
  5823     options.errorMsg = GECKO_ERROR_GENERIC_FAILURE;
  5825   options.statusMessage = this.IMEI;
  5826   this.sendChromeMessage(options);
  5827 };
  5828 RilObject.prototype[REQUEST_GET_IMEISV] = function REQUEST_GET_IMEISV(length, options) {
  5829   if (options.rilRequestError) {
  5830     return;
  5833   this.IMEISV = this.context.Buf.readString();
  5834 };
  5835 RilObject.prototype[REQUEST_ANSWER] = null;
  5836 RilObject.prototype[REQUEST_DEACTIVATE_DATA_CALL] = function REQUEST_DEACTIVATE_DATA_CALL(length, options) {
  5837   if (options.rilRequestError) {
  5838     return;
  5841   let datacall = this.currentDataCalls[options.cid];
  5842   delete this.currentDataCalls[options.cid];
  5843   datacall.state = GECKO_NETWORK_STATE_UNKNOWN;
  5844   datacall.rilMessageType = "datacallstatechange";
  5845   this.sendChromeMessage(datacall);
  5846 };
  5847 RilObject.prototype[REQUEST_QUERY_FACILITY_LOCK] = function REQUEST_QUERY_FACILITY_LOCK(length, options) {
  5848   options.success = (options.rilRequestError === 0);
  5849   if (!options.success) {
  5850     options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError];
  5853   let services;
  5854   if (length) {
  5855     // Buf.readInt32List()[0] for Call Barring is a bit vector of services.
  5856     services = this.context.Buf.readInt32List()[0];
  5857   } else {
  5858     options.success = false;
  5859     options.errorMsg = GECKO_ERROR_GENERIC_FAILURE;
  5860     this.sendChromeMessage(options);
  5861     return;
  5864   options.enabled = services === 0 ? false : true;
  5866   if (options.success && (options.rilMessageType === "sendMMI")) {
  5867     if (!options.enabled) {
  5868       options.statusMessage = MMI_SM_KS_SERVICE_DISABLED;
  5869     } else {
  5870       options.statusMessage = MMI_SM_KS_SERVICE_ENABLED_FOR;
  5871       let serviceClass = [];
  5872       for (let serviceClassMask = 1;
  5873            serviceClassMask <= ICC_SERVICE_CLASS_MAX;
  5874            serviceClassMask <<= 1) {
  5875         if ((serviceClassMask & services) !== 0) {
  5876           serviceClass.push(MMI_KS_SERVICE_CLASS_MAPPING[serviceClassMask]);
  5880       options.additionalInformation = serviceClass;
  5883   this.sendChromeMessage(options);
  5884 };
  5885 RilObject.prototype[REQUEST_SET_FACILITY_LOCK] = function REQUEST_SET_FACILITY_LOCK(length, options) {
  5886   options.success = (options.rilRequestError === 0);
  5887   if (!options.success) {
  5888     options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError];
  5891   options.retryCount = length ? this.context.Buf.readInt32List()[0] : -1;
  5893   if (options.success && (options.rilMessageType === "sendMMI")) {
  5894     switch (options.procedure) {
  5895       case MMI_PROCEDURE_ACTIVATION:
  5896         options.statusMessage = MMI_SM_KS_SERVICE_ENABLED;
  5897         break;
  5898       case MMI_PROCEDURE_DEACTIVATION:
  5899         options.statusMessage = MMI_SM_KS_SERVICE_DISABLED;
  5900         break;
  5903   this.sendChromeMessage(options);
  5904 };
  5905 RilObject.prototype[REQUEST_CHANGE_BARRING_PASSWORD] =
  5906   function REQUEST_CHANGE_BARRING_PASSWORD(length, options) {
  5907   if (options.rilRequestError) {
  5908     options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError];
  5910   this.sendChromeMessage(options);
  5911 };
  5912 RilObject.prototype[REQUEST_SIM_OPEN_CHANNEL] = function REQUEST_SIM_OPEN_CHANNEL(length, options) {
  5913   if (options.rilRequestError) {
  5914     options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError];
  5915     this.sendChromeMessage(options);
  5916     return;
  5919   options.channel = this.context.Buf.readInt32();
  5920   if (DEBUG) {
  5921     this.context.debug("Setting channel number in options: " + options.channel);
  5923   this.sendChromeMessage(options);
  5924 };
  5925 RilObject.prototype[REQUEST_SIM_CLOSE_CHANNEL] = function REQUEST_SIM_CLOSE_CHANNEL(length, options) {
  5926   if (options.rilRequestError) {
  5927     options.error = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError];
  5928     this.sendChromeMessage(options);
  5929     return;
  5932   // No return value
  5933   this.sendChromeMessage(options);
  5934 };
  5935 RilObject.prototype[REQUEST_SIM_ACCESS_CHANNEL] = function REQUEST_SIM_ACCESS_CHANNEL(length, options) {
  5936   if (options.rilRequestError) {
  5937     options.error = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError];
  5938     this.sendChromeMessage(options);
  5941   let Buf = this.context.Buf;
  5942   options.sw1 = Buf.readInt32();
  5943   options.sw2 = Buf.readInt32();
  5944   options.simResponse = Buf.readString();
  5945   if (DEBUG) {
  5946     this.context.debug("Setting return values for RIL[REQUEST_SIM_ACCESS_CHANNEL]: [" +
  5947                        options.sw1 + "," +
  5948                        options.sw2 + ", " +
  5949                        options.simResponse + "]");
  5951   this.sendChromeMessage(options);
  5952 };
  5953 RilObject.prototype[REQUEST_QUERY_NETWORK_SELECTION_MODE] = function REQUEST_QUERY_NETWORK_SELECTION_MODE(length, options) {
  5954   this._receivedNetworkInfo(NETWORK_INFO_NETWORK_SELECTION_MODE);
  5956   if (options.rilRequestError) {
  5957     return;
  5960   let mode = this.context.Buf.readInt32List();
  5961   let selectionMode;
  5963   switch (mode[0]) {
  5964     case NETWORK_SELECTION_MODE_AUTOMATIC:
  5965       selectionMode = GECKO_NETWORK_SELECTION_AUTOMATIC;
  5966       break;
  5967     case NETWORK_SELECTION_MODE_MANUAL:
  5968       selectionMode = GECKO_NETWORK_SELECTION_MANUAL;
  5969       break;
  5970     default:
  5971       selectionMode = GECKO_NETWORK_SELECTION_UNKNOWN;
  5972       break;
  5975   if (this.networkSelectionMode != selectionMode) {
  5976     this.networkSelectionMode = options.mode = selectionMode;
  5977     options.rilMessageType = "networkselectionmodechange";
  5978     this._sendNetworkInfoMessage(NETWORK_INFO_NETWORK_SELECTION_MODE, options);
  5980 };
  5981 RilObject.prototype[REQUEST_SET_NETWORK_SELECTION_AUTOMATIC] = function REQUEST_SET_NETWORK_SELECTION_AUTOMATIC(length, options) {
  5982   if (options.rilRequestError) {
  5983     options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError];
  5986   this.sendChromeMessage(options);
  5987 };
  5988 RilObject.prototype[REQUEST_SET_NETWORK_SELECTION_MANUAL] = function REQUEST_SET_NETWORK_SELECTION_MANUAL(length, options) {
  5989   if (options.rilRequestError) {
  5990     options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError];
  5993   this.sendChromeMessage(options);
  5994 };
  5995 RilObject.prototype[REQUEST_QUERY_AVAILABLE_NETWORKS] = function REQUEST_QUERY_AVAILABLE_NETWORKS(length, options) {
  5996   if (options.rilRequestError) {
  5997     options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError];
  5998   } else {
  5999     options.networks = this._processNetworks();
  6001   this.sendChromeMessage(options);
  6002 };
  6003 RilObject.prototype[REQUEST_DTMF_START] = null;
  6004 RilObject.prototype[REQUEST_DTMF_STOP] = null;
  6005 RilObject.prototype[REQUEST_BASEBAND_VERSION] = function REQUEST_BASEBAND_VERSION(length, options) {
  6006   if (options.rilRequestError) {
  6007     return;
  6010   this.basebandVersion = this.context.Buf.readString();
  6011   if (DEBUG) this.context.debug("Baseband version: " + this.basebandVersion);
  6012 };
  6013 RilObject.prototype[REQUEST_SEPARATE_CONNECTION] = function REQUEST_SEPARATE_CONNECTION(length, options) {
  6014   options.success = (options.rilRequestError === 0);
  6015   if (!options.success) {
  6016     options.errorName = "removeError";
  6017     options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError];
  6018     this.sendChromeMessage(options);
  6019     return;
  6022   this.sendChromeMessage(options);
  6023 };
  6024 RilObject.prototype[REQUEST_SET_MUTE] = null;
  6025 RilObject.prototype[REQUEST_GET_MUTE] = null;
  6026 RilObject.prototype[REQUEST_QUERY_CLIP] = function REQUEST_QUERY_CLIP(length, options) {
  6027   options.success = (options.rilRequestError === 0);
  6028   if (!options.success) {
  6029     options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError];
  6030     this.sendChromeMessage(options);
  6031     return;
  6034   let Buf = this.context.Buf;
  6035   let bufLength = Buf.readInt32();
  6036   if (!bufLength) {
  6037     options.success = false;
  6038     options.errorMsg = GECKO_ERROR_GENERIC_FAILURE;
  6039     this.sendChromeMessage(options);
  6040     return;
  6043   // options.provisioned informs about the called party receives the calling
  6044   // party's address information:
  6045   // 0 for CLIP not provisioned
  6046   // 1 for CLIP provisioned
  6047   // 2 for unknown
  6048   options.provisioned = Buf.readInt32();
  6049   if (options.rilMessageType === "sendMMI") {
  6050     switch (options.provisioned) {
  6051       case 0:
  6052         options.statusMessage = MMI_SM_KS_SERVICE_DISABLED;
  6053         break;
  6054       case 1:
  6055         options.statusMessage = MMI_SM_KS_SERVICE_ENABLED;
  6056         break;
  6057       default:
  6058         options.success = false;
  6059         options.errorMsg = MMI_ERROR_KS_ERROR;
  6060         break;
  6063   this.sendChromeMessage(options);
  6064 };
  6065 RilObject.prototype[REQUEST_LAST_DATA_CALL_FAIL_CAUSE] = null;
  6067 /**
  6068  * V3:
  6069  *  # address   - A space-delimited list of addresses.
  6071  * V4:
  6072  *  # address   - An address.
  6074  * V5:
  6075  *  # addresses - A space-delimited list of addresses.
  6076  *  # dnses     - A space-delimited list of DNS server addresses.
  6078  * V6:
  6079  *  # addresses - A space-delimited list of addresses with optional "/" prefix
  6080  *                length.
  6081  *  # dnses     - A space-delimited list of DNS server addresses.
  6082  *  # gateways  - A space-delimited list of default gateway addresses.
  6083  */
  6084 RilObject.prototype.readDataCall_v5 = function(options) {
  6085   if (!options) {
  6086     options = {};
  6088   let Buf = this.context.Buf;
  6089   options.cid = Buf.readInt32().toString();
  6090   options.active = Buf.readInt32(); // DATACALL_ACTIVE_*
  6091   options.type = Buf.readString();
  6092   options.apn = Buf.readString();
  6093   let addresses = Buf.readString();
  6094   let dnses = Buf.readString();
  6095   options.addresses = addresses ? addresses.split(" ") : [];
  6096   options.dnses = dnses ? dnses.split(" ") : [];
  6097   options.gateways = [];
  6098   return options;
  6099 };
  6101 RilObject.prototype.readDataCall_v6 = function(options) {
  6102   if (!options) {
  6103     options = {};
  6105   let Buf = this.context.Buf;
  6106   options.status = Buf.readInt32();  // DATACALL_FAIL_*
  6107   options.suggestedRetryTime = Buf.readInt32();
  6108   options.cid = Buf.readInt32().toString();
  6109   options.active = Buf.readInt32();  // DATACALL_ACTIVE_*
  6110   options.type = Buf.readString();
  6111   options.ifname = Buf.readString();
  6112   let addresses = Buf.readString();
  6113   let dnses = Buf.readString();
  6114   let gateways = Buf.readString();
  6115   options.addresses = addresses ? addresses.split(" ") : [];
  6116   options.dnses = dnses ? dnses.split(" ") : [];
  6117   options.gateways = gateways ? gateways.split(" ") : [];
  6118   return options;
  6119 };
  6121 RilObject.prototype[REQUEST_DATA_CALL_LIST] = function REQUEST_DATA_CALL_LIST(length, options) {
  6122   if (options.rilRequestError) {
  6123     return;
  6126   if (!length) {
  6127     this._processDataCallList(null);
  6128     return;
  6131   let Buf = this.context.Buf;
  6132   let version = 0;
  6133   if (!this.v5Legacy) {
  6134     version = Buf.readInt32();
  6136   let num = Buf.readInt32();
  6137   let datacalls = {};
  6138   for (let i = 0; i < num; i++) {
  6139     let datacall;
  6140     if (version < 6) {
  6141       datacall = this.readDataCall_v5();
  6142     } else {
  6143       datacall = this.readDataCall_v6();
  6145     datacalls[datacall.cid] = datacall;
  6148   let newDataCallOptions = null;
  6149   if (options.rilRequestType == REQUEST_SETUP_DATA_CALL) {
  6150     newDataCallOptions = options;
  6152   this._processDataCallList(datacalls, newDataCallOptions);
  6153 };
  6154 RilObject.prototype[REQUEST_RESET_RADIO] = null;
  6155 RilObject.prototype[REQUEST_OEM_HOOK_RAW] = null;
  6156 RilObject.prototype[REQUEST_OEM_HOOK_STRINGS] = null;
  6157 RilObject.prototype[REQUEST_SCREEN_STATE] = null;
  6158 RilObject.prototype[REQUEST_SET_SUPP_SVC_NOTIFICATION] = null;
  6159 RilObject.prototype[REQUEST_WRITE_SMS_TO_SIM] = function REQUEST_WRITE_SMS_TO_SIM(length, options) {
  6160   if (options.rilRequestError) {
  6161     // `The MS shall return a "protocol error, unspecified" error message if
  6162     // the short message cannot be stored in the (U)SIM, and there is other
  6163     // message storage available at the MS` ~ 3GPP TS 23.038 section 4. Here
  6164     // we assume we always have indexed db as another storage.
  6165     this.acknowledgeGsmSms(false, PDU_FCS_PROTOCOL_ERROR);
  6166   } else {
  6167     this.acknowledgeGsmSms(true, PDU_FCS_OK);
  6169 };
  6170 RilObject.prototype[REQUEST_DELETE_SMS_ON_SIM] = null;
  6171 RilObject.prototype[REQUEST_SET_BAND_MODE] = null;
  6172 RilObject.prototype[REQUEST_QUERY_AVAILABLE_BAND_MODE] = null;
  6173 RilObject.prototype[REQUEST_STK_GET_PROFILE] = null;
  6174 RilObject.prototype[REQUEST_STK_SET_PROFILE] = null;
  6175 RilObject.prototype[REQUEST_STK_SEND_ENVELOPE_COMMAND] = null;
  6176 RilObject.prototype[REQUEST_STK_SEND_TERMINAL_RESPONSE] = null;
  6177 RilObject.prototype[REQUEST_STK_HANDLE_CALL_SETUP_REQUESTED_FROM_SIM] = null;
  6178 RilObject.prototype[REQUEST_EXPLICIT_CALL_TRANSFER] = null;
  6179 RilObject.prototype[REQUEST_SET_PREFERRED_NETWORK_TYPE] = function REQUEST_SET_PREFERRED_NETWORK_TYPE(length, options) {
  6180   if (options.networkType == null) {
  6181     // The request was made by ril_worker itself automatically. Don't report.
  6182     return;
  6185   if (options.rilRequestError) {
  6186     options.success = false;
  6187     options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError];
  6188   } else {
  6189     options.success = true;
  6191   this.sendChromeMessage(options);
  6192 };
  6193 RilObject.prototype[REQUEST_GET_PREFERRED_NETWORK_TYPE] = function REQUEST_GET_PREFERRED_NETWORK_TYPE(length, options) {
  6194   if (options.rilRequestError) {
  6195     options.success = false;
  6196     options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError];
  6197     this.sendChromeMessage(options);
  6198     return;
  6201   let results = this.context.Buf.readInt32List();
  6202   options.networkType = this.preferredNetworkType = results[0];
  6203   options.success = true;
  6205   this.sendChromeMessage(options);
  6206 };
  6207 RilObject.prototype[REQUEST_GET_NEIGHBORING_CELL_IDS] = null;
  6208 RilObject.prototype[REQUEST_SET_LOCATION_UPDATES] = null;
  6209 RilObject.prototype[REQUEST_CDMA_SET_SUBSCRIPTION_SOURCE] = null;
  6210 RilObject.prototype[REQUEST_CDMA_SET_ROAMING_PREFERENCE] = function REQUEST_CDMA_SET_ROAMING_PREFERENCE(length, options) {
  6211   if (options.rilRequestError) {
  6212     options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError];
  6214   this.sendChromeMessage(options);
  6215 };
  6216 RilObject.prototype[REQUEST_CDMA_QUERY_ROAMING_PREFERENCE] = function REQUEST_CDMA_QUERY_ROAMING_PREFERENCE(length, options) {
  6217   if (options.rilRequestError) {
  6218     options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError];
  6219   } else {
  6220     let mode = this.context.Buf.readInt32List();
  6221     options.mode = CDMA_ROAMING_PREFERENCE_TO_GECKO[mode[0]];
  6223   this.sendChromeMessage(options);
  6224 };
  6225 RilObject.prototype[REQUEST_SET_TTY_MODE] = null;
  6226 RilObject.prototype[REQUEST_QUERY_TTY_MODE] = null;
  6227 RilObject.prototype[REQUEST_CDMA_SET_PREFERRED_VOICE_PRIVACY_MODE] = function REQUEST_CDMA_SET_PREFERRED_VOICE_PRIVACY_MODE(length, options) {
  6228   if (options.rilRequestError) {
  6229     options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError];
  6230     this.sendChromeMessage(options);
  6231     return;
  6234   this.sendChromeMessage(options);
  6235 };
  6236 RilObject.prototype[REQUEST_CDMA_QUERY_PREFERRED_VOICE_PRIVACY_MODE] = function REQUEST_CDMA_QUERY_PREFERRED_VOICE_PRIVACY_MODE(length, options) {
  6237   if (options.rilRequestError) {
  6238     options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError];
  6239     this.sendChromeMessage(options);
  6240     return;
  6243   let enabled = this.context.Buf.readInt32List();
  6244   options.enabled = enabled[0] ? true : false;
  6245   this.sendChromeMessage(options);
  6246 };
  6247 RilObject.prototype[REQUEST_CDMA_FLASH] = function REQUEST_CDMA_FLASH(length, options) {
  6248   options.success = (options.rilRequestError === 0);
  6249   if (!options.success) {
  6250     if (options.rilMessageType === "conferenceCall") {
  6251       options.errorName = "addError";
  6252     } else if (options.rilMessageType === "separateCall") {
  6253       options.errorName = "removeError";
  6255     options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError];
  6258   this.sendChromeMessage(options);
  6259 };
  6260 RilObject.prototype[REQUEST_CDMA_BURST_DTMF] = null;
  6261 RilObject.prototype[REQUEST_CDMA_VALIDATE_AND_WRITE_AKEY] = null;
  6262 RilObject.prototype[REQUEST_CDMA_SEND_SMS] = function REQUEST_CDMA_SEND_SMS(length, options) {
  6263   this._processSmsSendResult(length, options);
  6264 };
  6265 RilObject.prototype[REQUEST_CDMA_SMS_ACKNOWLEDGE] = null;
  6266 RilObject.prototype[REQUEST_GSM_GET_BROADCAST_SMS_CONFIG] = null;
  6267 RilObject.prototype[REQUEST_GSM_SET_BROADCAST_SMS_CONFIG] = function REQUEST_GSM_SET_BROADCAST_SMS_CONFIG(length, options) {
  6268   if (options.rilRequestError == ERROR_SUCCESS) {
  6269     this.setSmsBroadcastActivation(true);
  6271 };
  6272 RilObject.prototype[REQUEST_GSM_SMS_BROADCAST_ACTIVATION] = null;
  6273 RilObject.prototype[REQUEST_CDMA_GET_BROADCAST_SMS_CONFIG] = null;
  6274 RilObject.prototype[REQUEST_CDMA_SET_BROADCAST_SMS_CONFIG] = null;
  6275 RilObject.prototype[REQUEST_CDMA_SMS_BROADCAST_ACTIVATION] = null;
  6276 RilObject.prototype[REQUEST_CDMA_SUBSCRIPTION] = function REQUEST_CDMA_SUBSCRIPTION(length, options) {
  6277   if (options.rilRequestError) {
  6278     return;
  6281   let result = this.context.Buf.readStringList();
  6283   this.iccInfo.mdn = result[0];
  6284   // The result[1] is Home SID. (Already be handled in readCDMAHome())
  6285   // The result[2] is Home NID. (Already be handled in readCDMAHome())
  6286   // The result[3] is MIN.
  6287   this.iccInfo.prlVersion = parseInt(result[4], 10);
  6289   this.context.ICCUtilsHelper.handleICCInfoChange();
  6290 };
  6291 RilObject.prototype[REQUEST_CDMA_WRITE_SMS_TO_RUIM] = null;
  6292 RilObject.prototype[REQUEST_CDMA_DELETE_SMS_ON_RUIM] = null;
  6293 RilObject.prototype[REQUEST_DEVICE_IDENTITY] = function REQUEST_DEVICE_IDENTITY(length, options) {
  6294   if (options.rilRequestError) {
  6295     return;
  6298   let result = this.context.Buf.readStringList();
  6300   // The result[0] is for IMEI. (Already be handled in REQUEST_GET_IMEI)
  6301   // The result[1] is for IMEISV. (Already be handled in REQUEST_GET_IMEISV)
  6302   // They are both ignored.
  6303   this.ESN = result[2];
  6304   this.MEID = result[3];
  6305 };
  6306 RilObject.prototype[REQUEST_EXIT_EMERGENCY_CALLBACK_MODE] = function REQUEST_EXIT_EMERGENCY_CALLBACK_MODE(length, options) {
  6307   if (options.internal) {
  6308     return;
  6311   options.success = (options.rilRequestError === 0);
  6312   if (!options.success) {
  6313     options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError];
  6315   this.sendChromeMessage(options);
  6316 };
  6317 RilObject.prototype[REQUEST_GET_SMSC_ADDRESS] = function REQUEST_GET_SMSC_ADDRESS(length, options) {
  6318   this.SMSC = options.rilRequestError ? null : this.context.Buf.readString();
  6320   if (!options.rilMessageType || options.rilMessageType !== "getSmscAddress") {
  6321     return;
  6324   options.smscAddress = this.SMSC;
  6325   options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError];
  6326   this.sendChromeMessage(options);
  6327 };
  6328 RilObject.prototype[REQUEST_SET_SMSC_ADDRESS] = null;
  6329 RilObject.prototype[REQUEST_REPORT_SMS_MEMORY_STATUS] = null;
  6330 RilObject.prototype[REQUEST_REPORT_STK_SERVICE_IS_RUNNING] = null;
  6331 RilObject.prototype[REQUEST_ACKNOWLEDGE_INCOMING_GSM_SMS_WITH_PDU] = null;
  6332 RilObject.prototype[REQUEST_STK_SEND_ENVELOPE_WITH_STATUS] = function REQUEST_STK_SEND_ENVELOPE_WITH_STATUS(length, options) {
  6333   if (options.rilRequestError) {
  6334     this.acknowledgeGsmSms(false, PDU_FCS_UNSPECIFIED);
  6335     return;
  6338   let Buf = this.context.Buf;
  6339   let sw1 = Buf.readInt32();
  6340   let sw2 = Buf.readInt32();
  6341   if ((sw1 == ICC_STATUS_SAT_BUSY) && (sw2 === 0x00)) {
  6342     this.acknowledgeGsmSms(false, PDU_FCS_USAT_BUSY);
  6343     return;
  6346   let success = ((sw1 == ICC_STATUS_NORMAL_ENDING) && (sw2 === 0x00))
  6347                 || (sw1 == ICC_STATUS_NORMAL_ENDING_WITH_EXTRA);
  6349   let messageStringLength = Buf.readInt32(); // In semi-octets
  6350   let responsePduLen = messageStringLength / 2; // In octets
  6351   if (!responsePduLen) {
  6352     this.acknowledgeGsmSms(success, success ? PDU_FCS_OK
  6353                                          : PDU_FCS_USIM_DATA_DOWNLOAD_ERROR);
  6354     return;
  6357   this.acknowledgeIncomingGsmSmsWithPDU(success, responsePduLen, options);
  6358 };
  6359 RilObject.prototype[REQUEST_VOICE_RADIO_TECH] = function REQUEST_VOICE_RADIO_TECH(length, options) {
  6360   if (options.rilRequestError) {
  6361     if (DEBUG) {
  6362       this.context.debug("Error when getting voice radio tech: " +
  6363                          options.rilRequestError);
  6365     return;
  6367   let radioTech = this.context.Buf.readInt32List();
  6368   this._processRadioTech(radioTech[0]);
  6369 };
  6370 RilObject.prototype[REQUEST_GET_UNLOCK_RETRY_COUNT] = function REQUEST_GET_UNLOCK_RETRY_COUNT(length, options) {
  6371   options.success = (options.rilRequestError === 0);
  6372   if (!options.success) {
  6373     options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError];
  6375   options.retryCount = length ? this.context.Buf.readInt32List()[0] : -1;
  6376   this.sendChromeMessage(options);
  6377 };
  6378 RilObject.prototype[RIL_REQUEST_GPRS_ATTACH] = null;
  6379 RilObject.prototype[RIL_REQUEST_GPRS_DETACH] = null;
  6380 RilObject.prototype[UNSOLICITED_RESPONSE_RADIO_STATE_CHANGED] = function UNSOLICITED_RESPONSE_RADIO_STATE_CHANGED() {
  6381   let radioState = this.context.Buf.readInt32();
  6382   let newState;
  6383   if (radioState == RADIO_STATE_UNAVAILABLE) {
  6384     newState = GECKO_RADIOSTATE_UNAVAILABLE;
  6385   } else if (radioState == RADIO_STATE_OFF) {
  6386     newState = GECKO_RADIOSTATE_OFF;
  6387   } else {
  6388     newState = GECKO_RADIOSTATE_READY;
  6391   if (DEBUG) {
  6392     this.context.debug("Radio state changed from '" + this.radioState +
  6393                        "' to '" + newState + "'");
  6395   if (this.radioState == newState) {
  6396     return;
  6399   switch (radioState) {
  6400   case RADIO_STATE_SIM_READY:
  6401   case RADIO_STATE_SIM_NOT_READY:
  6402   case RADIO_STATE_SIM_LOCKED_OR_ABSENT:
  6403     this._isCdma = false;
  6404     this._waitingRadioTech = false;
  6405     break;
  6406   case RADIO_STATE_RUIM_READY:
  6407   case RADIO_STATE_RUIM_NOT_READY:
  6408   case RADIO_STATE_RUIM_LOCKED_OR_ABSENT:
  6409   case RADIO_STATE_NV_READY:
  6410   case RADIO_STATE_NV_NOT_READY:
  6411     this._isCdma = true;
  6412     this._waitingRadioTech = false;
  6413     break;
  6414   case RADIO_STATE_ON: // RIL v7
  6415     // This value is defined in RIL v7, we will retrieve radio tech by another
  6416     // request. We leave _isCdma untouched, and it will be set once we get the
  6417     // radio technology.
  6418     this._waitingRadioTech = true;
  6419     this.getVoiceRadioTechnology();
  6420     break;
  6423   if ((this.radioState == GECKO_RADIOSTATE_UNAVAILABLE ||
  6424        this.radioState == GECKO_RADIOSTATE_OFF) &&
  6425        newState == GECKO_RADIOSTATE_READY) {
  6426     // The radio became available, let's get its info.
  6427     if (!this._waitingRadioTech) {
  6428       if (this._isCdma) {
  6429         this.getDeviceIdentity();
  6430       } else {
  6431         this.getIMEI();
  6432         this.getIMEISV();
  6435     this.getBasebandVersion();
  6436     this.updateCellBroadcastConfig();
  6437     this.setPreferredNetworkType();
  6438     this.setCLIR();
  6439     if (RILQUIRKS_DATA_REGISTRATION_ON_DEMAND && this._attachDataRegistration) {
  6440       this.setDataRegistration({attach: true});
  6444   this.radioState = newState;
  6445   this.sendChromeMessage({
  6446     rilMessageType: "radiostatechange",
  6447     radioState: newState
  6448   });
  6450   // If the radio is up and on, so let's query the card state.
  6451   // On older RILs only if the card is actually ready, though.
  6452   // If _waitingRadioTech is set, we don't need to get icc status now.
  6453   if (radioState == RADIO_STATE_UNAVAILABLE ||
  6454       radioState == RADIO_STATE_OFF ||
  6455       this._waitingRadioTech) {
  6456     return;
  6458   this.getICCStatus();
  6459 };
  6460 RilObject.prototype[UNSOLICITED_RESPONSE_CALL_STATE_CHANGED] = function UNSOLICITED_RESPONSE_CALL_STATE_CHANGED() {
  6461   this.getCurrentCalls();
  6462 };
  6463 RilObject.prototype[UNSOLICITED_RESPONSE_VOICE_NETWORK_STATE_CHANGED] = function UNSOLICITED_RESPONSE_VOICE_NETWORK_STATE_CHANGED() {
  6464   if (DEBUG) {
  6465     this.context.debug("Network state changed, re-requesting phone state and " +
  6466                        "ICC status");
  6468   this.getICCStatus();
  6469   this.requestNetworkInfo();
  6470 };
  6471 RilObject.prototype[UNSOLICITED_RESPONSE_NEW_SMS] = function UNSOLICITED_RESPONSE_NEW_SMS(length) {
  6472   let [message, result] = this.context.GsmPDUHelper.processReceivedSms(length);
  6474   if (message) {
  6475     result = this._processSmsMultipart(message);
  6478   if (result == PDU_FCS_RESERVED || result == MOZ_FCS_WAIT_FOR_EXPLICIT_ACK) {
  6479     return;
  6482   // Not reserved FCS values, send ACK now.
  6483   this.acknowledgeGsmSms(result == PDU_FCS_OK, result);
  6484 };
  6485 RilObject.prototype[UNSOLICITED_RESPONSE_NEW_SMS_STATUS_REPORT] = function UNSOLICITED_RESPONSE_NEW_SMS_STATUS_REPORT(length) {
  6486   let result = this._processSmsStatusReport(length);
  6487   this.acknowledgeGsmSms(result == PDU_FCS_OK, result);
  6488 };
  6489 RilObject.prototype[UNSOLICITED_RESPONSE_NEW_SMS_ON_SIM] = function UNSOLICITED_RESPONSE_NEW_SMS_ON_SIM(length) {
  6490   let recordNumber = this.context.Buf.readInt32List()[0];
  6492   this.context.SimRecordHelper.readSMS(
  6493     recordNumber,
  6494     function onsuccess(message) {
  6495       if (message && message.simStatus === 3) { //New Unread SMS
  6496         this._processSmsMultipart(message);
  6498     }.bind(this),
  6499     function onerror(errorMsg) {
  6500       if (DEBUG) {
  6501         this.context.debug("Failed to Read NEW SMS on SIM #" + recordNumber +
  6502                            ", errorMsg: " + errorMsg);
  6504     });
  6505 };
  6506 RilObject.prototype[UNSOLICITED_ON_USSD] = function UNSOLICITED_ON_USSD() {
  6507   let [typeCode, message] = this.context.Buf.readStringList();
  6508   if (DEBUG) {
  6509     this.context.debug("On USSD. Type Code: " + typeCode + " Message: " + message);
  6512   this._ussdSession = (typeCode != "0" && typeCode != "2");
  6514   this.sendChromeMessage({rilMessageType: "USSDReceived",
  6515                           message: message,
  6516                           sessionEnded: !this._ussdSession});
  6517 };
  6518 RilObject.prototype[UNSOLICITED_NITZ_TIME_RECEIVED] = function UNSOLICITED_NITZ_TIME_RECEIVED() {
  6519   let dateString = this.context.Buf.readString();
  6521   // The data contained in the NITZ message is
  6522   // in the form "yy/mm/dd,hh:mm:ss(+/-)tz,dt"
  6523   // for example: 12/02/16,03:36:08-20,00,310410
  6524   // See also bug 714352 - Listen for NITZ updates from rild.
  6526   if (DEBUG) this.context.debug("DateTimeZone string " + dateString);
  6528   let now = Date.now();
  6530   let year = parseInt(dateString.substr(0, 2), 10);
  6531   let month = parseInt(dateString.substr(3, 2), 10);
  6532   let day = parseInt(dateString.substr(6, 2), 10);
  6533   let hours = parseInt(dateString.substr(9, 2), 10);
  6534   let minutes = parseInt(dateString.substr(12, 2), 10);
  6535   let seconds = parseInt(dateString.substr(15, 2), 10);
  6536   // Note that |tz| is in 15-min units.
  6537   let tz = parseInt(dateString.substr(17, 3), 10);
  6538   // Note that |dst| is in 1-hour units and is already applied in |tz|.
  6539   let dst = parseInt(dateString.substr(21, 2), 10);
  6541   let timeInMS = Date.UTC(year + PDU_TIMESTAMP_YEAR_OFFSET, month - 1, day,
  6542                           hours, minutes, seconds);
  6544   if (isNaN(timeInMS)) {
  6545     if (DEBUG) this.context.debug("NITZ failed to convert date");
  6546     return;
  6549   this.sendChromeMessage({rilMessageType: "nitzTime",
  6550                           networkTimeInMS: timeInMS,
  6551                           networkTimeZoneInMinutes: -(tz * 15),
  6552                           networkDSTInMinutes: -(dst * 60),
  6553                           receiveTimeInMS: now});
  6554 };
  6556 RilObject.prototype[UNSOLICITED_SIGNAL_STRENGTH] = function UNSOLICITED_SIGNAL_STRENGTH(length) {
  6557   this[REQUEST_SIGNAL_STRENGTH](length, {rilRequestError: ERROR_SUCCESS});
  6558 };
  6559 RilObject.prototype[UNSOLICITED_DATA_CALL_LIST_CHANGED] = function UNSOLICITED_DATA_CALL_LIST_CHANGED(length) {
  6560   if (this.v5Legacy) {
  6561     this.getDataCallList();
  6562     return;
  6564   this[REQUEST_DATA_CALL_LIST](length, {rilRequestError: ERROR_SUCCESS});
  6565 };
  6566 RilObject.prototype[UNSOLICITED_SUPP_SVC_NOTIFICATION] = function UNSOLICITED_SUPP_SVC_NOTIFICATION(length) {
  6567   let Buf = this.context.Buf;
  6568   let info = {};
  6569   info.notificationType = Buf.readInt32();
  6570   info.code = Buf.readInt32();
  6571   info.index = Buf.readInt32();
  6572   info.type = Buf.readInt32();
  6573   info.number = Buf.readString();
  6575   this._processSuppSvcNotification(info);
  6576 };
  6578 RilObject.prototype[UNSOLICITED_STK_SESSION_END] = function UNSOLICITED_STK_SESSION_END() {
  6579   this.sendChromeMessage({rilMessageType: "stksessionend"});
  6580 };
  6581 RilObject.prototype[UNSOLICITED_STK_PROACTIVE_COMMAND] = function UNSOLICITED_STK_PROACTIVE_COMMAND() {
  6582   this.processStkProactiveCommand();
  6583 };
  6584 RilObject.prototype[UNSOLICITED_STK_EVENT_NOTIFY] = function UNSOLICITED_STK_EVENT_NOTIFY() {
  6585   this.processStkProactiveCommand();
  6586 };
  6587 RilObject.prototype[UNSOLICITED_STK_CALL_SETUP] = null;
  6588 RilObject.prototype[UNSOLICITED_SIM_SMS_STORAGE_FULL] = null;
  6589 RilObject.prototype[UNSOLICITED_SIM_REFRESH] = null;
  6590 RilObject.prototype[UNSOLICITED_CALL_RING] = function UNSOLICITED_CALL_RING() {
  6591   let Buf = this.context.Buf;
  6592   let info = {rilMessageType: "callRing"};
  6593   let isCDMA = false; //XXX TODO hard-code this for now
  6594   if (isCDMA) {
  6595     info.isPresent = Buf.readInt32();
  6596     info.signalType = Buf.readInt32();
  6597     info.alertPitch = Buf.readInt32();
  6598     info.signal = Buf.readInt32();
  6600   // At this point we don't know much other than the fact there's an incoming
  6601   // call, but that's enough to bring up the Phone app already. We'll know
  6602   // details once we get a call state changed notification and can then
  6603   // dispatch DOM events etc.
  6604   this.sendChromeMessage(info);
  6605 };
  6606 RilObject.prototype[UNSOLICITED_RESPONSE_SIM_STATUS_CHANGED] = function UNSOLICITED_RESPONSE_SIM_STATUS_CHANGED() {
  6607   this.getICCStatus();
  6608 };
  6609 RilObject.prototype[UNSOLICITED_RESPONSE_CDMA_NEW_SMS] = function UNSOLICITED_RESPONSE_CDMA_NEW_SMS(length) {
  6610   let [message, result] = this.context.CdmaPDUHelper.processReceivedSms(length);
  6612   if (message) {
  6613     if (message.teleservice === PDU_CDMA_MSG_TELESERIVCIE_ID_WAP) {
  6614       result = this._processCdmaSmsWapPush(message);
  6615     } else if (message.subMsgType === PDU_CDMA_MSG_TYPE_DELIVER_ACK) {
  6616       result = this._processCdmaSmsStatusReport(message);
  6617     } else {
  6618       result = this._processSmsMultipart(message);
  6622   if (result == PDU_FCS_RESERVED || result == MOZ_FCS_WAIT_FOR_EXPLICIT_ACK) {
  6623     return;
  6626   // Not reserved FCS values, send ACK now.
  6627   this.acknowledgeCdmaSms(result == PDU_FCS_OK, result);
  6628 };
  6629 RilObject.prototype[UNSOLICITED_RESPONSE_NEW_BROADCAST_SMS] = function UNSOLICITED_RESPONSE_NEW_BROADCAST_SMS(length) {
  6630   let message;
  6631   try {
  6632     message =
  6633       this.context.GsmPDUHelper.readCbMessage(this.context.Buf.readInt32());
  6634   } catch (e) {
  6635     if (DEBUG) {
  6636       this.context.debug("Failed to parse Cell Broadcast message: " +
  6637                          JSON.stringify(e));
  6639     return;
  6642   message = this._processReceivedSmsCbPage(message);
  6643   if (!message) {
  6644     return;
  6647   message.rilMessageType = "cellbroadcast-received";
  6648   this.sendChromeMessage(message);
  6649 };
  6650 RilObject.prototype[UNSOLICITED_CDMA_RUIM_SMS_STORAGE_FULL] = null;
  6651 RilObject.prototype[UNSOLICITED_RESTRICTED_STATE_CHANGED] = null;
  6652 RilObject.prototype[UNSOLICITED_ENTER_EMERGENCY_CALLBACK_MODE] = function UNSOLICITED_ENTER_EMERGENCY_CALLBACK_MODE() {
  6653   this._handleChangedEmergencyCbMode(true);
  6654 };
  6655 RilObject.prototype[UNSOLICITED_CDMA_CALL_WAITING] = function UNSOLICITED_CDMA_CALL_WAITING(length) {
  6656   let Buf = this.context.Buf;
  6657   let call = {};
  6658   call.number              = Buf.readString();
  6659   call.numberPresentation  = Buf.readInt32();
  6660   call.name                = Buf.readString();
  6661   call.namePresentation    = Buf.readInt32();
  6662   call.isPresent           = Buf.readInt32();
  6663   call.signalType          = Buf.readInt32();
  6664   call.alertPitch          = Buf.readInt32();
  6665   call.signal              = Buf.readInt32();
  6666   this.sendChromeMessage({rilMessageType: "cdmaCallWaiting",
  6667                           number: call.number});
  6668 };
  6669 RilObject.prototype[UNSOLICITED_CDMA_OTA_PROVISION_STATUS] = function UNSOLICITED_CDMA_OTA_PROVISION_STATUS() {
  6670   let status = this.context.Buf.readInt32List()[0];
  6671   this.sendChromeMessage({rilMessageType: "otastatuschange",
  6672                           status: status});
  6673 };
  6674 RilObject.prototype[UNSOLICITED_CDMA_INFO_REC] = function UNSOLICITED_CDMA_INFO_REC(length) {
  6675   let record = this.context.CdmaPDUHelper.decodeInformationRecord();
  6676   record.rilMessageType = "cdma-info-rec-received";
  6677   this.sendChromeMessage(record);
  6678 };
  6679 RilObject.prototype[UNSOLICITED_OEM_HOOK_RAW] = null;
  6680 RilObject.prototype[UNSOLICITED_RINGBACK_TONE] = null;
  6681 RilObject.prototype[UNSOLICITED_RESEND_INCALL_MUTE] = null;
  6682 RilObject.prototype[UNSOLICITED_CDMA_SUBSCRIPTION_SOURCE_CHANGED] = null;
  6683 RilObject.prototype[UNSOLICITED_CDMA_PRL_CHANGED] = function UNSOLICITED_CDMA_PRL_CHANGED(length) {
  6684   let version = this.context.Buf.readInt32List()[0];
  6685   if (version !== this.iccInfo.prlVersion) {
  6686     this.iccInfo.prlVersion = version;
  6687     this.context.ICCUtilsHelper.handleICCInfoChange();
  6689 };
  6690 RilObject.prototype[UNSOLICITED_EXIT_EMERGENCY_CALLBACK_MODE] = function UNSOLICITED_EXIT_EMERGENCY_CALLBACK_MODE() {
  6691   this._handleChangedEmergencyCbMode(false);
  6692 };
  6693 RilObject.prototype[UNSOLICITED_RIL_CONNECTED] = function UNSOLICITED_RIL_CONNECTED(length) {
  6694   // Prevent response id collision between UNSOLICITED_RIL_CONNECTED and
  6695   // UNSOLICITED_VOICE_RADIO_TECH_CHANGED for Akami on gingerbread branch.
  6696   if (!length) {
  6697     return;
  6700   let version = this.context.Buf.readInt32List()[0];
  6701   this.v5Legacy = (version < 5);
  6702   if (DEBUG) {
  6703     this.context.debug("Detected RIL version " + version);
  6704     this.context.debug("this.v5Legacy is " + this.v5Legacy);
  6707   this.initRILState();
  6708   // Always ensure that we are not in emergency callback mode when init.
  6709   this.exitEmergencyCbMode();
  6710   // Reset radio in the case that b2g restart (or crash).
  6711   this.setRadioEnabled({enabled: false});
  6712 };
  6714 /**
  6715  * This object exposes the functionality to parse and serialize PDU strings
  6717  * A PDU is a string containing a series of hexadecimally encoded octets
  6718  * or nibble-swapped binary-coded decimals (BCDs). It contains not only the
  6719  * message text but information about the sender, the SMS service center,
  6720  * timestamp, etc.
  6721  */
  6722 function GsmPDUHelperObject(aContext) {
  6723   this.context = aContext;
  6725 GsmPDUHelperObject.prototype = {
  6726   context: null,
  6728   /**
  6729    * Read one character (2 bytes) from a RIL string and decode as hex.
  6731    * @return the nibble as a number.
  6732    */
  6733   readHexNibble: function() {
  6734     let nibble = this.context.Buf.readUint16();
  6735     if (nibble >= 48 && nibble <= 57) {
  6736       nibble -= 48; // ASCII '0'..'9'
  6737     } else if (nibble >= 65 && nibble <= 70) {
  6738       nibble -= 55; // ASCII 'A'..'F'
  6739     } else if (nibble >= 97 && nibble <= 102) {
  6740       nibble -= 87; // ASCII 'a'..'f'
  6741     } else {
  6742       throw "Found invalid nibble during PDU parsing: " +
  6743             String.fromCharCode(nibble);
  6745     return nibble;
  6746   },
  6748   /**
  6749    * Encode a nibble as one hex character in a RIL string (2 bytes).
  6751    * @param nibble
  6752    *        The nibble to encode (represented as a number)
  6753    */
  6754   writeHexNibble: function(nibble) {
  6755     nibble &= 0x0f;
  6756     if (nibble < 10) {
  6757       nibble += 48; // ASCII '0'
  6758     } else {
  6759       nibble += 55; // ASCII 'A'
  6761     this.context.Buf.writeUint16(nibble);
  6762   },
  6764   /**
  6765    * Read a hex-encoded octet (two nibbles).
  6767    * @return the octet as a number.
  6768    */
  6769   readHexOctet: function() {
  6770     return (this.readHexNibble() << 4) | this.readHexNibble();
  6771   },
  6773   /**
  6774    * Write an octet as two hex-encoded nibbles.
  6776    * @param octet
  6777    *        The octet (represented as a number) to encode.
  6778    */
  6779   writeHexOctet: function(octet) {
  6780     this.writeHexNibble(octet >> 4);
  6781     this.writeHexNibble(octet);
  6782   },
  6784   /**
  6785    * Read an array of hex-encoded octets.
  6786    */
  6787   readHexOctetArray: function(length) {
  6788     let array = new Uint8Array(length);
  6789     for (let i = 0; i < length; i++) {
  6790       array[i] = this.readHexOctet();
  6792     return array;
  6793   },
  6795   /**
  6796    * Convert an octet (number) to a BCD number.
  6798    * Any nibbles that are not in the BCD range count as 0.
  6800    * @param octet
  6801    *        The octet (a number, as returned by getOctet())
  6803    * @return the corresponding BCD number.
  6804    */
  6805   octetToBCD: function(octet) {
  6806     return ((octet & 0xf0) <= 0x90) * ((octet >> 4) & 0x0f) +
  6807            ((octet & 0x0f) <= 0x09) * (octet & 0x0f) * 10;
  6808   },
  6810   /**
  6811    * Convert a BCD number to an octet (number)
  6813    * Only take two digits with absolute value.
  6815    * @param bcd
  6817    * @return the corresponding octet.
  6818    */
  6819   BCDToOctet: function(bcd) {
  6820     bcd = Math.abs(bcd);
  6821     return ((bcd % 10) << 4) + (Math.floor(bcd / 10) % 10);
  6822   },
  6824   /**
  6825    * Convert a semi-octet (number) to a GSM BCD char, or return empty string
  6826    * if invalid semiOctet and supressException is set to true.
  6828    * @param semiOctet
  6829    *        Nibble to be converted to.
  6830    * @param [optional] supressException
  6831    *        Supress exception if invalid semiOctet and supressException is set
  6832    *        to true.
  6834    * @return GSM BCD char, or empty string.
  6835    */
  6836   bcdChars: "0123456789*#,;",
  6837   semiOctetToBcdChar: function(semiOctet, supressException) {
  6838     if (semiOctet >= 14) {
  6839       if (supressException) {
  6840         return "";
  6841       } else {
  6842         throw new RangeError();
  6846     return this.bcdChars.charAt(semiOctet);
  6847   },
  6849   /**
  6850    * Read a *swapped nibble* binary coded decimal (BCD)
  6852    * @param pairs
  6853    *        Number of nibble *pairs* to read.
  6855    * @return the decimal as a number.
  6856    */
  6857   readSwappedNibbleBcdNum: function(pairs) {
  6858     let number = 0;
  6859     for (let i = 0; i < pairs; i++) {
  6860       let octet = this.readHexOctet();
  6861       // Ignore 'ff' octets as they're often used as filler.
  6862       if (octet == 0xff) {
  6863         continue;
  6865       // If the first nibble is an "F" , only the second nibble is to be taken
  6866       // into account.
  6867       if ((octet & 0xf0) == 0xf0) {
  6868         number *= 10;
  6869         number += octet & 0x0f;
  6870         continue;
  6872       number *= 100;
  6873       number += this.octetToBCD(octet);
  6875     return number;
  6876   },
  6878   /**
  6879    * Read a *swapped nibble* binary coded string (BCD)
  6881    * @param pairs
  6882    *        Number of nibble *pairs* to read.
  6883    * @param [optional] supressException
  6884    *        Supress exception if invalid semiOctet and supressException is set
  6885    *        to true.
  6887    * @return The BCD string.
  6888    */
  6889   readSwappedNibbleBcdString: function(pairs, supressException) {
  6890     let str = "";
  6891     for (let i = 0; i < pairs; i++) {
  6892       let nibbleH = this.readHexNibble();
  6893       let nibbleL = this.readHexNibble();
  6894       if (nibbleL == 0x0F) {
  6895         break;
  6898       str += this.semiOctetToBcdChar(nibbleL, supressException);
  6899       if (nibbleH != 0x0F) {
  6900         str += this.semiOctetToBcdChar(nibbleH, supressException);
  6904     return str;
  6905   },
  6907   /**
  6908    * Write numerical data as swapped nibble BCD.
  6910    * @param data
  6911    *        Data to write (as a string or a number)
  6912    */
  6913   writeSwappedNibbleBCD: function(data) {
  6914     data = data.toString();
  6915     if (data.length % 2) {
  6916       data += "F";
  6918     let Buf = this.context.Buf;
  6919     for (let i = 0; i < data.length; i += 2) {
  6920       Buf.writeUint16(data.charCodeAt(i + 1));
  6921       Buf.writeUint16(data.charCodeAt(i));
  6923   },
  6925   /**
  6926    * Write numerical data as swapped nibble BCD.
  6927    * If the number of digit of data is even, add '0' at the beginning.
  6929    * @param data
  6930    *        Data to write (as a string or a number)
  6931    */
  6932   writeSwappedNibbleBCDNum: function(data) {
  6933     data = data.toString();
  6934     if (data.length % 2) {
  6935       data = "0" + data;
  6937     let Buf = this.context.Buf;
  6938     for (let i = 0; i < data.length; i += 2) {
  6939       Buf.writeUint16(data.charCodeAt(i + 1));
  6940       Buf.writeUint16(data.charCodeAt(i));
  6942   },
  6944   /**
  6945    * Read user data, convert to septets, look up relevant characters in a
  6946    * 7-bit alphabet, and construct string.
  6948    * @param length
  6949    *        Number of septets to read (*not* octets)
  6950    * @param paddingBits
  6951    *        Number of padding bits in the first byte of user data.
  6952    * @param langIndex
  6953    *        Table index used for normal 7-bit encoded character lookup.
  6954    * @param langShiftIndex
  6955    *        Table index used for escaped 7-bit encoded character lookup.
  6957    * @return a string.
  6958    */
  6959   readSeptetsToString: function(length, paddingBits, langIndex, langShiftIndex) {
  6960     let ret = "";
  6961     let byteLength = Math.ceil((length * 7 + paddingBits) / 8);
  6963     /**
  6964      * |<-                    last byte in header                    ->|
  6965      * |<-           incompleteBits          ->|<- last header septet->|
  6966      * +===7===|===6===|===5===|===4===|===3===|===2===|===1===|===0===|
  6968      * |<-                   1st byte in user data                   ->|
  6969      * |<-               data septet 1               ->|<-paddingBits->|
  6970      * +===7===|===6===|===5===|===4===|===3===|===2===|===1===|===0===|
  6972      * |<-                   2nd byte in user data                   ->|
  6973      * |<-                   data spetet 2                   ->|<-ds1->|
  6974      * +===7===|===6===|===5===|===4===|===3===|===2===|===1===|===0===|
  6975      */
  6976     let data = 0;
  6977     let dataBits = 0;
  6978     if (paddingBits) {
  6979       data = this.readHexOctet() >> paddingBits;
  6980       dataBits = 8 - paddingBits;
  6981       --byteLength;
  6984     let escapeFound = false;
  6985     const langTable = PDU_NL_LOCKING_SHIFT_TABLES[langIndex];
  6986     const langShiftTable = PDU_NL_SINGLE_SHIFT_TABLES[langShiftIndex];
  6987     do {
  6988       // Read as much as fits in 32bit word
  6989       let bytesToRead = Math.min(byteLength, dataBits ? 3 : 4);
  6990       for (let i = 0; i < bytesToRead; i++) {
  6991         data |= this.readHexOctet() << dataBits;
  6992         dataBits += 8;
  6993         --byteLength;
  6996       // Consume available full septets
  6997       for (; dataBits >= 7; dataBits -= 7) {
  6998         let septet = data & 0x7F;
  6999         data >>>= 7;
  7001         if (escapeFound) {
  7002           escapeFound = false;
  7003           if (septet == PDU_NL_EXTENDED_ESCAPE) {
  7004             // According to 3GPP TS 23.038, section 6.2.1.1, NOTE 1, "On
  7005             // receipt of this code, a receiving entity shall display a space
  7006             // until another extensiion table is defined."
  7007             ret += " ";
  7008           } else if (septet == PDU_NL_RESERVED_CONTROL) {
  7009             // According to 3GPP TS 23.038 B.2, "This code represents a control
  7010             // character and therefore must not be used for language specific
  7011             // characters."
  7012             ret += " ";
  7013           } else {
  7014             ret += langShiftTable[septet];
  7016         } else if (septet == PDU_NL_EXTENDED_ESCAPE) {
  7017           escapeFound = true;
  7019           // <escape> is not an effective character
  7020           --length;
  7021         } else {
  7022           ret += langTable[septet];
  7025     } while (byteLength);
  7027     if (ret.length != length) {
  7028       /**
  7029        * If num of effective characters does not equal to the length of read
  7030        * string, cut the tail off. This happens when the last octet of user
  7031        * data has following layout:
  7033        * |<-              penultimate octet in user data               ->|
  7034        * |<-               data septet N               ->|<-   dsN-1   ->|
  7035        * +===7===|===6===|===5===|===4===|===3===|===2===|===1===|===0===|
  7037        * |<-                  last octet in user data                  ->|
  7038        * |<-                       fill bits                   ->|<-dsN->|
  7039        * +===7===|===6===|===5===|===4===|===3===|===2===|===1===|===0===|
  7041        * The fill bits in the last octet may happen to form a full septet and
  7042        * be appended at the end of result string.
  7043        */
  7044       ret = ret.slice(0, length);
  7046     return ret;
  7047   },
  7049   writeStringAsSeptets: function(message, paddingBits, langIndex, langShiftIndex) {
  7050     const langTable = PDU_NL_LOCKING_SHIFT_TABLES[langIndex];
  7051     const langShiftTable = PDU_NL_SINGLE_SHIFT_TABLES[langShiftIndex];
  7053     let dataBits = paddingBits;
  7054     let data = 0;
  7055     for (let i = 0; i < message.length; i++) {
  7056       let c = message.charAt(i);
  7057       let septet = langTable.indexOf(c);
  7058       if (septet == PDU_NL_EXTENDED_ESCAPE) {
  7059         continue;
  7062       if (septet >= 0) {
  7063         data |= septet << dataBits;
  7064         dataBits += 7;
  7065       } else {
  7066         septet = langShiftTable.indexOf(c);
  7067         if (septet == -1) {
  7068           throw new Error("'" + c + "' is not in 7 bit alphabet "
  7069                           + langIndex + ":" + langShiftIndex + "!");
  7072         if (septet == PDU_NL_RESERVED_CONTROL) {
  7073           continue;
  7076         data |= PDU_NL_EXTENDED_ESCAPE << dataBits;
  7077         dataBits += 7;
  7078         data |= septet << dataBits;
  7079         dataBits += 7;
  7082       for (; dataBits >= 8; dataBits -= 8) {
  7083         this.writeHexOctet(data & 0xFF);
  7084         data >>>= 8;
  7088     if (dataBits !== 0) {
  7089       this.writeHexOctet(data & 0xFF);
  7091   },
  7093   /**
  7094    * Read user data and decode as a UCS2 string.
  7096    * @param numOctets
  7097    *        Number of octets to be read as UCS2 string.
  7099    * @return a string.
  7100    */
  7101   readUCS2String: function(numOctets) {
  7102     let str = "";
  7103     let length = numOctets / 2;
  7104     for (let i = 0; i < length; ++i) {
  7105       let code = (this.readHexOctet() << 8) | this.readHexOctet();
  7106       str += String.fromCharCode(code);
  7109     if (DEBUG) this.context.debug("Read UCS2 string: " + str);
  7111     return str;
  7112   },
  7114   /**
  7115    * Write user data as a UCS2 string.
  7117    * @param message
  7118    *        Message string to encode as UCS2 in hex-encoded octets.
  7119    */
  7120   writeUCS2String: function(message) {
  7121     for (let i = 0; i < message.length; ++i) {
  7122       let code = message.charCodeAt(i);
  7123       this.writeHexOctet((code >> 8) & 0xFF);
  7124       this.writeHexOctet(code & 0xFF);
  7126   },
  7128   /**
  7129    * Read 1 + UDHL octets and construct user data header.
  7131    * @param msg
  7132    *        message object for output.
  7134    * @see 3GPP TS 23.040 9.2.3.24
  7135    */
  7136   readUserDataHeader: function(msg) {
  7137     /**
  7138      * A header object with properties contained in received message.
  7139      * The properties set include:
  7141      * length: totoal length of the header, default 0.
  7142      * langIndex: used locking shift table index, default
  7143      * PDU_NL_IDENTIFIER_DEFAULT.
  7144      * langShiftIndex: used locking shift table index, default
  7145      * PDU_NL_IDENTIFIER_DEFAULT.
  7147      */
  7148     let header = {
  7149       length: 0,
  7150       langIndex: PDU_NL_IDENTIFIER_DEFAULT,
  7151       langShiftIndex: PDU_NL_IDENTIFIER_DEFAULT
  7152     };
  7154     header.length = this.readHexOctet();
  7155     if (DEBUG) this.context.debug("Read UDH length: " + header.length);
  7157     let dataAvailable = header.length;
  7158     while (dataAvailable >= 2) {
  7159       let id = this.readHexOctet();
  7160       let length = this.readHexOctet();
  7161       if (DEBUG) this.context.debug("Read UDH id: " + id + ", length: " + length);
  7163       dataAvailable -= 2;
  7165       switch (id) {
  7166         case PDU_IEI_CONCATENATED_SHORT_MESSAGES_8BIT: {
  7167           let ref = this.readHexOctet();
  7168           let max = this.readHexOctet();
  7169           let seq = this.readHexOctet();
  7170           dataAvailable -= 3;
  7171           if (max && seq && (seq <= max)) {
  7172             header.segmentRef = ref;
  7173             header.segmentMaxSeq = max;
  7174             header.segmentSeq = seq;
  7176           break;
  7178         case PDU_IEI_APPLICATION_PORT_ADDRESSING_SCHEME_8BIT: {
  7179           let dstp = this.readHexOctet();
  7180           let orip = this.readHexOctet();
  7181           dataAvailable -= 2;
  7182           if ((dstp < PDU_APA_RESERVED_8BIT_PORTS)
  7183               || (orip < PDU_APA_RESERVED_8BIT_PORTS)) {
  7184             // 3GPP TS 23.040 clause 9.2.3.24.3: "A receiving entity shall
  7185             // ignore any information element where the value of the
  7186             // Information-Element-Data is Reserved or not supported"
  7187             break;
  7189           header.destinationPort = dstp;
  7190           header.originatorPort = orip;
  7191           break;
  7193         case PDU_IEI_APPLICATION_PORT_ADDRESSING_SCHEME_16BIT: {
  7194           let dstp = (this.readHexOctet() << 8) | this.readHexOctet();
  7195           let orip = (this.readHexOctet() << 8) | this.readHexOctet();
  7196           dataAvailable -= 4;
  7197           // 3GPP TS 23.040 clause 9.2.3.24.4: "A receiving entity shall
  7198           // ignore any information element where the value of the
  7199           // Information-Element-Data is Reserved or not supported"
  7200           if ((dstp < PDU_APA_VALID_16BIT_PORTS)
  7201               && (orip < PDU_APA_VALID_16BIT_PORTS)) {
  7202             header.destinationPort = dstp;
  7203             header.originatorPort = orip;
  7205           break;
  7207         case PDU_IEI_CONCATENATED_SHORT_MESSAGES_16BIT: {
  7208           let ref = (this.readHexOctet() << 8) | this.readHexOctet();
  7209           let max = this.readHexOctet();
  7210           let seq = this.readHexOctet();
  7211           dataAvailable -= 4;
  7212           if (max && seq && (seq <= max)) {
  7213             header.segmentRef = ref;
  7214             header.segmentMaxSeq = max;
  7215             header.segmentSeq = seq;
  7217           break;
  7219         case PDU_IEI_NATIONAL_LANGUAGE_SINGLE_SHIFT:
  7220           let langShiftIndex = this.readHexOctet();
  7221           --dataAvailable;
  7222           if (langShiftIndex < PDU_NL_SINGLE_SHIFT_TABLES.length) {
  7223             header.langShiftIndex = langShiftIndex;
  7225           break;
  7226         case PDU_IEI_NATIONAL_LANGUAGE_LOCKING_SHIFT:
  7227           let langIndex = this.readHexOctet();
  7228           --dataAvailable;
  7229           if (langIndex < PDU_NL_LOCKING_SHIFT_TABLES.length) {
  7230             header.langIndex = langIndex;
  7232           break;
  7233         case PDU_IEI_SPECIAL_SMS_MESSAGE_INDICATION:
  7234           let msgInd = this.readHexOctet() & 0xFF;
  7235           let msgCount = this.readHexOctet();
  7236           dataAvailable -= 2;
  7239           /*
  7240            * TS 23.040 V6.8.1 Sec 9.2.3.24.2
  7241            * bits 1 0   : basic message indication type
  7242            * bits 4 3 2 : extended message indication type
  7243            * bits 6 5   : Profile id
  7244            * bit  7     : storage type
  7245            */
  7246           let storeType = msgInd & PDU_MWI_STORE_TYPE_BIT;
  7247           let mwi = msg.mwi;
  7248           if (!mwi) {
  7249             mwi = msg.mwi = {};
  7252           if (storeType == PDU_MWI_STORE_TYPE_STORE) {
  7253             // Store message because TP_UDH indicates so, note this may override
  7254             // the setting in DCS, but that is expected
  7255             mwi.discard = false;
  7256           } else if (mwi.discard === undefined) {
  7257             // storeType == PDU_MWI_STORE_TYPE_DISCARD
  7258             // only override mwi.discard here if it hasn't already been set
  7259             mwi.discard = true;
  7262           mwi.msgCount = msgCount & 0xFF;
  7263           mwi.active = mwi.msgCount > 0;
  7265           if (DEBUG) {
  7266             this.context.debug("MWI in TP_UDH received: " + JSON.stringify(mwi));
  7269           break;
  7270         default:
  7271           if (DEBUG) {
  7272             this.context.debug("readUserDataHeader: unsupported IEI(" + id +
  7273                                "), " + length + " bytes.");
  7276           // Read out unsupported data
  7277           if (length) {
  7278             let octets;
  7279             if (DEBUG) octets = new Uint8Array(length);
  7281             for (let i = 0; i < length; i++) {
  7282               let octet = this.readHexOctet();
  7283               if (DEBUG) octets[i] = octet;
  7285             dataAvailable -= length;
  7287             if (DEBUG) {
  7288               this.context.debug("readUserDataHeader: " + Array.slice(octets));
  7291           break;
  7295     if (dataAvailable !== 0) {
  7296       throw new Error("Illegal user data header found!");
  7299     msg.header = header;
  7300   },
  7302   /**
  7303    * Write out user data header.
  7305    * @param options
  7306    *        Options containing information for user data header write-out. The
  7307    *        `userDataHeaderLength` property must be correctly pre-calculated.
  7308    */
  7309   writeUserDataHeader: function(options) {
  7310     this.writeHexOctet(options.userDataHeaderLength);
  7312     if (options.segmentMaxSeq > 1) {
  7313       if (options.segmentRef16Bit) {
  7314         this.writeHexOctet(PDU_IEI_CONCATENATED_SHORT_MESSAGES_16BIT);
  7315         this.writeHexOctet(4);
  7316         this.writeHexOctet((options.segmentRef >> 8) & 0xFF);
  7317       } else {
  7318         this.writeHexOctet(PDU_IEI_CONCATENATED_SHORT_MESSAGES_8BIT);
  7319         this.writeHexOctet(3);
  7321       this.writeHexOctet(options.segmentRef & 0xFF);
  7322       this.writeHexOctet(options.segmentMaxSeq & 0xFF);
  7323       this.writeHexOctet(options.segmentSeq & 0xFF);
  7326     if (options.dcs == PDU_DCS_MSG_CODING_7BITS_ALPHABET) {
  7327       if (options.langIndex != PDU_NL_IDENTIFIER_DEFAULT) {
  7328         this.writeHexOctet(PDU_IEI_NATIONAL_LANGUAGE_LOCKING_SHIFT);
  7329         this.writeHexOctet(1);
  7330         this.writeHexOctet(options.langIndex);
  7333       if (options.langShiftIndex != PDU_NL_IDENTIFIER_DEFAULT) {
  7334         this.writeHexOctet(PDU_IEI_NATIONAL_LANGUAGE_SINGLE_SHIFT);
  7335         this.writeHexOctet(1);
  7336         this.writeHexOctet(options.langShiftIndex);
  7339   },
  7341   /**
  7342    * Read SM-TL Address.
  7344    * @param len
  7345    *        Length of useful semi-octets within the Address-Value field. For
  7346    *        example, the lenth of "12345" should be 5, and 4 for "1234".
  7348    * @see 3GPP TS 23.040 9.1.2.5
  7349    */
  7350   readAddress: function(len) {
  7351     // Address Length
  7352     if (!len || (len < 0)) {
  7353       if (DEBUG) {
  7354         this.context.debug("PDU error: invalid sender address length: " + len);
  7356       return null;
  7358     if (len % 2 == 1) {
  7359       len += 1;
  7361     if (DEBUG) this.context.debug("PDU: Going to read address: " + len);
  7363     // Type-of-Address
  7364     let toa = this.readHexOctet();
  7365     let addr = "";
  7367     if ((toa & 0xF0) == PDU_TOA_ALPHANUMERIC) {
  7368       addr = this.readSeptetsToString(Math.floor(len * 4 / 7), 0,
  7369           PDU_NL_IDENTIFIER_DEFAULT , PDU_NL_IDENTIFIER_DEFAULT );
  7370       return addr;
  7372     addr = this.readSwappedNibbleBcdString(len / 2);
  7373     if (addr.length <= 0) {
  7374       if (DEBUG) this.context.debug("PDU error: no number provided");
  7375       return null;
  7377     if ((toa & 0xF0) == (PDU_TOA_INTERNATIONAL)) {
  7378       addr = '+' + addr;
  7381     return addr;
  7382   },
  7384   /**
  7385    * Read TP-Protocol-Indicator(TP-PID).
  7387    * @param msg
  7388    *        message object for output.
  7390    * @see 3GPP TS 23.040 9.2.3.9
  7391    */
  7392   readProtocolIndicator: function(msg) {
  7393     // `The MS shall interpret reserved, obsolete, or unsupported values as the
  7394     // value 00000000 but shall store them exactly as received.`
  7395     msg.pid = this.readHexOctet();
  7397     msg.epid = msg.pid;
  7398     switch (msg.epid & 0xC0) {
  7399       case 0x40:
  7400         // Bit 7..0 = 01xxxxxx
  7401         switch (msg.epid) {
  7402           case PDU_PID_SHORT_MESSAGE_TYPE_0:
  7403           case PDU_PID_ANSI_136_R_DATA:
  7404           case PDU_PID_USIM_DATA_DOWNLOAD:
  7405             return;
  7407         break;
  7410     msg.epid = PDU_PID_DEFAULT;
  7411   },
  7413   /**
  7414    * Read TP-Data-Coding-Scheme(TP-DCS)
  7416    * @param msg
  7417    *        message object for output.
  7419    * @see 3GPP TS 23.040 9.2.3.10, 3GPP TS 23.038 4.
  7420    */
  7421   readDataCodingScheme: function(msg) {
  7422     let dcs = this.readHexOctet();
  7423     if (DEBUG) this.context.debug("PDU: read SMS dcs: " + dcs);
  7425     // No message class by default.
  7426     let messageClass = PDU_DCS_MSG_CLASS_NORMAL;
  7427     // 7 bit is the default fallback encoding.
  7428     let encoding = PDU_DCS_MSG_CODING_7BITS_ALPHABET;
  7429     switch (dcs & PDU_DCS_CODING_GROUP_BITS) {
  7430       case 0x40: // bits 7..4 = 01xx
  7431       case 0x50:
  7432       case 0x60:
  7433       case 0x70:
  7434         // Bit 5..0 are coded exactly the same as Group 00xx
  7435       case 0x00: // bits 7..4 = 00xx
  7436       case 0x10:
  7437       case 0x20:
  7438       case 0x30:
  7439         if (dcs & 0x10) {
  7440           messageClass = dcs & PDU_DCS_MSG_CLASS_BITS;
  7442         switch (dcs & 0x0C) {
  7443           case 0x4:
  7444             encoding = PDU_DCS_MSG_CODING_8BITS_ALPHABET;
  7445             break;
  7446           case 0x8:
  7447             encoding = PDU_DCS_MSG_CODING_16BITS_ALPHABET;
  7448             break;
  7450         break;
  7452       case 0xE0: // bits 7..4 = 1110
  7453         encoding = PDU_DCS_MSG_CODING_16BITS_ALPHABET;
  7454         // Bit 3..0 are coded exactly the same as Message Waiting Indication
  7455         // Group 1101.
  7456         // Fall through.
  7457       case 0xC0: // bits 7..4 = 1100
  7458       case 0xD0: // bits 7..4 = 1101
  7459         // Indiciates voicemail indicator set or clear
  7460         let active = (dcs & PDU_DCS_MWI_ACTIVE_BITS) == PDU_DCS_MWI_ACTIVE_VALUE;
  7462         // If TP-UDH is present, these values will be overwritten
  7463         switch (dcs & PDU_DCS_MWI_TYPE_BITS) {
  7464           case PDU_DCS_MWI_TYPE_VOICEMAIL:
  7465             let mwi = msg.mwi;
  7466             if (!mwi) {
  7467               mwi = msg.mwi = {};
  7470             mwi.active = active;
  7471             mwi.discard = (dcs & PDU_DCS_CODING_GROUP_BITS) == 0xC0;
  7472             mwi.msgCount = active ? GECKO_VOICEMAIL_MESSAGE_COUNT_UNKNOWN : 0;
  7474             if (DEBUG) {
  7475               this.context.debug("MWI in DCS received for voicemail: " +
  7476                                  JSON.stringify(mwi));
  7478             break;
  7479           case PDU_DCS_MWI_TYPE_FAX:
  7480             if (DEBUG) this.context.debug("MWI in DCS received for fax");
  7481             break;
  7482           case PDU_DCS_MWI_TYPE_EMAIL:
  7483             if (DEBUG) this.context.debug("MWI in DCS received for email");
  7484             break;
  7485           default:
  7486             if (DEBUG) this.context.debug("MWI in DCS received for \"other\"");
  7487             break;
  7489         break;
  7491       case 0xF0: // bits 7..4 = 1111
  7492         if (dcs & 0x04) {
  7493           encoding = PDU_DCS_MSG_CODING_8BITS_ALPHABET;
  7495         messageClass = dcs & PDU_DCS_MSG_CLASS_BITS;
  7496         break;
  7498       default:
  7499         // Falling back to default encoding.
  7500         break;
  7503     msg.dcs = dcs;
  7504     msg.encoding = encoding;
  7505     msg.messageClass = GECKO_SMS_MESSAGE_CLASSES[messageClass];
  7507     if (DEBUG) this.context.debug("PDU: message encoding is " + encoding + " bit.");
  7508   },
  7510   /**
  7511    * Read GSM TP-Service-Centre-Time-Stamp(TP-SCTS).
  7513    * @see 3GPP TS 23.040 9.2.3.11
  7514    */
  7515   readTimestamp: function() {
  7516     let year   = this.readSwappedNibbleBcdNum(1) + PDU_TIMESTAMP_YEAR_OFFSET;
  7517     let month  = this.readSwappedNibbleBcdNum(1) - 1;
  7518     let day    = this.readSwappedNibbleBcdNum(1);
  7519     let hour   = this.readSwappedNibbleBcdNum(1);
  7520     let minute = this.readSwappedNibbleBcdNum(1);
  7521     let second = this.readSwappedNibbleBcdNum(1);
  7522     let timestamp = Date.UTC(year, month, day, hour, minute, second);
  7524     // If the most significant bit of the least significant nibble is 1,
  7525     // the timezone offset is negative (fourth bit from the right => 0x08):
  7526     //   localtime = UTC + tzOffset
  7527     // therefore
  7528     //   UTC = localtime - tzOffset
  7529     let tzOctet = this.readHexOctet();
  7530     let tzOffset = this.octetToBCD(tzOctet & ~0x08) * 15 * 60 * 1000;
  7531     tzOffset = (tzOctet & 0x08) ? -tzOffset : tzOffset;
  7532     timestamp -= tzOffset;
  7534     return timestamp;
  7535   },
  7537   /**
  7538    * Write GSM TP-Service-Centre-Time-Stamp(TP-SCTS).
  7540    * @see 3GPP TS 23.040 9.2.3.11
  7541    */
  7542   writeTimestamp: function(date) {
  7543     this.writeSwappedNibbleBCDNum(date.getFullYear() - PDU_TIMESTAMP_YEAR_OFFSET);
  7545     // The value returned by getMonth() is an integer between 0 and 11.
  7546     // 0 is corresponds to January, 1 to February, and so on.
  7547     this.writeSwappedNibbleBCDNum(date.getMonth() + 1);
  7548     this.writeSwappedNibbleBCDNum(date.getDate());
  7549     this.writeSwappedNibbleBCDNum(date.getHours());
  7550     this.writeSwappedNibbleBCDNum(date.getMinutes());
  7551     this.writeSwappedNibbleBCDNum(date.getSeconds());
  7553     // the value returned by getTimezoneOffset() is the difference,
  7554     // in minutes, between UTC and local time.
  7555     // For example, if your time zone is UTC+10 (Australian Eastern Standard Time),
  7556     // -600 will be returned.
  7557     // In TS 23.040 9.2.3.11, the Time Zone field of TP-SCTS indicates
  7558     // the different between the local time and GMT.
  7559     // And expressed in quarters of an hours. (so need to divid by 15)
  7560     let zone = date.getTimezoneOffset() / 15;
  7561     let octet = this.BCDToOctet(zone);
  7563     // the bit3 of the Time Zone field represents the algebraic sign.
  7564     // (0: positive, 1: negative).
  7565     // For example, if the time zone is -0800 GMT,
  7566     // 480 will be returned by getTimezoneOffset().
  7567     // In this case, need to mark sign bit as 1. => 0x08
  7568     if (zone > 0) {
  7569       octet = octet | 0x08;
  7571     this.writeHexOctet(octet);
  7572   },
  7574   /**
  7575    * User data can be 7 bit (default alphabet) data, 8 bit data, or 16 bit
  7576    * (UCS2) data.
  7578    * @param msg
  7579    *        message object for output.
  7580    * @param length
  7581    *        length of user data to read in octets.
  7582    */
  7583   readUserData: function(msg, length) {
  7584     if (DEBUG) {
  7585       this.context.debug("Reading " + length + " bytes of user data.");
  7588     let paddingBits = 0;
  7589     if (msg.udhi) {
  7590       this.readUserDataHeader(msg);
  7592       if (msg.encoding == PDU_DCS_MSG_CODING_7BITS_ALPHABET) {
  7593         let headerBits = (msg.header.length + 1) * 8;
  7594         let headerSeptets = Math.ceil(headerBits / 7);
  7596         length -= headerSeptets;
  7597         paddingBits = headerSeptets * 7 - headerBits;
  7598       } else {
  7599         length -= (msg.header.length + 1);
  7603     if (DEBUG) {
  7604       this.context.debug("After header, " + length + " septets left of user data");
  7607     msg.body = null;
  7608     msg.data = null;
  7609     switch (msg.encoding) {
  7610       case PDU_DCS_MSG_CODING_7BITS_ALPHABET:
  7611         // 7 bit encoding allows 140 octets, which means 160 characters
  7612         // ((140x8) / 7 = 160 chars)
  7613         if (length > PDU_MAX_USER_DATA_7BIT) {
  7614           if (DEBUG) {
  7615             this.context.debug("PDU error: user data is too long: " + length);
  7617           break;
  7620         let langIndex = msg.udhi ? msg.header.langIndex : PDU_NL_IDENTIFIER_DEFAULT;
  7621         let langShiftIndex = msg.udhi ? msg.header.langShiftIndex : PDU_NL_IDENTIFIER_DEFAULT;
  7622         msg.body = this.readSeptetsToString(length, paddingBits, langIndex,
  7623                                             langShiftIndex);
  7624         break;
  7625       case PDU_DCS_MSG_CODING_8BITS_ALPHABET:
  7626         msg.data = this.readHexOctetArray(length);
  7627         break;
  7628       case PDU_DCS_MSG_CODING_16BITS_ALPHABET:
  7629         msg.body = this.readUCS2String(length);
  7630         break;
  7632   },
  7634   /**
  7635    * Read extra parameters if TP-PI is set.
  7637    * @param msg
  7638    *        message object for output.
  7639    */
  7640   readExtraParams: function(msg) {
  7641     // Because each PDU octet is converted to two UCS2 char2, we should always
  7642     // get even messageStringLength in this#_processReceivedSms(). So, we'll
  7643     // always need two delimitors at the end.
  7644     if (this.context.Buf.getReadAvailable() <= 4) {
  7645       return;
  7648     // TP-Parameter-Indicator
  7649     let pi;
  7650     do {
  7651       // `The most significant bit in octet 1 and any other TP-PI octets which
  7652       // may be added later is reserved as an extension bit which when set to a
  7653       // 1 shall indicate that another TP-PI octet follows immediately
  7654       // afterwards.` ~ 3GPP TS 23.040 9.2.3.27
  7655       pi = this.readHexOctet();
  7656     } while (pi & PDU_PI_EXTENSION);
  7658     // `If the TP-UDL bit is set to "1" but the TP-DCS bit is set to "0" then
  7659     // the receiving entity shall for TP-DCS assume a value of 0x00, i.e. the
  7660     // 7bit default alphabet.` ~ 3GPP 23.040 9.2.3.27
  7661     msg.dcs = 0;
  7662     msg.encoding = PDU_DCS_MSG_CODING_7BITS_ALPHABET;
  7664     // TP-Protocol-Identifier
  7665     if (pi & PDU_PI_PROTOCOL_IDENTIFIER) {
  7666       this.readProtocolIndicator(msg);
  7668     // TP-Data-Coding-Scheme
  7669     if (pi & PDU_PI_DATA_CODING_SCHEME) {
  7670       this.readDataCodingScheme(msg);
  7672     // TP-User-Data-Length
  7673     if (pi & PDU_PI_USER_DATA_LENGTH) {
  7674       let userDataLength = this.readHexOctet();
  7675       this.readUserData(msg, userDataLength);
  7677   },
  7679   /**
  7680    * Read and decode a PDU-encoded message from the stream.
  7682    * TODO: add some basic sanity checks like:
  7683    * - do we have the minimum number of chars available
  7684    */
  7685   readMessage: function() {
  7686     // An empty message object. This gets filled below and then returned.
  7687     let msg = {
  7688       // D:DELIVER, DR:DELIVER-REPORT, S:SUBMIT, SR:SUBMIT-REPORT,
  7689       // ST:STATUS-REPORT, C:COMMAND
  7690       // M:Mandatory, O:Optional, X:Unavailable
  7691       //                          D  DR S  SR ST C
  7692       SMSC:              null, // M  M  M  M  M  M
  7693       mti:               null, // M  M  M  M  M  M
  7694       udhi:              null, // M  M  O  M  M  M
  7695       sender:            null, // M  X  X  X  X  X
  7696       recipient:         null, // X  X  M  X  M  M
  7697       pid:               null, // M  O  M  O  O  M
  7698       epid:              null, // M  O  M  O  O  M
  7699       dcs:               null, // M  O  M  O  O  X
  7700       mwi:               null, // O  O  O  O  O  O
  7701       replace:          false, // O  O  O  O  O  O
  7702       header:            null, // M  M  O  M  M  M
  7703       body:              null, // M  O  M  O  O  O
  7704       data:              null, // M  O  M  O  O  O
  7705       sentTimestamp:     null, // M  X  X  X  X  X
  7706       status:            null, // X  X  X  X  M  X
  7707       scts:              null, // X  X  X  M  M  X
  7708       dt:                null, // X  X  X  X  M  X
  7709     };
  7711     // SMSC info
  7712     let smscLength = this.readHexOctet();
  7713     if (smscLength > 0) {
  7714       let smscTypeOfAddress = this.readHexOctet();
  7715       // Subtract the type-of-address octet we just read from the length.
  7716       msg.SMSC = this.readSwappedNibbleBcdString(smscLength - 1);
  7717       if ((smscTypeOfAddress >> 4) == (PDU_TOA_INTERNATIONAL >> 4)) {
  7718         msg.SMSC = '+' + msg.SMSC;
  7722     // First octet of this SMS-DELIVER or SMS-SUBMIT message
  7723     let firstOctet = this.readHexOctet();
  7724     // Message Type Indicator
  7725     msg.mti = firstOctet & 0x03;
  7726     // User data header indicator
  7727     msg.udhi = firstOctet & PDU_UDHI;
  7729     switch (msg.mti) {
  7730       case PDU_MTI_SMS_RESERVED:
  7731         // `If an MS receives a TPDU with a "Reserved" value in the TP-MTI it
  7732         // shall process the message as if it were an "SMS-DELIVER" but store
  7733         // the message exactly as received.` ~ 3GPP TS 23.040 9.2.3.1
  7734       case PDU_MTI_SMS_DELIVER:
  7735         return this.readDeliverMessage(msg);
  7736       case PDU_MTI_SMS_STATUS_REPORT:
  7737         return this.readStatusReportMessage(msg);
  7738       default:
  7739         return null;
  7741   },
  7743   /**
  7744    * Helper for processing received SMS parcel data.
  7746    * @param length
  7747    *        Length of SMS string in the incoming parcel.
  7749    * @return Message parsed or null for invalid message.
  7750    */
  7751   processReceivedSms: function(length) {
  7752     if (!length) {
  7753       if (DEBUG) this.context.debug("Received empty SMS!");
  7754       return [null, PDU_FCS_UNSPECIFIED];
  7757     let Buf = this.context.Buf;
  7759     // An SMS is a string, but we won't read it as such, so let's read the
  7760     // string length and then defer to PDU parsing helper.
  7761     let messageStringLength = Buf.readInt32();
  7762     if (DEBUG) this.context.debug("Got new SMS, length " + messageStringLength);
  7763     let message = this.readMessage();
  7764     if (DEBUG) this.context.debug("Got new SMS: " + JSON.stringify(message));
  7766     // Read string delimiters. See Buf.readString().
  7767     Buf.readStringDelimiter(length);
  7769     // Determine result
  7770     if (!message) {
  7771       return [null, PDU_FCS_UNSPECIFIED];
  7774     if (message.epid == PDU_PID_SHORT_MESSAGE_TYPE_0) {
  7775       // `A short message type 0 indicates that the ME must acknowledge receipt
  7776       // of the short message but shall discard its contents.` ~ 3GPP TS 23.040
  7777       // 9.2.3.9
  7778       return [null, PDU_FCS_OK];
  7781     if (message.messageClass == GECKO_SMS_MESSAGE_CLASSES[PDU_DCS_MSG_CLASS_2]) {
  7782       let RIL = this.context.RIL;
  7783       switch (message.epid) {
  7784         case PDU_PID_ANSI_136_R_DATA:
  7785         case PDU_PID_USIM_DATA_DOWNLOAD:
  7786           let ICCUtilsHelper = this.context.ICCUtilsHelper;
  7787           if (ICCUtilsHelper.isICCServiceAvailable("DATA_DOWNLOAD_SMS_PP")) {
  7788             // `If the service "data download via SMS Point-to-Point" is
  7789             // allocated and activated in the (U)SIM Service Table, ... then the
  7790             // ME shall pass the message transparently to the UICC using the
  7791             // ENVELOPE (SMS-PP DOWNLOAD).` ~ 3GPP TS 31.111 7.1.1.1
  7792             RIL.dataDownloadViaSMSPP(message);
  7794             // `the ME shall not display the message, or alert the user of a
  7795             // short message waiting.` ~ 3GPP TS 31.111 7.1.1.1
  7796             return [null, PDU_FCS_RESERVED];
  7799           // If the service "data download via SMS-PP" is not available in the
  7800           // (U)SIM Service Table, ..., then the ME shall store the message in
  7801           // EFsms in accordance with TS 31.102` ~ 3GPP TS 31.111 7.1.1.1
  7803           // Fall through.
  7804         default:
  7805           RIL.writeSmsToSIM(message);
  7806           break;
  7810     // TODO: Bug 739143: B2G SMS: Support SMS Storage Full event
  7811     if ((message.messageClass != GECKO_SMS_MESSAGE_CLASSES[PDU_DCS_MSG_CLASS_0]) && !true) {
  7812       // `When a mobile terminated message is class 0..., the MS shall display
  7813       // the message immediately and send a ACK to the SC ..., irrespective of
  7814       // whether there is memory available in the (U)SIM or ME.` ~ 3GPP 23.038
  7815       // clause 4.
  7817       if (message.messageClass == GECKO_SMS_MESSAGE_CLASSES[PDU_DCS_MSG_CLASS_2]) {
  7818         // `If all the short message storage at the MS is already in use, the
  7819         // MS shall return "memory capacity exceeded".` ~ 3GPP 23.038 clause 4.
  7820         return [null, PDU_FCS_MEMORY_CAPACITY_EXCEEDED];
  7823       return [null, PDU_FCS_UNSPECIFIED];
  7826     return [message, PDU_FCS_OK];
  7827   },
  7829   /**
  7830    * Read and decode a SMS-DELIVER PDU.
  7832    * @param msg
  7833    *        message object for output.
  7834    */
  7835   readDeliverMessage: function(msg) {
  7836     // - Sender Address info -
  7837     let senderAddressLength = this.readHexOctet();
  7838     msg.sender = this.readAddress(senderAddressLength);
  7839     // - TP-Protocolo-Identifier -
  7840     this.readProtocolIndicator(msg);
  7841     // - TP-Data-Coding-Scheme -
  7842     this.readDataCodingScheme(msg);
  7843     // - TP-Service-Center-Time-Stamp -
  7844     msg.sentTimestamp = this.readTimestamp();
  7845     // - TP-User-Data-Length -
  7846     let userDataLength = this.readHexOctet();
  7848     // - TP-User-Data -
  7849     if (userDataLength > 0) {
  7850       this.readUserData(msg, userDataLength);
  7853     return msg;
  7854   },
  7856   /**
  7857    * Read and decode a SMS-STATUS-REPORT PDU.
  7859    * @param msg
  7860    *        message object for output.
  7861    */
  7862   readStatusReportMessage: function(msg) {
  7863     // TP-Message-Reference
  7864     msg.messageRef = this.readHexOctet();
  7865     // TP-Recipient-Address
  7866     let recipientAddressLength = this.readHexOctet();
  7867     msg.recipient = this.readAddress(recipientAddressLength);
  7868     // TP-Service-Centre-Time-Stamp
  7869     msg.scts = this.readTimestamp();
  7870     // TP-Discharge-Time
  7871     msg.dt = this.readTimestamp();
  7872     // TP-Status
  7873     msg.status = this.readHexOctet();
  7875     this.readExtraParams(msg);
  7877     return msg;
  7878   },
  7880   /**
  7881    * Serialize a SMS-SUBMIT PDU message and write it to the output stream.
  7883    * This method expects that a data coding scheme has been chosen already
  7884    * and that the length of the user data payload in that encoding is known,
  7885    * too. Both go hand in hand together anyway.
  7887    * @param address
  7888    *        String containing the address (number) of the SMS receiver
  7889    * @param userData
  7890    *        String containing the message to be sent as user data
  7891    * @param dcs
  7892    *        Data coding scheme. One of the PDU_DCS_MSG_CODING_*BITS_ALPHABET
  7893    *        constants.
  7894    * @param userDataHeaderLength
  7895    *        Length of embedded user data header, in bytes. The whole header
  7896    *        size will be userDataHeaderLength + 1; 0 for no header.
  7897    * @param encodedBodyLength
  7898    *        Length of the user data when encoded with the given DCS. For UCS2,
  7899    *        in bytes; for 7-bit, in septets.
  7900    * @param langIndex
  7901    *        Table index used for normal 7-bit encoded character lookup.
  7902    * @param langShiftIndex
  7903    *        Table index used for escaped 7-bit encoded character lookup.
  7904    * @param requestStatusReport
  7905    *        Request status report.
  7906    */
  7907   writeMessage: function(options) {
  7908     if (DEBUG) {
  7909       this.context.debug("writeMessage: " + JSON.stringify(options));
  7911     let Buf = this.context.Buf;
  7912     let address = options.number;
  7913     let body = options.body;
  7914     let dcs = options.dcs;
  7915     let userDataHeaderLength = options.userDataHeaderLength;
  7916     let encodedBodyLength = options.encodedBodyLength;
  7917     let langIndex = options.langIndex;
  7918     let langShiftIndex = options.langShiftIndex;
  7920     // SMS-SUBMIT Format:
  7921     //
  7922     // PDU Type - 1 octet
  7923     // Message Reference - 1 octet
  7924     // DA - Destination Address - 2 to 12 octets
  7925     // PID - Protocol Identifier - 1 octet
  7926     // DCS - Data Coding Scheme - 1 octet
  7927     // VP - Validity Period - 0, 1 or 7 octets
  7928     // UDL - User Data Length - 1 octet
  7929     // UD - User Data - 140 octets
  7931     let addressFormat = PDU_TOA_ISDN; // 81
  7932     if (address[0] == '+') {
  7933       addressFormat = PDU_TOA_INTERNATIONAL | PDU_TOA_ISDN; // 91
  7934       address = address.substring(1);
  7936     //TODO validity is unsupported for now
  7937     let validity = 0;
  7939     let headerOctets = (userDataHeaderLength ? userDataHeaderLength + 1 : 0);
  7940     let paddingBits;
  7941     let userDataLengthInSeptets;
  7942     let userDataLengthInOctets;
  7943     if (dcs == PDU_DCS_MSG_CODING_7BITS_ALPHABET) {
  7944       let headerSeptets = Math.ceil(headerOctets * 8 / 7);
  7945       userDataLengthInSeptets = headerSeptets + encodedBodyLength;
  7946       userDataLengthInOctets = Math.ceil(userDataLengthInSeptets * 7 / 8);
  7947       paddingBits = headerSeptets * 7 - headerOctets * 8;
  7948     } else {
  7949       userDataLengthInOctets = headerOctets + encodedBodyLength;
  7950       paddingBits = 0;
  7953     let pduOctetLength = 4 + // PDU Type, Message Ref, address length + format
  7954                          Math.ceil(address.length / 2) +
  7955                          3 + // PID, DCS, UDL
  7956                          userDataLengthInOctets;
  7957     if (validity) {
  7958       //TODO: add more to pduOctetLength
  7961     // Start the string. Since octets are represented in hex, we will need
  7962     // twice as many characters as octets.
  7963     Buf.writeInt32(pduOctetLength * 2);
  7965     // - PDU-TYPE-
  7967     // +--------+----------+---------+---------+--------+---------+
  7968     // | RP (1) | UDHI (1) | SRR (1) | VPF (2) | RD (1) | MTI (2) |
  7969     // +--------+----------+---------+---------+--------+---------+
  7970     // RP:    0   Reply path parameter is not set
  7971     //        1   Reply path parameter is set
  7972     // UDHI:  0   The UD Field contains only the short message
  7973     //        1   The beginning of the UD field contains a header in addition
  7974     //            of the short message
  7975     // SRR:   0   A status report is not requested
  7976     //        1   A status report is requested
  7977     // VPF:   bit4  bit3
  7978     //        0     0     VP field is not present
  7979     //        0     1     Reserved
  7980     //        1     0     VP field present an integer represented (relative)
  7981     //        1     1     VP field present a semi-octet represented (absolute)
  7982     // RD:        Instruct the SMSC to accept(0) or reject(1) an SMS-SUBMIT
  7983     //            for a short message still held in the SMSC which has the same
  7984     //            MR and DA as a previously submitted short message from the
  7985     //            same OA
  7986     // MTI:   bit1  bit0    Message Type
  7987     //        0     0       SMS-DELIVER (SMSC ==> MS)
  7988     //        0     1       SMS-SUBMIT (MS ==> SMSC)
  7990     // PDU type. MTI is set to SMS-SUBMIT
  7991     let firstOctet = PDU_MTI_SMS_SUBMIT;
  7993     // Status-Report-Request
  7994     if (options.requestStatusReport) {
  7995       firstOctet |= PDU_SRI_SRR;
  7998     // Validity period
  7999     if (validity) {
  8000       //TODO: not supported yet, OR with one of PDU_VPF_*
  8002     // User data header indicator
  8003     if (headerOctets) {
  8004       firstOctet |= PDU_UDHI;
  8006     this.writeHexOctet(firstOctet);
  8008     // Message reference 00
  8009     this.writeHexOctet(0x00);
  8011     // - Destination Address -
  8012     this.writeHexOctet(address.length);
  8013     this.writeHexOctet(addressFormat);
  8014     this.writeSwappedNibbleBCD(address);
  8016     // - Protocol Identifier -
  8017     this.writeHexOctet(0x00);
  8019     // - Data coding scheme -
  8020     // For now it assumes bits 7..4 = 1111 except for the 16 bits use case
  8021     this.writeHexOctet(dcs);
  8023     // - Validity Period -
  8024     if (validity) {
  8025       this.writeHexOctet(validity);
  8028     // - User Data -
  8029     if (dcs == PDU_DCS_MSG_CODING_7BITS_ALPHABET) {
  8030       this.writeHexOctet(userDataLengthInSeptets);
  8031     } else {
  8032       this.writeHexOctet(userDataLengthInOctets);
  8035     if (headerOctets) {
  8036       this.writeUserDataHeader(options);
  8039     switch (dcs) {
  8040       case PDU_DCS_MSG_CODING_7BITS_ALPHABET:
  8041         this.writeStringAsSeptets(body, paddingBits, langIndex, langShiftIndex);
  8042         break;
  8043       case PDU_DCS_MSG_CODING_8BITS_ALPHABET:
  8044         // Unsupported.
  8045         break;
  8046       case PDU_DCS_MSG_CODING_16BITS_ALPHABET:
  8047         this.writeUCS2String(body);
  8048         break;
  8051     // End of the string. The string length is always even by definition, so
  8052     // we write two \0 delimiters.
  8053     Buf.writeUint16(0);
  8054     Buf.writeUint16(0);
  8055   },
  8057   /**
  8058    * Read GSM CBS message serial number.
  8060    * @param msg
  8061    *        message object for output.
  8063    * @see 3GPP TS 23.041 section 9.4.1.2.1
  8064    */
  8065   readCbSerialNumber: function(msg) {
  8066     let Buf = this.context.Buf;
  8067     msg.serial = Buf.readUint8() << 8 | Buf.readUint8();
  8068     msg.geographicalScope = (msg.serial >>> 14) & 0x03;
  8069     msg.messageCode = (msg.serial >>> 4) & 0x03FF;
  8070     msg.updateNumber = msg.serial & 0x0F;
  8071   },
  8073   /**
  8074    * Read GSM CBS message message identifier.
  8076    * @param msg
  8077    *        message object for output.
  8079    * @see 3GPP TS 23.041 section 9.4.1.2.2
  8080    */
  8081   readCbMessageIdentifier: function(msg) {
  8082     let Buf = this.context.Buf;
  8083     msg.messageId = Buf.readUint8() << 8 | Buf.readUint8();
  8085     if ((msg.format != CB_FORMAT_ETWS)
  8086         && (msg.messageId >= CB_GSM_MESSAGEID_ETWS_BEGIN)
  8087         && (msg.messageId <= CB_GSM_MESSAGEID_ETWS_END)) {
  8088       // `In the case of transmitting CBS message for ETWS, a part of
  8089       // Message Code can be used to command mobile terminals to activate
  8090       // emergency user alert and message popup in order to alert the users.`
  8091       msg.etws = {
  8092         emergencyUserAlert: msg.messageCode & 0x0200 ? true : false,
  8093         popup:              msg.messageCode & 0x0100 ? true : false
  8094       };
  8096       let warningType = msg.messageId - CB_GSM_MESSAGEID_ETWS_BEGIN;
  8097       if (warningType < CB_ETWS_WARNING_TYPE_NAMES.length) {
  8098         msg.etws.warningType = warningType;
  8101   },
  8103   /**
  8104    * Read CBS Data Coding Scheme.
  8106    * @param msg
  8107    *        message object for output.
  8109    * @see 3GPP TS 23.038 section 5.
  8110    */
  8111   readCbDataCodingScheme: function(msg) {
  8112     let dcs = this.context.Buf.readUint8();
  8113     if (DEBUG) this.context.debug("PDU: read CBS dcs: " + dcs);
  8115     let language = null, hasLanguageIndicator = false;
  8116     // `Any reserved codings shall be assumed to be the GSM 7bit default
  8117     // alphabet.`
  8118     let encoding = PDU_DCS_MSG_CODING_7BITS_ALPHABET;
  8119     let messageClass = PDU_DCS_MSG_CLASS_NORMAL;
  8121     switch (dcs & PDU_DCS_CODING_GROUP_BITS) {
  8122       case 0x00: // 0000
  8123         language = CB_DCS_LANG_GROUP_1[dcs & 0x0F];
  8124         break;
  8126       case 0x10: // 0001
  8127         switch (dcs & 0x0F) {
  8128           case 0x00:
  8129             hasLanguageIndicator = true;
  8130             break;
  8131           case 0x01:
  8132             encoding = PDU_DCS_MSG_CODING_16BITS_ALPHABET;
  8133             hasLanguageIndicator = true;
  8134             break;
  8136         break;
  8138       case 0x20: // 0010
  8139         language = CB_DCS_LANG_GROUP_2[dcs & 0x0F];
  8140         break;
  8142       case 0x40: // 01xx
  8143       case 0x50:
  8144       //case 0x60: Text Compression, not supported
  8145       //case 0x70: Text Compression, not supported
  8146       case 0x90: // 1001
  8147         encoding = (dcs & 0x0C);
  8148         if (encoding == 0x0C) {
  8149           encoding = PDU_DCS_MSG_CODING_7BITS_ALPHABET;
  8151         messageClass = (dcs & PDU_DCS_MSG_CLASS_BITS);
  8152         break;
  8154       case 0xF0:
  8155         encoding = (dcs & 0x04) ? PDU_DCS_MSG_CODING_8BITS_ALPHABET
  8156                                 : PDU_DCS_MSG_CODING_7BITS_ALPHABET;
  8157         switch(dcs & PDU_DCS_MSG_CLASS_BITS) {
  8158           case 0x01: messageClass = PDU_DCS_MSG_CLASS_USER_1; break;
  8159           case 0x02: messageClass = PDU_DCS_MSG_CLASS_USER_2; break;
  8160           case 0x03: messageClass = PDU_DCS_MSG_CLASS_3; break;
  8162         break;
  8164       case 0x30: // 0011 (Reserved)
  8165       case 0x80: // 1000 (Reserved)
  8166       case 0xA0: // 1010..1100 (Reserved)
  8167       case 0xB0:
  8168       case 0xC0:
  8169         break;
  8171       default:
  8172         throw new Error("Unsupported CBS data coding scheme: " + dcs);
  8175     msg.dcs = dcs;
  8176     msg.encoding = encoding;
  8177     msg.language = language;
  8178     msg.messageClass = GECKO_SMS_MESSAGE_CLASSES[messageClass];
  8179     msg.hasLanguageIndicator = hasLanguageIndicator;
  8180   },
  8182   /**
  8183    * Read GSM CBS message page parameter.
  8185    * @param msg
  8186    *        message object for output.
  8188    * @see 3GPP TS 23.041 section 9.4.1.2.4
  8189    */
  8190   readCbPageParameter: function(msg) {
  8191     let octet = this.context.Buf.readUint8();
  8192     msg.pageIndex = (octet >>> 4) & 0x0F;
  8193     msg.numPages = octet & 0x0F;
  8194     if (!msg.pageIndex || !msg.numPages) {
  8195       // `If a mobile receives the code 0000 in either the first field or the
  8196       // second field then it shall treat the CBS message exactly the same as a
  8197       // CBS message with page parameter 0001 0001 (i.e. a single page message).`
  8198       msg.pageIndex = msg.numPages = 1;
  8200   },
  8202   /**
  8203    * Read ETWS Primary Notification message warning type.
  8205    * @param msg
  8206    *        message object for output.
  8208    * @see 3GPP TS 23.041 section 9.3.24
  8209    */
  8210   readCbWarningType: function(msg) {
  8211     let Buf = this.context.Buf;
  8212     let word = Buf.readUint8() << 8 | Buf.readUint8();
  8213     msg.etws = {
  8214       warningType:        (word >>> 9) & 0x7F,
  8215       popup:              word & 0x80 ? true : false,
  8216       emergencyUserAlert: word & 0x100 ? true : false
  8217     };
  8218   },
  8220   /**
  8221    * Read CBS-Message-Information-Page
  8223    * @param msg
  8224    *        message object for output.
  8225    * @param length
  8226    *        length of cell broadcast data to read in octets.
  8228    * @see 3GPP TS 23.041 section 9.3.19
  8229    */
  8230   readGsmCbData: function(msg, length) {
  8231     let Buf = this.context.Buf;
  8232     let bufAdapter = {
  8233       context: this.context,
  8234       readHexOctet: function() {
  8235         return Buf.readUint8();
  8237     };
  8239     msg.body = null;
  8240     msg.data = null;
  8241     switch (msg.encoding) {
  8242       case PDU_DCS_MSG_CODING_7BITS_ALPHABET:
  8243         msg.body = this.readSeptetsToString.call(bufAdapter,
  8244                                                  (length * 8 / 7), 0,
  8245                                                  PDU_NL_IDENTIFIER_DEFAULT,
  8246                                                  PDU_NL_IDENTIFIER_DEFAULT);
  8247         if (msg.hasLanguageIndicator) {
  8248           msg.language = msg.body.substring(0, 2);
  8249           msg.body = msg.body.substring(3);
  8251         break;
  8253       case PDU_DCS_MSG_CODING_8BITS_ALPHABET:
  8254         msg.data = Buf.readUint8Array(length);
  8255         break;
  8257       case PDU_DCS_MSG_CODING_16BITS_ALPHABET:
  8258         if (msg.hasLanguageIndicator) {
  8259           msg.language = this.readSeptetsToString.call(bufAdapter, 2, 0,
  8260                                                        PDU_NL_IDENTIFIER_DEFAULT,
  8261                                                        PDU_NL_IDENTIFIER_DEFAULT);
  8262           length -= 2;
  8264         msg.body = this.readUCS2String.call(bufAdapter, length);
  8265         break;
  8267   },
  8269   /**
  8270    * Read Cell GSM/ETWS/UMTS Broadcast Message.
  8272    * @param pduLength
  8273    *        total length of the incoming PDU in octets.
  8274    */
  8275   readCbMessage: function(pduLength) {
  8276     // Validity                                                   GSM ETWS UMTS
  8277     let msg = {
  8278       // Internally used in ril_worker:
  8279       serial:               null,                              //  O   O    O
  8280       updateNumber:         null,                              //  O   O    O
  8281       format:               null,                              //  O   O    O
  8282       dcs:                  0x0F,                              //  O   X    O
  8283       encoding:             PDU_DCS_MSG_CODING_7BITS_ALPHABET, //  O   X    O
  8284       hasLanguageIndicator: false,                             //  O   X    O
  8285       data:                 null,                              //  O   X    O
  8286       body:                 null,                              //  O   X    O
  8287       pageIndex:            1,                                 //  O   X    X
  8288       numPages:             1,                                 //  O   X    X
  8290       // DOM attributes:
  8291       geographicalScope:    null,                              //  O   O    O
  8292       messageCode:          null,                              //  O   O    O
  8293       messageId:            null,                              //  O   O    O
  8294       language:             null,                              //  O   X    O
  8295       fullBody:             null,                              //  O   X    O
  8296       fullData:             null,                              //  O   X    O
  8297       messageClass:         GECKO_SMS_MESSAGE_CLASSES[PDU_DCS_MSG_CLASS_NORMAL], //  O   x    O
  8298       etws:                 null                               //  ?   O    ?
  8299       /*{
  8300         warningType:        null,                              //  X   O    X
  8301         popup:              false,                             //  X   O    X
  8302         emergencyUserAlert: false,                             //  X   O    X
  8303       }*/
  8304     };
  8306     if (pduLength <= CB_MESSAGE_SIZE_ETWS) {
  8307       msg.format = CB_FORMAT_ETWS;
  8308       return this.readEtwsCbMessage(msg);
  8311     if (pduLength <= CB_MESSAGE_SIZE_GSM) {
  8312       msg.format = CB_FORMAT_GSM;
  8313       return this.readGsmCbMessage(msg, pduLength);
  8316     return null;
  8317   },
  8319   /**
  8320    * Read GSM Cell Broadcast Message.
  8322    * @param msg
  8323    *        message object for output.
  8324    * @param pduLength
  8325    *        total length of the incomint PDU in octets.
  8327    * @see 3GPP TS 23.041 clause 9.4.1.2
  8328    */
  8329   readGsmCbMessage: function(msg, pduLength) {
  8330     this.readCbSerialNumber(msg);
  8331     this.readCbMessageIdentifier(msg);
  8332     this.readCbDataCodingScheme(msg);
  8333     this.readCbPageParameter(msg);
  8335     // GSM CB message header takes 6 octets.
  8336     this.readGsmCbData(msg, pduLength - 6);
  8338     return msg;
  8339   },
  8341   /**
  8342    * Read ETWS Primary Notification Message.
  8344    * @param msg
  8345    *        message object for output.
  8347    * @see 3GPP TS 23.041 clause 9.4.1.3
  8348    */
  8349   readEtwsCbMessage: function(msg) {
  8350     this.readCbSerialNumber(msg);
  8351     this.readCbMessageIdentifier(msg);
  8352     this.readCbWarningType(msg);
  8354     // Octet 7..56 is Warning Security Information. However, according to
  8355     // section 9.4.1.3.6, `The UE shall ignore this parameter.` So we just skip
  8356     // processing it here.
  8358     return msg;
  8359   },
  8361   /**
  8362    * Read network name.
  8364    * @param len Length of the information element.
  8365    * @return
  8366    *   {
  8367    *     networkName: network name.
  8368    *     shouldIncludeCi: Should Country's initials included in text string.
  8369    *   }
  8370    * @see TS 24.008 clause 10.5.3.5a.
  8371    */
  8372   readNetworkName: function(len) {
  8373     // According to TS 24.008 Sec. 10.5.3.5a, the first octet is:
  8374     // bit 8: must be 1.
  8375     // bit 5-7: Text encoding.
  8376     //          000 - GSM default alphabet.
  8377     //          001 - UCS2 (16 bit).
  8378     //          else - reserved.
  8379     // bit 4: MS should add the letters for Country's Initials and a space
  8380     //        to the text string if this bit is true.
  8381     // bit 1-3: number of spare bits in last octet.
  8383     let codingInfo = this.readHexOctet();
  8384     if (!(codingInfo & 0x80)) {
  8385       return null;
  8388     let textEncoding = (codingInfo & 0x70) >> 4;
  8389     let shouldIncludeCountryInitials = !!(codingInfo & 0x08);
  8390     let spareBits = codingInfo & 0x07;
  8391     let resultString;
  8393     switch (textEncoding) {
  8394     case 0:
  8395       // GSM Default alphabet.
  8396       resultString = this.readSeptetsToString(
  8397         ((len - 1) * 8 - spareBits) / 7, 0,
  8398         PDU_NL_IDENTIFIER_DEFAULT,
  8399         PDU_NL_IDENTIFIER_DEFAULT);
  8400       break;
  8401     case 1:
  8402       // UCS2 encoded.
  8403       resultString = this.readUCS2String(len - 1);
  8404       break;
  8405     default:
  8406       // Not an available text coding.
  8407       return null;
  8410     // TODO - Bug 820286: According to shouldIncludeCountryInitials, add
  8411     // country initials to the resulting string.
  8412     return resultString;
  8414 };
  8416 /**
  8417  * Provide buffer with bitwise read/write function so make encoding/decoding easier.
  8418  */
  8419 function BitBufferHelperObject(/* unused */aContext) {
  8420   this.readBuffer = [];
  8421   this.writeBuffer = [];
  8423 BitBufferHelperObject.prototype = {
  8424   readCache: 0,
  8425   readCacheSize: 0,
  8426   readBuffer: null,
  8427   readIndex: 0,
  8428   writeCache: 0,
  8429   writeCacheSize: 0,
  8430   writeBuffer: null,
  8432   // Max length is 32 because we use integer as read/write cache.
  8433   // All read/write functions are implemented based on bitwise operation.
  8434   readBits: function(length) {
  8435     if (length <= 0 || length > 32) {
  8436       return null;
  8439     if (length > this.readCacheSize) {
  8440       let bytesToRead = Math.ceil((length - this.readCacheSize) / 8);
  8441       for(let i = 0; i < bytesToRead; i++) {
  8442         this.readCache = (this.readCache << 8) | (this.readBuffer[this.readIndex++] & 0xFF);
  8443         this.readCacheSize += 8;
  8447     let bitOffset = (this.readCacheSize - length),
  8448         resultMask = (1 << length) - 1,
  8449         result = 0;
  8451     result = (this.readCache >> bitOffset) & resultMask;
  8452     this.readCacheSize -= length;
  8454     return result;
  8455   },
  8457   backwardReadPilot: function(length) {
  8458     if (length <= 0) {
  8459       return;
  8462     // Zero-based position.
  8463     let bitIndexToRead = this.readIndex * 8 - this.readCacheSize - length;
  8465     if (bitIndexToRead < 0) {
  8466       return;
  8469     // Update readIndex, readCache, readCacheSize accordingly.
  8470     let readBits = bitIndexToRead % 8;
  8471     this.readIndex = Math.floor(bitIndexToRead / 8) + ((readBits) ? 1 : 0);
  8472     this.readCache = (readBits) ? this.readBuffer[this.readIndex - 1] : 0;
  8473     this.readCacheSize = (readBits) ? (8 - readBits) : 0;
  8474   },
  8476   writeBits: function(value, length) {
  8477     if (length <= 0 || length > 32) {
  8478       return;
  8481     let totalLength = length + this.writeCacheSize;
  8483     // 8-byte cache not full
  8484     if (totalLength < 8) {
  8485       let valueMask = (1 << length) - 1;
  8486       this.writeCache = (this.writeCache << length) | (value & valueMask);
  8487       this.writeCacheSize += length;
  8488       return;
  8491     // Deal with unaligned part
  8492     if (this.writeCacheSize) {
  8493       let mergeLength = 8 - this.writeCacheSize,
  8494           valueMask = (1 << mergeLength) - 1;
  8496       this.writeCache = (this.writeCache << mergeLength) | ((value >> (length - mergeLength)) & valueMask);
  8497       this.writeBuffer.push(this.writeCache & 0xFF);
  8498       length -= mergeLength;
  8501     // Aligned part, just copy
  8502     while (length >= 8) {
  8503       length -= 8;
  8504       this.writeBuffer.push((value >> length) & 0xFF);
  8507     // Rest part is saved into cache
  8508     this.writeCacheSize = length;
  8509     this.writeCache = value & ((1 << length) - 1);
  8511     return;
  8512   },
  8514   // Drop what still in read cache and goto next 8-byte alignment.
  8515   // There might be a better naming.
  8516   nextOctetAlign: function() {
  8517     this.readCache = 0;
  8518     this.readCacheSize = 0;
  8519   },
  8521   // Flush current write cache to Buf with padding 0s.
  8522   // There might be a better naming.
  8523   flushWithPadding: function() {
  8524     if (this.writeCacheSize) {
  8525       this.writeBuffer.push(this.writeCache << (8 - this.writeCacheSize));
  8527     this.writeCache = 0;
  8528     this.writeCacheSize = 0;
  8529   },
  8531   startWrite: function(dataBuffer) {
  8532     this.writeBuffer = dataBuffer;
  8533     this.writeCache = 0;
  8534     this.writeCacheSize = 0;
  8535   },
  8537   startRead: function(dataBuffer) {
  8538     this.readBuffer = dataBuffer;
  8539     this.readCache = 0;
  8540     this.readCacheSize = 0;
  8541     this.readIndex = 0;
  8542   },
  8544   getWriteBufferSize: function() {
  8545     return this.writeBuffer.length;
  8546   },
  8548   overwriteWriteBuffer: function(position, data) {
  8549     let writeLength = data.length;
  8550     if (writeLength + position >= this.writeBuffer.length) {
  8551       writeLength = this.writeBuffer.length - position;
  8553     for (let i = 0; i < writeLength; i++) {
  8554       this.writeBuffer[i + position] = data[i];
  8557 };
  8559 /**
  8560  * Helper for CDMA PDU
  8562  * Currently, some function are shared with GsmPDUHelper, they should be
  8563  * moved from GsmPDUHelper to a common object shared among GsmPDUHelper and
  8564  * CdmaPDUHelper.
  8565  */
  8566 function CdmaPDUHelperObject(aContext) {
  8567   this.context = aContext;
  8569 CdmaPDUHelperObject.prototype = {
  8570   context: null,
  8572   //       1..........C
  8573   // Only "1234567890*#" is defined in C.S0005-D v2.0
  8574   dtmfChars: ".1234567890*#...",
  8576   /**
  8577    * Entry point for SMS encoding, the options object is made compatible
  8578    * with existing writeMessage() of GsmPDUHelper, but less key is used.
  8580    * Current used key in options:
  8581    * @param number
  8582    *        String containing the address (number) of the SMS receiver
  8583    * @param body
  8584    *        String containing the message to be sent, segmented part
  8585    * @param dcs
  8586    *        Data coding scheme. One of the PDU_DCS_MSG_CODING_*BITS_ALPHABET
  8587    *        constants.
  8588    * @param encodedBodyLength
  8589    *        Length of the user data when encoded with the given DCS. For UCS2,
  8590    *        in bytes; for 7-bit, in septets.
  8591    * @param requestStatusReport
  8592    *        Request status report.
  8593    * @param segmentRef
  8594    *        Reference number of concatenated SMS message
  8595    * @param segmentMaxSeq
  8596    *        Total number of concatenated SMS message
  8597    * @param segmentSeq
  8598    *        Sequence number of concatenated SMS message
  8599    */
  8600   writeMessage: function(options) {
  8601     if (DEBUG) {
  8602       this.context.debug("cdma_writeMessage: " + JSON.stringify(options));
  8605     // Get encoding
  8606     options.encoding = this.gsmDcsToCdmaEncoding(options.dcs);
  8608     // Common Header
  8609     if (options.segmentMaxSeq > 1) {
  8610       this.writeInt(PDU_CDMA_MSG_TELESERIVCIE_ID_WEMT);
  8611     } else {
  8612       this.writeInt(PDU_CDMA_MSG_TELESERIVCIE_ID_SMS);
  8615     this.writeInt(0);
  8616     this.writeInt(PDU_CDMA_MSG_CATEGORY_UNSPEC);
  8618     // Just fill out address info in byte, rild will encap them for us
  8619     let addrInfo = this.encodeAddr(options.number);
  8620     this.writeByte(addrInfo.digitMode);
  8621     this.writeByte(addrInfo.numberMode);
  8622     this.writeByte(addrInfo.numberType);
  8623     this.writeByte(addrInfo.numberPlan);
  8624     this.writeByte(addrInfo.address.length);
  8625     for (let i = 0; i < addrInfo.address.length; i++) {
  8626       this.writeByte(addrInfo.address[i]);
  8629     // Subaddress, not supported
  8630     this.writeByte(0);  // Subaddress : Type
  8631     this.writeByte(0);  // Subaddress : Odd
  8632     this.writeByte(0);  // Subaddress : length
  8634     // User Data
  8635     let encodeResult = this.encodeUserData(options);
  8636     this.writeByte(encodeResult.length);
  8637     for (let i = 0; i < encodeResult.length; i++) {
  8638       this.writeByte(encodeResult[i]);
  8641     encodeResult = null;
  8642   },
  8644   /**
  8645    * Data writters
  8646    */
  8647   writeInt: function(value) {
  8648     this.context.Buf.writeInt32(value);
  8649   },
  8651   writeByte: function(value) {
  8652     this.context.Buf.writeInt32(value & 0xFF);
  8653   },
  8655   /**
  8656    * Transform GSM DCS to CDMA encoding.
  8657    */
  8658   gsmDcsToCdmaEncoding: function(encoding) {
  8659     switch (encoding) {
  8660       case PDU_DCS_MSG_CODING_7BITS_ALPHABET:
  8661         return PDU_CDMA_MSG_CODING_7BITS_ASCII;
  8662       case PDU_DCS_MSG_CODING_8BITS_ALPHABET:
  8663         return PDU_CDMA_MSG_CODING_OCTET;
  8664       case PDU_DCS_MSG_CODING_16BITS_ALPHABET:
  8665         return PDU_CDMA_MSG_CODING_UNICODE;
  8667     throw new Error("gsmDcsToCdmaEncoding(): Invalid GSM SMS DCS value: " + encoding);
  8668   },
  8670   /**
  8671    * Encode address into CDMA address format, as a byte array.
  8673    * @see 3GGP2 C.S0015-B 2.0, 3.4.3.3 Address Parameters
  8675    * @param address
  8676    *        String of address to be encoded
  8677    */
  8678   encodeAddr: function(address) {
  8679     let result = {};
  8681     result.numberType = PDU_CDMA_MSG_ADDR_NUMBER_TYPE_UNKNOWN;
  8682     result.numberPlan = PDU_CDMA_MSG_ADDR_NUMBER_TYPE_UNKNOWN;
  8684     if (address[0] === '+') {
  8685       address = address.substring(1);
  8688     // Try encode with DTMF first
  8689     result.digitMode = PDU_CDMA_MSG_ADDR_DIGIT_MODE_DTMF;
  8690     result.numberMode = PDU_CDMA_MSG_ADDR_NUMBER_MODE_ANSI;
  8692     result.address = [];
  8693     for (let i = 0; i < address.length; i++) {
  8694       let addrDigit = this.dtmfChars.indexOf(address.charAt(i));
  8695       if (addrDigit < 0) {
  8696         result.digitMode = PDU_CDMA_MSG_ADDR_DIGIT_MODE_ASCII;
  8697         result.numberMode = PDU_CDMA_MSG_ADDR_NUMBER_MODE_ASCII;
  8698         result.address = [];
  8699         break;
  8701       result.address.push(addrDigit);
  8704     // Address can't be encoded with DTMF, then use 7-bit ASCII
  8705     if (result.digitMode !== PDU_CDMA_MSG_ADDR_DIGIT_MODE_DTMF) {
  8706       if (address.indexOf("@") !== -1) {
  8707         result.numberType = PDU_CDMA_MSG_ADDR_NUMBER_TYPE_NATIONAL;
  8710       for (let i = 0; i < address.length; i++) {
  8711         result.address.push(address.charCodeAt(i) & 0x7F);
  8715     return result;
  8716   },
  8718   /**
  8719    * Encode SMS contents in options into CDMA userData field.
  8720    * Corresponding and required subparameters will be added automatically.
  8722    * @see 3GGP2 C.S0015-B 2.0, 3.4.3.7 Bearer Data
  8723    *                           4.5 Bearer Data Parameters
  8725    * Current used key in options:
  8726    * @param body
  8727    *        String containing the message to be sent, segmented part
  8728    * @param encoding
  8729    *        Encoding method of CDMA, can be transformed from GSM DCS by function
  8730    *        cdmaPduHelp.gsmDcsToCdmaEncoding()
  8731    * @param encodedBodyLength
  8732    *        Length of the user data when encoded with the given DCS. For UCS2,
  8733    *        in bytes; for 7-bit, in septets.
  8734    * @param requestStatusReport
  8735    *        Request status report.
  8736    * @param segmentRef
  8737    *        Reference number of concatenated SMS message
  8738    * @param segmentMaxSeq
  8739    *        Total number of concatenated SMS message
  8740    * @param segmentSeq
  8741    *        Sequence number of concatenated SMS message
  8742    */
  8743   encodeUserData: function(options) {
  8744     let userDataBuffer = [];
  8745     this.context.BitBufferHelper.startWrite(userDataBuffer);
  8747     // Message Identifier
  8748     this.encodeUserDataMsgId(options);
  8750     // User Data
  8751     this.encodeUserDataMsg(options);
  8753     // Reply Option
  8754     this.encodeUserDataReplyOption(options);
  8756     return userDataBuffer;
  8757   },
  8759   /**
  8760    * User data subparameter encoder : Message Identifier
  8762    * @see 3GGP2 C.S0015-B 2.0, 4.5.1 Message Identifier
  8763    */
  8764   encodeUserDataMsgId: function(options) {
  8765     let BitBufferHelper = this.context.BitBufferHelper;
  8766     BitBufferHelper.writeBits(PDU_CDMA_MSG_USERDATA_MSG_ID, 8);
  8767     BitBufferHelper.writeBits(3, 8);
  8768     BitBufferHelper.writeBits(PDU_CDMA_MSG_TYPE_SUBMIT, 4);
  8769     BitBufferHelper.writeBits(1, 16); // TODO: How to get message ID?
  8770     if (options.segmentMaxSeq > 1) {
  8771       BitBufferHelper.writeBits(1, 1);
  8772     } else {
  8773       BitBufferHelper.writeBits(0, 1);
  8776     BitBufferHelper.flushWithPadding();
  8777   },
  8779   /**
  8780    * User data subparameter encoder : User Data
  8782    * @see 3GGP2 C.S0015-B 2.0, 4.5.2 User Data
  8783    */
  8784   encodeUserDataMsg: function(options) {
  8785     let BitBufferHelper = this.context.BitBufferHelper;
  8786     BitBufferHelper.writeBits(PDU_CDMA_MSG_USERDATA_BODY, 8);
  8787     // Reserve space for length
  8788     BitBufferHelper.writeBits(0, 8);
  8789     let lengthPosition = BitBufferHelper.getWriteBufferSize();
  8791     BitBufferHelper.writeBits(options.encoding, 5);
  8793     // Add user data header for message segement
  8794     let msgBody = options.body,
  8795         msgBodySize = (options.encoding === PDU_CDMA_MSG_CODING_7BITS_ASCII ?
  8796                        options.encodedBodyLength :
  8797                        msgBody.length);
  8798     if (options.segmentMaxSeq > 1) {
  8799       if (options.encoding === PDU_CDMA_MSG_CODING_7BITS_ASCII) {
  8800           BitBufferHelper.writeBits(msgBodySize + 7, 8); // Required length for user data header, in septet(7-bit)
  8802           BitBufferHelper.writeBits(5, 8);  // total header length 5 bytes
  8803           BitBufferHelper.writeBits(PDU_IEI_CONCATENATED_SHORT_MESSAGES_8BIT, 8);  // header id 0
  8804           BitBufferHelper.writeBits(3, 8);  // length of element for id 0 is 3
  8805           BitBufferHelper.writeBits(options.segmentRef & 0xFF, 8);      // Segement reference
  8806           BitBufferHelper.writeBits(options.segmentMaxSeq & 0xFF, 8);   // Max segment
  8807           BitBufferHelper.writeBits(options.segmentSeq & 0xFF, 8);      // Current segment
  8808           BitBufferHelper.writeBits(0, 1);  // Padding to make header data septet(7-bit) aligned
  8809         } else {
  8810           if (options.encoding === PDU_CDMA_MSG_CODING_UNICODE) {
  8811             BitBufferHelper.writeBits(msgBodySize + 3, 8); // Required length for user data header, in 16-bit
  8812           } else {
  8813             BitBufferHelper.writeBits(msgBodySize + 6, 8); // Required length for user data header, in octet(8-bit)
  8816           BitBufferHelper.writeBits(5, 8);  // total header length 5 bytes
  8817           BitBufferHelper.writeBits(PDU_IEI_CONCATENATED_SHORT_MESSAGES_8BIT, 8);  // header id 0
  8818           BitBufferHelper.writeBits(3, 8);  // length of element for id 0 is 3
  8819           BitBufferHelper.writeBits(options.segmentRef & 0xFF, 8);      // Segement reference
  8820           BitBufferHelper.writeBits(options.segmentMaxSeq & 0xFF, 8);   // Max segment
  8821           BitBufferHelper.writeBits(options.segmentSeq & 0xFF, 8);      // Current segment
  8823     } else {
  8824       BitBufferHelper.writeBits(msgBodySize, 8);
  8827     // Encode message based on encoding method
  8828     const langTable = PDU_NL_LOCKING_SHIFT_TABLES[PDU_NL_IDENTIFIER_DEFAULT];
  8829     const langShiftTable = PDU_NL_SINGLE_SHIFT_TABLES[PDU_NL_IDENTIFIER_DEFAULT];
  8830     for (let i = 0; i < msgBody.length; i++) {
  8831       switch (options.encoding) {
  8832         case PDU_CDMA_MSG_CODING_OCTET: {
  8833           let msgDigit = msgBody.charCodeAt(i);
  8834           BitBufferHelper.writeBits(msgDigit, 8);
  8835           break;
  8837         case PDU_CDMA_MSG_CODING_7BITS_ASCII: {
  8838           let msgDigit = msgBody.charCodeAt(i),
  8839               msgDigitChar = msgBody.charAt(i);
  8841           if (msgDigit >= 32) {
  8842             BitBufferHelper.writeBits(msgDigit, 7);
  8843           } else {
  8844             msgDigit = langTable.indexOf(msgDigitChar);
  8846             if (msgDigit === PDU_NL_EXTENDED_ESCAPE) {
  8847               break;
  8849             if (msgDigit >= 0) {
  8850               BitBufferHelper.writeBits(msgDigit, 7);
  8851             } else {
  8852               msgDigit = langShiftTable.indexOf(msgDigitChar);
  8853               if (msgDigit == -1) {
  8854                 throw new Error("'" + msgDigitChar + "' is not in 7 bit alphabet "
  8855                                 + langIndex + ":" + langShiftIndex + "!");
  8858               if (msgDigit === PDU_NL_RESERVED_CONTROL) {
  8859                 break;
  8862               BitBufferHelper.writeBits(PDU_NL_EXTENDED_ESCAPE, 7);
  8863               BitBufferHelper.writeBits(msgDigit, 7);
  8866           break;
  8868         case PDU_CDMA_MSG_CODING_UNICODE: {
  8869           let msgDigit = msgBody.charCodeAt(i);
  8870           BitBufferHelper.writeBits(msgDigit, 16);
  8871           break;
  8875     BitBufferHelper.flushWithPadding();
  8877     // Fill length
  8878     let currentPosition = BitBufferHelper.getWriteBufferSize();
  8879     BitBufferHelper.overwriteWriteBuffer(lengthPosition - 1, [currentPosition - lengthPosition]);
  8880   },
  8882   /**
  8883    * User data subparameter encoder : Reply Option
  8885    * @see 3GGP2 C.S0015-B 2.0, 4.5.11 Reply Option
  8886    */
  8887   encodeUserDataReplyOption: function(options) {
  8888     if (options.requestStatusReport) {
  8889       let BitBufferHelper = this.context.BitBufferHelper;
  8890       BitBufferHelper.writeBits(PDU_CDMA_MSG_USERDATA_REPLY_OPTION, 8);
  8891       BitBufferHelper.writeBits(1, 8);
  8892       BitBufferHelper.writeBits(0, 1); // USER_ACK_REQ
  8893       BitBufferHelper.writeBits(1, 1); // DAK_REQ
  8894       BitBufferHelper.flushWithPadding();
  8896   },
  8898   /**
  8899    * Entry point for SMS decoding, the returned object is made compatible
  8900    * with existing readMessage() of GsmPDUHelper
  8901    */
  8902   readMessage: function() {
  8903     let message = {};
  8905     // Teleservice Identifier
  8906     message.teleservice = this.readInt();
  8908     // Message Type
  8909     let isServicePresent = this.readByte();
  8910     if (isServicePresent) {
  8911       message.messageType = PDU_CDMA_MSG_TYPE_BROADCAST;
  8912     } else {
  8913       if (message.teleservice) {
  8914         message.messageType = PDU_CDMA_MSG_TYPE_P2P;
  8915       } else {
  8916         message.messageType = PDU_CDMA_MSG_TYPE_ACK;
  8920     // Service Category
  8921     message.service = this.readInt();
  8923     // Originated Address
  8924     let addrInfo = {};
  8925     addrInfo.digitMode = (this.readInt() & 0x01);
  8926     addrInfo.numberMode = (this.readInt() & 0x01);
  8927     addrInfo.numberType = (this.readInt() & 0x01);
  8928     addrInfo.numberPlan = (this.readInt() & 0x01);
  8929     addrInfo.addrLength = this.readByte();
  8930     addrInfo.address = [];
  8931     for (let i = 0; i < addrInfo.addrLength; i++) {
  8932       addrInfo.address.push(this.readByte());
  8934     message.sender = this.decodeAddr(addrInfo);
  8936     // Originated Subaddress
  8937     addrInfo.Type = (this.readInt() & 0x07);
  8938     addrInfo.Odd = (this.readByte() & 0x01);
  8939     addrInfo.addrLength = this.readByte();
  8940     for (let i = 0; i < addrInfo.addrLength; i++) {
  8941       let addrDigit = this.readByte();
  8942       message.sender += String.fromCharCode(addrDigit);
  8945     // Bearer Data
  8946     this.decodeUserData(message);
  8948     // Bearer Data Sub-Parameter: User Data
  8949     let userData = message[PDU_CDMA_MSG_USERDATA_BODY];
  8950     [message.header, message.body, message.encoding, message.data] =
  8951       (userData) ? [userData.header, userData.body, userData.encoding, userData.data]
  8952                  : [null, null, null, null];
  8954     // Bearer Data Sub-Parameter: Message Status
  8955     // Success Delivery (0) if both Message Status and User Data are absent.
  8956     // Message Status absent (-1) if only User Data is available.
  8957     let msgStatus = message[PDU_CDMA_MSG_USER_DATA_MSG_STATUS];
  8958     [message.errorClass, message.msgStatus] =
  8959       (msgStatus) ? [msgStatus.errorClass, msgStatus.msgStatus]
  8960                   : ((message.body) ? [-1, -1] : [0, 0]);
  8962     // Transform message to GSM msg
  8963     let msg = {
  8964       SMSC:             "",
  8965       mti:              0,
  8966       udhi:             0,
  8967       sender:           message.sender,
  8968       recipient:        null,
  8969       pid:              PDU_PID_DEFAULT,
  8970       epid:             PDU_PID_DEFAULT,
  8971       dcs:              0,
  8972       mwi:              null,
  8973       replace:          false,
  8974       header:           message.header,
  8975       body:             message.body,
  8976       data:             message.data,
  8977       sentTimestamp:    message[PDU_CDMA_MSG_USERDATA_TIMESTAMP],
  8978       language:         message[PDU_CDMA_LANGUAGE_INDICATOR],
  8979       status:           null,
  8980       scts:             null,
  8981       dt:               null,
  8982       encoding:         message.encoding,
  8983       messageClass:     GECKO_SMS_MESSAGE_CLASSES[PDU_DCS_MSG_CLASS_NORMAL],
  8984       messageType:      message.messageType,
  8985       serviceCategory:  message.service,
  8986       subMsgType:       message[PDU_CDMA_MSG_USERDATA_MSG_ID].msgType,
  8987       msgId:            message[PDU_CDMA_MSG_USERDATA_MSG_ID].msgId,
  8988       errorClass:       message.errorClass,
  8989       msgStatus:        message.msgStatus,
  8990       teleservice:      message.teleservice
  8991     };
  8993     return msg;
  8994   },
  8996   /**
  8997    * Helper for processing received SMS parcel data.
  8999    * @param length
  9000    *        Length of SMS string in the incoming parcel.
  9002    * @return Message parsed or null for invalid message.
  9003    */
  9004   processReceivedSms: function(length) {
  9005     if (!length) {
  9006       if (DEBUG) this.context.debug("Received empty SMS!");
  9007       return [null, PDU_FCS_UNSPECIFIED];
  9010     let message = this.readMessage();
  9011     if (DEBUG) this.context.debug("Got new SMS: " + JSON.stringify(message));
  9013     // Determine result
  9014     if (!message) {
  9015       return [null, PDU_FCS_UNSPECIFIED];
  9018     return [message, PDU_FCS_OK];
  9019   },
  9021   /**
  9022    * Data readers
  9023    */
  9024   readInt: function() {
  9025     return this.context.Buf.readInt32();
  9026   },
  9028   readByte: function() {
  9029     return (this.context.Buf.readInt32() & 0xFF);
  9030   },
  9032   /**
  9033    * Decode CDMA address data into address string
  9035    * @see 3GGP2 C.S0015-B 2.0, 3.4.3.3 Address Parameters
  9037    * Required key in addrInfo
  9038    * @param addrLength
  9039    *        Length of address
  9040    * @param digitMode
  9041    *        Address encoding method
  9042    * @param address
  9043    *        Array of encoded address data
  9044    */
  9045   decodeAddr: function(addrInfo) {
  9046     let result = "";
  9047     for (let i = 0; i < addrInfo.addrLength; i++) {
  9048       if (addrInfo.digitMode === PDU_CDMA_MSG_ADDR_DIGIT_MODE_DTMF) {
  9049         result += this.dtmfChars.charAt(addrInfo.address[i]);
  9050       } else {
  9051         result += String.fromCharCode(addrInfo.address[i]);
  9054     return result;
  9055   },
  9057   /**
  9058    * Read userData in parcel buffer and decode into message object.
  9059    * Each subparameter will be stored in corresponding key.
  9061    * @see 3GGP2 C.S0015-B 2.0, 3.4.3.7 Bearer Data
  9062    *                           4.5 Bearer Data Parameters
  9063    */
  9064   decodeUserData: function(message) {
  9065     let userDataLength = this.readInt();
  9067     while (userDataLength > 0) {
  9068       let id = this.readByte(),
  9069           length = this.readByte(),
  9070           userDataBuffer = [];
  9072       for (let i = 0; i < length; i++) {
  9073           userDataBuffer.push(this.readByte());
  9076       this.context.BitBufferHelper.startRead(userDataBuffer);
  9078       switch (id) {
  9079         case PDU_CDMA_MSG_USERDATA_MSG_ID:
  9080           message[id] = this.decodeUserDataMsgId();
  9081           break;
  9082         case PDU_CDMA_MSG_USERDATA_BODY:
  9083           message[id] = this.decodeUserDataMsg(message[PDU_CDMA_MSG_USERDATA_MSG_ID].userHeader);
  9084           break;
  9085         case PDU_CDMA_MSG_USERDATA_TIMESTAMP:
  9086           message[id] = this.decodeUserDataTimestamp();
  9087           break;
  9088         case PDU_CDMA_MSG_USERDATA_REPLY_OPTION:
  9089           message[id] = this.decodeUserDataReplyOption();
  9090           break;
  9091         case PDU_CDMA_LANGUAGE_INDICATOR:
  9092           message[id] = this.decodeLanguageIndicator();
  9093           break;
  9094         case PDU_CDMA_MSG_USERDATA_CALLBACK_NUMBER:
  9095           message[id] = this.decodeUserDataCallbackNumber();
  9096           break;
  9097         case PDU_CDMA_MSG_USER_DATA_MSG_STATUS:
  9098           message[id] = this.decodeUserDataMsgStatus();
  9099           break;
  9102       userDataLength -= (length + 2);
  9103       userDataBuffer = [];
  9105   },
  9107   /**
  9108    * User data subparameter decoder: Message Identifier
  9110    * @see 3GGP2 C.S0015-B 2.0, 4.5.1 Message Identifier
  9111    */
  9112   decodeUserDataMsgId: function() {
  9113     let result = {};
  9114     let BitBufferHelper = this.context.BitBufferHelper;
  9115     result.msgType = BitBufferHelper.readBits(4);
  9116     result.msgId = BitBufferHelper.readBits(16);
  9117     result.userHeader = BitBufferHelper.readBits(1);
  9119     return result;
  9120   },
  9122   /**
  9123    * Decode user data header, we only care about segment information
  9124    * on CDMA.
  9126    * This function is mostly copied from gsmPduHelper.readUserDataHeader() but
  9127    * change the read function, because CDMA user header decoding is't byte-wise
  9128    * aligned.
  9129    */
  9130   decodeUserDataHeader: function(encoding) {
  9131     let BitBufferHelper = this.context.BitBufferHelper;
  9132     let header = {},
  9133         headerSize = BitBufferHelper.readBits(8),
  9134         userDataHeaderSize = headerSize + 1,
  9135         headerPaddingBits = 0;
  9137     // Calculate header size
  9138     if (encoding === PDU_DCS_MSG_CODING_7BITS_ALPHABET) {
  9139       // Length is in 7-bit
  9140       header.length = Math.ceil(userDataHeaderSize * 8 / 7);
  9141       // Calulate padding length
  9142       headerPaddingBits = (header.length * 7) - (userDataHeaderSize * 8);
  9143     } else if (encoding === PDU_DCS_MSG_CODING_8BITS_ALPHABET) {
  9144       header.length = userDataHeaderSize;
  9145     } else {
  9146       header.length = userDataHeaderSize / 2;
  9149     while (headerSize) {
  9150       let identifier = BitBufferHelper.readBits(8),
  9151           length = BitBufferHelper.readBits(8);
  9153       headerSize -= (2 + length);
  9155       switch (identifier) {
  9156         case PDU_IEI_CONCATENATED_SHORT_MESSAGES_8BIT: {
  9157           let ref = BitBufferHelper.readBits(8),
  9158               max = BitBufferHelper.readBits(8),
  9159               seq = BitBufferHelper.readBits(8);
  9160           if (max && seq && (seq <= max)) {
  9161             header.segmentRef = ref;
  9162             header.segmentMaxSeq = max;
  9163             header.segmentSeq = seq;
  9165           break;
  9167         case PDU_IEI_APPLICATION_PORT_ADDRESSING_SCHEME_8BIT: {
  9168           let dstp = BitBufferHelper.readBits(8),
  9169               orip = BitBufferHelper.readBits(8);
  9170           if ((dstp < PDU_APA_RESERVED_8BIT_PORTS)
  9171               || (orip < PDU_APA_RESERVED_8BIT_PORTS)) {
  9172             // 3GPP TS 23.040 clause 9.2.3.24.3: "A receiving entity shall
  9173             // ignore any information element where the value of the
  9174             // Information-Element-Data is Reserved or not supported"
  9175             break;
  9177           header.destinationPort = dstp;
  9178           header.originatorPort = orip;
  9179           break;
  9181         case PDU_IEI_APPLICATION_PORT_ADDRESSING_SCHEME_16BIT: {
  9182           let dstp = (BitBufferHelper.readBits(8) << 8) | BitBufferHelper.readBits(8),
  9183               orip = (BitBufferHelper.readBits(8) << 8) | BitBufferHelper.readBits(8);
  9184           // 3GPP TS 23.040 clause 9.2.3.24.4: "A receiving entity shall
  9185           // ignore any information element where the value of the
  9186           // Information-Element-Data is Reserved or not supported"
  9187           if ((dstp < PDU_APA_VALID_16BIT_PORTS)
  9188               && (orip < PDU_APA_VALID_16BIT_PORTS)) {
  9189             header.destinationPort = dstp;
  9190             header.originatorPort = orip;
  9192           break;
  9194         case PDU_IEI_CONCATENATED_SHORT_MESSAGES_16BIT: {
  9195           let ref = (BitBufferHelper.readBits(8) << 8) | BitBufferHelper.readBits(8),
  9196               max = BitBufferHelper.readBits(8),
  9197               seq = BitBufferHelper.readBits(8);
  9198           if (max && seq && (seq <= max)) {
  9199             header.segmentRef = ref;
  9200             header.segmentMaxSeq = max;
  9201             header.segmentSeq = seq;
  9203           break;
  9205         case PDU_IEI_NATIONAL_LANGUAGE_SINGLE_SHIFT: {
  9206           let langShiftIndex = BitBufferHelper.readBits(8);
  9207           if (langShiftIndex < PDU_NL_SINGLE_SHIFT_TABLES.length) {
  9208             header.langShiftIndex = langShiftIndex;
  9210           break;
  9212         case PDU_IEI_NATIONAL_LANGUAGE_LOCKING_SHIFT: {
  9213           let langIndex = BitBufferHelper.readBits(8);
  9214           if (langIndex < PDU_NL_LOCKING_SHIFT_TABLES.length) {
  9215             header.langIndex = langIndex;
  9217           break;
  9219         case PDU_IEI_SPECIAL_SMS_MESSAGE_INDICATION: {
  9220           let msgInd = BitBufferHelper.readBits(8) & 0xFF,
  9221               msgCount = BitBufferHelper.readBits(8);
  9222           /*
  9223            * TS 23.040 V6.8.1 Sec 9.2.3.24.2
  9224            * bits 1 0   : basic message indication type
  9225            * bits 4 3 2 : extended message indication type
  9226            * bits 6 5   : Profile id
  9227            * bit  7     : storage type
  9228            */
  9229           let storeType = msgInd & PDU_MWI_STORE_TYPE_BIT;
  9230           header.mwi = {};
  9231           mwi = header.mwi;
  9233           if (storeType == PDU_MWI_STORE_TYPE_STORE) {
  9234             // Store message because TP_UDH indicates so, note this may override
  9235             // the setting in DCS, but that is expected
  9236             mwi.discard = false;
  9237           } else if (mwi.discard === undefined) {
  9238             // storeType == PDU_MWI_STORE_TYPE_DISCARD
  9239             // only override mwi.discard here if it hasn't already been set
  9240             mwi.discard = true;
  9243           mwi.msgCount = msgCount & 0xFF;
  9244           mwi.active = mwi.msgCount > 0;
  9246           if (DEBUG) {
  9247             this.context.debug("MWI in TP_UDH received: " + JSON.stringify(mwi));
  9249           break;
  9251         default:
  9252           // Drop unsupported id
  9253           for (let i = 0; i < length; i++) {
  9254             BitBufferHelper.readBits(8);
  9259     // Consume padding bits
  9260     if (headerPaddingBits) {
  9261       BitBufferHelper.readBits(headerPaddingBits);
  9264     return header;
  9265   },
  9267   getCdmaMsgEncoding: function(encoding) {
  9268     // Determine encoding method
  9269     switch (encoding) {
  9270       case PDU_CDMA_MSG_CODING_7BITS_ASCII:
  9271       case PDU_CDMA_MSG_CODING_IA5:
  9272       case PDU_CDMA_MSG_CODING_7BITS_GSM:
  9273         return PDU_DCS_MSG_CODING_7BITS_ALPHABET;
  9274       case PDU_CDMA_MSG_CODING_OCTET:
  9275       case PDU_CDMA_MSG_CODING_IS_91:
  9276       case PDU_CDMA_MSG_CODING_LATIN_HEBREW:
  9277       case PDU_CDMA_MSG_CODING_LATIN:
  9278         return PDU_DCS_MSG_CODING_8BITS_ALPHABET;
  9279       case PDU_CDMA_MSG_CODING_UNICODE:
  9280       case PDU_CDMA_MSG_CODING_SHIFT_JIS:
  9281       case PDU_CDMA_MSG_CODING_KOREAN:
  9282         return PDU_DCS_MSG_CODING_16BITS_ALPHABET;
  9284     return null;
  9285   },
  9287   decodeCdmaPDUMsg: function(encoding, msgType, msgBodySize) {
  9288     const langTable = PDU_NL_LOCKING_SHIFT_TABLES[PDU_NL_IDENTIFIER_DEFAULT];
  9289     const langShiftTable = PDU_NL_SINGLE_SHIFT_TABLES[PDU_NL_IDENTIFIER_DEFAULT];
  9290     let BitBufferHelper = this.context.BitBufferHelper;
  9291     let result = "";
  9292     let msgDigit;
  9293     switch (encoding) {
  9294       case PDU_CDMA_MSG_CODING_OCTET:         // TODO : Require Test
  9295         while(msgBodySize > 0) {
  9296           msgDigit = String.fromCharCode(BitBufferHelper.readBits(8));
  9297           result += msgDigit;
  9298           msgBodySize--;
  9300         break;
  9301       case PDU_CDMA_MSG_CODING_IS_91:         // TODO : Require Test
  9302         // Referenced from android code
  9303         switch (msgType) {
  9304           case PDU_CDMA_MSG_CODING_IS_91_TYPE_SMS:
  9305           case PDU_CDMA_MSG_CODING_IS_91_TYPE_SMS_FULL:
  9306           case PDU_CDMA_MSG_CODING_IS_91_TYPE_VOICEMAIL_STATUS:
  9307             while(msgBodySize > 0) {
  9308               msgDigit = String.fromCharCode(BitBufferHelper.readBits(6) + 0x20);
  9309               result += msgDigit;
  9310               msgBodySize--;
  9312             break;
  9313           case PDU_CDMA_MSG_CODING_IS_91_TYPE_CLI:
  9314             let addrInfo = {};
  9315             addrInfo.digitMode = PDU_CDMA_MSG_ADDR_DIGIT_MODE_DTMF;
  9316             addrInfo.numberMode = PDU_CDMA_MSG_ADDR_NUMBER_MODE_ANSI;
  9317             addrInfo.numberType = PDU_CDMA_MSG_ADDR_NUMBER_TYPE_UNKNOWN;
  9318             addrInfo.numberPlan = PDU_CDMA_MSG_ADDR_NUMBER_PLAN_UNKNOWN;
  9319             addrInfo.addrLength = msgBodySize;
  9320             addrInfo.address = [];
  9321             for (let i = 0; i < addrInfo.addrLength; i++) {
  9322               addrInfo.address.push(BitBufferHelper.readBits(4));
  9324             result = this.decodeAddr(addrInfo);
  9325             break;
  9327         // Fall through.
  9328       case PDU_CDMA_MSG_CODING_7BITS_ASCII:
  9329       case PDU_CDMA_MSG_CODING_IA5:           // TODO : Require Test
  9330         while(msgBodySize > 0) {
  9331           msgDigit = BitBufferHelper.readBits(7);
  9332           if (msgDigit >= 32) {
  9333             msgDigit = String.fromCharCode(msgDigit);
  9334           } else {
  9335             if (msgDigit !== PDU_NL_EXTENDED_ESCAPE) {
  9336               msgDigit = langTable[msgDigit];
  9337             } else {
  9338               msgDigit = BitBufferHelper.readBits(7);
  9339               msgBodySize--;
  9340               msgDigit = langShiftTable[msgDigit];
  9343           result += msgDigit;
  9344           msgBodySize--;
  9346         break;
  9347       case PDU_CDMA_MSG_CODING_UNICODE:
  9348         while(msgBodySize > 0) {
  9349           msgDigit = String.fromCharCode(BitBufferHelper.readBits(16));
  9350           result += msgDigit;
  9351           msgBodySize--;
  9353         break;
  9354       case PDU_CDMA_MSG_CODING_7BITS_GSM:     // TODO : Require Test
  9355         while(msgBodySize > 0) {
  9356           msgDigit = BitBufferHelper.readBits(7);
  9357           if (msgDigit !== PDU_NL_EXTENDED_ESCAPE) {
  9358             msgDigit = langTable[msgDigit];
  9359           } else {
  9360             msgDigit = BitBufferHelper.readBits(7);
  9361             msgBodySize--;
  9362             msgDigit = langShiftTable[msgDigit];
  9364           result += msgDigit;
  9365           msgBodySize--;
  9367         break;
  9368       case PDU_CDMA_MSG_CODING_LATIN:         // TODO : Require Test
  9369         // Reference : http://en.wikipedia.org/wiki/ISO/IEC_8859-1
  9370         while(msgBodySize > 0) {
  9371           msgDigit = String.fromCharCode(BitBufferHelper.readBits(8));
  9372           result += msgDigit;
  9373           msgBodySize--;
  9375         break;
  9376       case PDU_CDMA_MSG_CODING_LATIN_HEBREW:  // TODO : Require Test
  9377         // Reference : http://en.wikipedia.org/wiki/ISO/IEC_8859-8
  9378         while(msgBodySize > 0) {
  9379           msgDigit = BitBufferHelper.readBits(8);
  9380           if (msgDigit === 0xDF) {
  9381             msgDigit = String.fromCharCode(0x2017);
  9382           } else if (msgDigit === 0xFD) {
  9383             msgDigit = String.fromCharCode(0x200E);
  9384           } else if (msgDigit === 0xFE) {
  9385             msgDigit = String.fromCharCode(0x200F);
  9386           } else if (msgDigit >= 0xE0 && msgDigit <= 0xFA) {
  9387             msgDigit = String.fromCharCode(0x4F0 + msgDigit);
  9388           } else {
  9389             msgDigit = String.fromCharCode(msgDigit);
  9391           result += msgDigit;
  9392           msgBodySize--;
  9394         break;
  9395       case PDU_CDMA_MSG_CODING_SHIFT_JIS:
  9396         // Reference : http://msdn.microsoft.com/en-US/goglobal/cc305152.aspx
  9397         //             http://demo.icu-project.org/icu-bin/convexp?conv=Shift_JIS
  9398         let shift_jis_message = [];
  9400         while (msgBodySize > 0) {
  9401           shift_jis_message.push(BitBufferHelper.readBits(8));
  9402           msgBodySize--;
  9405         let decoder = new TextDecoder("shift_jis");
  9406         result = decoder.decode(new Uint8Array(shift_jis_message));
  9407         break;
  9408       case PDU_CDMA_MSG_CODING_KOREAN:
  9409       case PDU_CDMA_MSG_CODING_GSM_DCS:
  9410         // Fall through.
  9411       default:
  9412         break;
  9414     return result;
  9415   },
  9417   /**
  9418    * User data subparameter decoder : User Data
  9420    * @see 3GGP2 C.S0015-B 2.0, 4.5.2 User Data
  9421    */
  9422   decodeUserDataMsg: function(hasUserHeader) {
  9423     let BitBufferHelper = this.context.BitBufferHelper;
  9424     let result = {},
  9425         encoding = BitBufferHelper.readBits(5),
  9426         msgType;
  9428     if (encoding === PDU_CDMA_MSG_CODING_IS_91) {
  9429       msgType = BitBufferHelper.readBits(8);
  9431     result.encoding = this.getCdmaMsgEncoding(encoding);
  9433     let msgBodySize = BitBufferHelper.readBits(8);
  9435     // For segmented SMS, a user header is included before sms content
  9436     if (hasUserHeader) {
  9437       result.header = this.decodeUserDataHeader(result.encoding);
  9438       // header size is included in body size, they are decoded
  9439       msgBodySize -= result.header.length;
  9442     // Store original payload if enconding is OCTET for further handling of WAP Push, etc.
  9443     if (encoding === PDU_CDMA_MSG_CODING_OCTET && msgBodySize > 0) {
  9444       result.data = new Uint8Array(msgBodySize);
  9445       for (let i = 0; i < msgBodySize; i++) {
  9446         result.data[i] = BitBufferHelper.readBits(8);
  9448       BitBufferHelper.backwardReadPilot(8 * msgBodySize);
  9451     // Decode sms content
  9452     result.body = this.decodeCdmaPDUMsg(encoding, msgType, msgBodySize);
  9454     return result;
  9455   },
  9457   decodeBcd: function(value) {
  9458     return ((value >> 4) & 0xF) * 10 + (value & 0x0F);
  9459   },
  9461   /**
  9462    * User data subparameter decoder : Time Stamp
  9464    * @see 3GGP2 C.S0015-B 2.0, 4.5.4 Message Center Time Stamp
  9465    */
  9466   decodeUserDataTimestamp: function() {
  9467     let BitBufferHelper = this.context.BitBufferHelper;
  9468     let year = this.decodeBcd(BitBufferHelper.readBits(8)),
  9469         month = this.decodeBcd(BitBufferHelper.readBits(8)) - 1,
  9470         date = this.decodeBcd(BitBufferHelper.readBits(8)),
  9471         hour = this.decodeBcd(BitBufferHelper.readBits(8)),
  9472         min = this.decodeBcd(BitBufferHelper.readBits(8)),
  9473         sec = this.decodeBcd(BitBufferHelper.readBits(8));
  9475     if (year >= 96 && year <= 99) {
  9476       year += 1900;
  9477     } else {
  9478       year += 2000;
  9481     let result = (new Date(year, month, date, hour, min, sec, 0)).valueOf();
  9483     return result;
  9484   },
  9486   /**
  9487    * User data subparameter decoder : Reply Option
  9489    * @see 3GGP2 C.S0015-B 2.0, 4.5.11 Reply Option
  9490    */
  9491   decodeUserDataReplyOption: function() {
  9492     let replyAction = this.context.BitBufferHelper.readBits(4),
  9493         result = { userAck: (replyAction & 0x8) ? true : false,
  9494                    deliverAck: (replyAction & 0x4) ? true : false,
  9495                    readAck: (replyAction & 0x2) ? true : false,
  9496                    report: (replyAction & 0x1) ? true : false
  9497                  };
  9499     return result;
  9500   },
  9502   /**
  9503    * User data subparameter decoder : Language Indicator
  9505    * @see 3GGP2 C.S0015-B 2.0, 4.5.14 Language Indicator
  9506    */
  9507   decodeLanguageIndicator: function() {
  9508     let language = this.context.BitBufferHelper.readBits(8);
  9509     let result = CB_CDMA_LANG_GROUP[language];
  9510     return result;
  9511   },
  9513   /**
  9514    * User data subparameter decoder : Call-Back Number
  9516    * @see 3GGP2 C.S0015-B 2.0, 4.5.15 Call-Back Number
  9517    */
  9518   decodeUserDataCallbackNumber: function() {
  9519     let BitBufferHelper = this.context.BitBufferHelper;
  9520     let digitMode = BitBufferHelper.readBits(1);
  9521     if (digitMode) {
  9522       let numberType = BitBufferHelper.readBits(3),
  9523           numberPlan = BitBufferHelper.readBits(4);
  9525     let numberFields = BitBufferHelper.readBits(8),
  9526         result = "";
  9527     for (let i = 0; i < numberFields; i++) {
  9528       if (digitMode === PDU_CDMA_MSG_ADDR_DIGIT_MODE_DTMF) {
  9529         let addrDigit = BitBufferHelper.readBits(4);
  9530         result += this.dtmfChars.charAt(addrDigit);
  9531       } else {
  9532         let addrDigit = BitBufferHelper.readBits(8);
  9533         result += String.fromCharCode(addrDigit);
  9537     return result;
  9538   },
  9540   /**
  9541    * User data subparameter decoder : Message Status
  9543    * @see 3GGP2 C.S0015-B 2.0, 4.5.21 Message Status
  9544    */
  9545   decodeUserDataMsgStatus: function() {
  9546     let BitBufferHelper = this.context.BitBufferHelper;
  9547     let result = {
  9548       errorClass: BitBufferHelper.readBits(2),
  9549       msgStatus: BitBufferHelper.readBits(6)
  9550     };
  9552     return result;
  9553   },
  9555   /**
  9556    * Decode information record parcel.
  9557    */
  9558   decodeInformationRecord: function() {
  9559     let Buf = this.context.Buf;
  9560     let record = {};
  9561     let numOfRecords = Buf.readInt32();
  9563     let type;
  9564     for (let i = 0; i < numOfRecords; i++) {
  9565       type = Buf.readInt32();
  9567       switch (type) {
  9568         /*
  9569          * Every type is encaped by ril, except extended display
  9570          */
  9571         case PDU_CDMA_INFO_REC_TYPE_DISPLAY:
  9572           record.display = Buf.readString();
  9573           break;
  9574         case PDU_CDMA_INFO_REC_TYPE_CALLED_PARTY_NUMBER:
  9575           record.calledNumber = {};
  9576           record.calledNumber.number = Buf.readString();
  9577           record.calledNumber.type = Buf.readInt32();
  9578           record.calledNumber.plan = Buf.readInt32();
  9579           record.calledNumber.pi = Buf.readInt32();
  9580           record.calledNumber.si = Buf.readInt32();
  9581           break;
  9582         case PDU_CDMA_INFO_REC_TYPE_CALLING_PARTY_NUMBER:
  9583           record.callingNumber = {};
  9584           record.callingNumber.number = Buf.readString();
  9585           record.callingNumber.type = Buf.readInt32();
  9586           record.callingNumber.plan = Buf.readInt32();
  9587           record.callingNumber.pi = Buf.readInt32();
  9588           record.callingNumber.si = Buf.readInt32();
  9589           break;
  9590         case PDU_CDMA_INFO_REC_TYPE_CONNECTED_NUMBER:
  9591           record.connectedNumber = {};
  9592           record.connectedNumber.number = Buf.readString();
  9593           record.connectedNumber.type = Buf.readInt32();
  9594           record.connectedNumber.plan = Buf.readInt32();
  9595           record.connectedNumber.pi = Buf.readInt32();
  9596           record.connectedNumber.si = Buf.readInt32();
  9597           break;
  9598         case PDU_CDMA_INFO_REC_TYPE_SIGNAL:
  9599           record.signal = {};
  9600           record.signal.present = Buf.readInt32();
  9601           record.signal.type = Buf.readInt32();
  9602           record.signal.alertPitch = Buf.readInt32();
  9603           record.signal.signal = Buf.readInt32();
  9604           break;
  9605         case PDU_CDMA_INFO_REC_TYPE_REDIRECTING_NUMBER:
  9606           record.redirect = {};
  9607           record.redirect.number = Buf.readString();
  9608           record.redirect.type = Buf.readInt32();
  9609           record.redirect.plan = Buf.readInt32();
  9610           record.redirect.pi = Buf.readInt32();
  9611           record.redirect.si = Buf.readInt32();
  9612           record.redirect.reason = Buf.readInt32();
  9613           break;
  9614         case PDU_CDMA_INFO_REC_TYPE_LINE_CONTROL:
  9615           record.lineControl = {};
  9616           record.lineControl.polarityIncluded = Buf.readInt32();
  9617           record.lineControl.toggle = Buf.readInt32();
  9618           record.lineControl.recerse = Buf.readInt32();
  9619           record.lineControl.powerDenial = Buf.readInt32();
  9620           break;
  9621         case PDU_CDMA_INFO_REC_TYPE_EXTENDED_DISPLAY:
  9622           let length = Buf.readInt32();
  9623           /*
  9624            * Extended display is still in format defined in
  9625            * C.S0005-F v1.0, 3.7.5.16
  9626            */
  9627           record.extendedDisplay = {};
  9629           let headerByte = Buf.readInt32();
  9630           length--;
  9631           // Based on spec, headerByte must be 0x80 now
  9632           record.extendedDisplay.indicator = (headerByte >> 7);
  9633           record.extendedDisplay.type = (headerByte & 0x7F);
  9634           record.extendedDisplay.records = [];
  9636           while (length > 0) {
  9637             let display = {};
  9639             display.tag = Buf.readInt32();
  9640             length--;
  9641             if (display.tag !== INFO_REC_EXTENDED_DISPLAY_BLANK &&
  9642                 display.tag !== INFO_REC_EXTENDED_DISPLAY_SKIP) {
  9643               display.content = Buf.readString();
  9644               length -= (display.content.length + 1);
  9647             record.extendedDisplay.records.push(display);
  9649           break;
  9650         case PDU_CDMA_INFO_REC_TYPE_T53_CLIR:
  9651           record.cause = Buf.readInt32();
  9652           break;
  9653         case PDU_CDMA_INFO_REC_TYPE_T53_AUDIO_CONTROL:
  9654           record.audioControl = {};
  9655           record.audioControl.upLink = Buf.readInt32();
  9656           record.audioControl.downLink = Buf.readInt32();
  9657           break;
  9658         case PDU_CDMA_INFO_REC_TYPE_T53_RELEASE:
  9659           // Fall through
  9660         default:
  9661           throw new Error("UNSOLICITED_CDMA_INFO_REC(), Unsupported information record type " + record.type + "\n");
  9665     return record;
  9667 };
  9669 /**
  9670  * Helper for processing ICC PDUs.
  9671  */
  9672 function ICCPDUHelperObject(aContext) {
  9673   this.context = aContext;
  9675 ICCPDUHelperObject.prototype = {
  9676   context: null,
  9678   /**
  9679    * Read GSM 8-bit unpacked octets,
  9680    * which are default 7-bit alphabets with bit 8 set to 0.
  9682    * @param numOctets
  9683    *        Number of octets to be read.
  9684    */
  9685   read8BitUnpackedToString: function(numOctets) {
  9686     let GsmPDUHelper = this.context.GsmPDUHelper;
  9688     let ret = "";
  9689     let escapeFound = false;
  9690     let i;
  9691     const langTable = PDU_NL_LOCKING_SHIFT_TABLES[PDU_NL_IDENTIFIER_DEFAULT];
  9692     const langShiftTable = PDU_NL_SINGLE_SHIFT_TABLES[PDU_NL_IDENTIFIER_DEFAULT];
  9694     for(i = 0; i < numOctets; i++) {
  9695       let octet = GsmPDUHelper.readHexOctet();
  9696       if (octet == 0xff) {
  9697         i++;
  9698         break;
  9701       if (escapeFound) {
  9702         escapeFound = false;
  9703         if (octet == PDU_NL_EXTENDED_ESCAPE) {
  9704           // According to 3GPP TS 23.038, section 6.2.1.1, NOTE 1, "On
  9705           // receipt of this code, a receiving entity shall display a space
  9706           // until another extensiion table is defined."
  9707           ret += " ";
  9708         } else if (octet == PDU_NL_RESERVED_CONTROL) {
  9709           // According to 3GPP TS 23.038 B.2, "This code represents a control
  9710           // character and therefore must not be used for language specific
  9711           // characters."
  9712           ret += " ";
  9713         } else {
  9714           ret += langShiftTable[octet];
  9716       } else if (octet == PDU_NL_EXTENDED_ESCAPE) {
  9717         escapeFound = true;
  9718       } else {
  9719         ret += langTable[octet];
  9723     let Buf = this.context.Buf;
  9724     Buf.seekIncoming((numOctets - i) * Buf.PDU_HEX_OCTET_SIZE);
  9725     return ret;
  9726   },
  9728   /**
  9729    * Write GSM 8-bit unpacked octets.
  9731    * @param numOctets   Number of total octets to be writen, including trailing
  9732    *                    0xff.
  9733    * @param str         String to be written. Could be null.
  9734    */
  9735   writeStringTo8BitUnpacked: function(numOctets, str) {
  9736     const langTable = PDU_NL_LOCKING_SHIFT_TABLES[PDU_NL_IDENTIFIER_DEFAULT];
  9737     const langShiftTable = PDU_NL_SINGLE_SHIFT_TABLES[PDU_NL_IDENTIFIER_DEFAULT];
  9739     let GsmPDUHelper = this.context.GsmPDUHelper;
  9741     // If the character is GSM extended alphabet, two octets will be written.
  9742     // So we need to keep track of number of octets to be written.
  9743     let i, j;
  9744     let len = str ? str.length : 0;
  9745     for (i = 0, j = 0; i < len && j < numOctets; i++) {
  9746       let c = str.charAt(i);
  9747       let octet = langTable.indexOf(c);
  9749       if (octet == -1) {
  9750         // Make sure we still have enough space to write two octets.
  9751         if (j + 2 > numOctets) {
  9752           break;
  9755         octet = langShiftTable.indexOf(c);
  9756         if (octet == -1) {
  9757           // Fallback to ASCII space.
  9758           octet = langTable.indexOf(' ');
  9760         GsmPDUHelper.writeHexOctet(PDU_NL_EXTENDED_ESCAPE);
  9761         j++;
  9763       GsmPDUHelper.writeHexOctet(octet);
  9764       j++;
  9767     // trailing 0xff
  9768     while (j++ < numOctets) {
  9769       GsmPDUHelper.writeHexOctet(0xff);
  9771   },
  9773   /**
  9774    * Read UCS2 String on UICC.
  9776    * @see TS 101.221, Annex A.
  9777    * @param scheme
  9778    *        Coding scheme for UCS2 on UICC. One of 0x80, 0x81 or 0x82.
  9779    * @param numOctets
  9780    *        Number of octets to be read as UCS2 string.
  9781    */
  9782   readICCUCS2String: function(scheme, numOctets) {
  9783     let Buf = this.context.Buf;
  9784     let GsmPDUHelper = this.context.GsmPDUHelper;
  9786     let str = "";
  9787     switch (scheme) {
  9788       /**
  9789        * +------+---------+---------+---------+---------+------+------+
  9790        * | 0x80 | Ch1_msb | Ch1_lsb | Ch2_msb | Ch2_lsb | 0xff | 0xff |
  9791        * +------+---------+---------+---------+---------+------+------+
  9792        */
  9793       case 0x80:
  9794         let isOdd = numOctets % 2;
  9795         let i;
  9796         for (i = 0; i < numOctets - isOdd; i += 2) {
  9797           let code = (GsmPDUHelper.readHexOctet() << 8) | GsmPDUHelper.readHexOctet();
  9798           if (code == 0xffff) {
  9799             i += 2;
  9800             break;
  9802           str += String.fromCharCode(code);
  9805         // Skip trailing 0xff
  9806         Buf.seekIncoming((numOctets - i) * Buf.PDU_HEX_OCTET_SIZE);
  9807         break;
  9808       case 0x81: // Fall through
  9809       case 0x82:
  9810         /**
  9811          * +------+-----+--------+-----+-----+-----+--------+------+
  9812          * | 0x81 | len | offset | Ch1 | Ch2 | ... | Ch_len | 0xff |
  9813          * +------+-----+--------+-----+-----+-----+--------+------+
  9815          * len : The length of characters.
  9816          * offset : 0hhh hhhh h000 0000
  9817          * Ch_n: bit 8 = 0
  9818          *       GSM default alphabets
  9819          *       bit 8 = 1
  9820          *       UCS2 character whose char code is (Ch_n & 0x7f) + offset
  9822          * +------+-----+------------+------------+-----+-----+-----+--------+
  9823          * | 0x82 | len | offset_msb | offset_lsb | Ch1 | Ch2 | ... | Ch_len |
  9824          * +------+-----+------------+------------+-----+-----+-----+--------+
  9826          * len : The length of characters.
  9827          * offset_msb, offset_lsn: offset
  9828          * Ch_n: bit 8 = 0
  9829          *       GSM default alphabets
  9830          *       bit 8 = 1
  9831          *       UCS2 character whose char code is (Ch_n & 0x7f) + offset
  9832          */
  9833         let len = GsmPDUHelper.readHexOctet();
  9834         let offset, headerLen;
  9835         if (scheme == 0x81) {
  9836           offset = GsmPDUHelper.readHexOctet() << 7;
  9837           headerLen = 2;
  9838         } else {
  9839           offset = (GsmPDUHelper.readHexOctet() << 8) | GsmPDUHelper.readHexOctet();
  9840           headerLen = 3;
  9843         for (let i = 0; i < len; i++) {
  9844           let ch = GsmPDUHelper.readHexOctet();
  9845           if (ch & 0x80) {
  9846             // UCS2
  9847             str += String.fromCharCode((ch & 0x7f) + offset);
  9848           } else {
  9849             // GSM 8bit
  9850             let count = 0, gotUCS2 = 0;
  9851             while ((i + count + 1 < len)) {
  9852               count++;
  9853               if (GsmPDUHelper.readHexOctet() & 0x80) {
  9854                 gotUCS2 = 1;
  9855                 break;
  9858             // Unread.
  9859             // +1 for the GSM alphabet indexed at i,
  9860             Buf.seekIncoming(-1 * (count + 1) * Buf.PDU_HEX_OCTET_SIZE);
  9861             str += this.read8BitUnpackedToString(count + 1 - gotUCS2);
  9862             i += count - gotUCS2;
  9866         // Skipping trailing 0xff
  9867         Buf.seekIncoming((numOctets - len - headerLen) * Buf.PDU_HEX_OCTET_SIZE);
  9868         break;
  9870     return str;
  9871   },
  9873   /**
  9874    * Read Alpha Id and Dialling number from TS TS 151.011 clause 10.5.1
  9876    * @param recordSize  The size of linear fixed record.
  9877    */
  9878   readAlphaIdDiallingNumber: function(recordSize) {
  9879     let Buf = this.context.Buf;
  9880     let length = Buf.readInt32();
  9882     let alphaLen = recordSize - ADN_FOOTER_SIZE_BYTES;
  9883     let alphaId = this.readAlphaIdentifier(alphaLen);
  9885     let number = this.readNumberWithLength();
  9887     // Skip 2 unused octets, CCP and EXT1.
  9888     Buf.seekIncoming(2 * Buf.PDU_HEX_OCTET_SIZE);
  9889     Buf.readStringDelimiter(length);
  9891     let contact = null;
  9892     if (alphaId || number) {
  9893       contact = {alphaId: alphaId,
  9894                  number: number};
  9896     return contact;
  9897   },
  9899   /**
  9900    * Write Alpha Identifier and Dialling number from TS 151.011 clause 10.5.1
  9902    * @param recordSize  The size of linear fixed record.
  9903    * @param alphaId     Alpha Identifier to be written.
  9904    * @param number      Dialling Number to be written.
  9905    */
  9906   writeAlphaIdDiallingNumber: function(recordSize, alphaId, number) {
  9907     let Buf = this.context.Buf;
  9908     let GsmPDUHelper = this.context.GsmPDUHelper;
  9910     // Write String length
  9911     let strLen = recordSize * 2;
  9912     Buf.writeInt32(strLen);
  9914     let alphaLen = recordSize - ADN_FOOTER_SIZE_BYTES;
  9915     this.writeAlphaIdentifier(alphaLen, alphaId);
  9916     this.writeNumberWithLength(number);
  9918     // Write unused octets 0xff, CCP and EXT1.
  9919     GsmPDUHelper.writeHexOctet(0xff);
  9920     GsmPDUHelper.writeHexOctet(0xff);
  9921     Buf.writeStringDelimiter(strLen);
  9922   },
  9924   /**
  9925    * Read Alpha Identifier.
  9927    * @see TS 131.102
  9929    * @param numOctets
  9930    *        Number of octets to be read.
  9932    * It uses either
  9933    *  1. SMS default 7-bit alphabet with bit 8 set to 0.
  9934    *  2. UCS2 string.
  9936    * Unused bytes should be set to 0xff.
  9937    */
  9938   readAlphaIdentifier: function(numOctets) {
  9939     if (numOctets === 0) {
  9940       return "";
  9943     let temp;
  9944     // Read the 1st octet to determine the encoding.
  9945     if ((temp = this.context.GsmPDUHelper.readHexOctet()) == 0x80 ||
  9946          temp == 0x81 ||
  9947          temp == 0x82) {
  9948       numOctets--;
  9949       return this.readICCUCS2String(temp, numOctets);
  9950     } else {
  9951       let Buf = this.context.Buf;
  9952       Buf.seekIncoming(-1 * Buf.PDU_HEX_OCTET_SIZE);
  9953       return this.read8BitUnpackedToString(numOctets);
  9955   },
  9957   /**
  9958    * Write Alpha Identifier.
  9960    * @param numOctets
  9961    *        Total number of octets to be written. This includes the length of
  9962    *        alphaId and the length of trailing unused octets(0xff).
  9963    * @param alphaId
  9964    *        Alpha Identifier to be written.
  9966    * Unused octets will be written as 0xff.
  9967    */
  9968   writeAlphaIdentifier: function(numOctets, alphaId) {
  9969     if (numOctets === 0) {
  9970       return;
  9973     // If alphaId is empty or it's of GSM 8 bit.
  9974     if (!alphaId || this.context.ICCUtilsHelper.isGsm8BitAlphabet(alphaId)) {
  9975       this.writeStringTo8BitUnpacked(numOctets, alphaId);
  9976     } else {
  9977       let GsmPDUHelper = this.context.GsmPDUHelper;
  9979       // Currently only support UCS2 coding scheme 0x80.
  9980       GsmPDUHelper.writeHexOctet(0x80);
  9981       numOctets--;
  9982       // Now the alphaId is UCS2 string, each character will take 2 octets.
  9983       if (alphaId.length * 2 > numOctets) {
  9984         alphaId = alphaId.substring(0, Math.floor(numOctets / 2));
  9986       GsmPDUHelper.writeUCS2String(alphaId);
  9987       for (let i = alphaId.length * 2; i < numOctets; i++) {
  9988         GsmPDUHelper.writeHexOctet(0xff);
  9991   },
  9993   /**
  9994    * Read Dialling number.
  9996    * @see TS 131.102
  9998    * @param len
  9999    *        The Length of BCD number.
 10001    * From TS 131.102, in EF_ADN, EF_FDN, the field 'Length of BCD number'
 10002    * means the total bytes should be allocated to store the TON/NPI and
 10003    * the dialing number.
 10004    * For example, if the dialing number is 1234567890,
 10005    * and the TON/NPI is 0x81,
 10006    * The field 'Length of BCD number' should be 06, which is
 10007    * 1 byte to store the TON/NPI, 0x81
 10008    * 5 bytes to store the BCD number 2143658709.
 10010    * Here the definition of the length is different from SMS spec,
 10011    * TS 23.040 9.1.2.5, which the length means
 10012    * "number of useful semi-octets within the Address-Value field".
 10013    */
 10014   readDiallingNumber: function(len) {
 10015     if (DEBUG) this.context.debug("PDU: Going to read Dialling number: " + len);
 10016     if (len === 0) {
 10017       return "";
 10020     let GsmPDUHelper = this.context.GsmPDUHelper;
 10022     // TOA = TON + NPI
 10023     let toa = GsmPDUHelper.readHexOctet();
 10025     let number = GsmPDUHelper.readSwappedNibbleBcdString(len - 1);
 10026     if (number.length <= 0) {
 10027       if (DEBUG) this.context.debug("No number provided");
 10028       return "";
 10030     if ((toa >> 4) == (PDU_TOA_INTERNATIONAL >> 4)) {
 10031       number = '+' + number;
 10033     return number;
 10034   },
 10036   /**
 10037    * Write Dialling Number.
 10039    * @param number  The Dialling number
 10040    */
 10041   writeDiallingNumber: function(number) {
 10042     let GsmPDUHelper = this.context.GsmPDUHelper;
 10044     let toa = PDU_TOA_ISDN; // 81
 10045     if (number[0] == '+') {
 10046       toa = PDU_TOA_INTERNATIONAL | PDU_TOA_ISDN; // 91
 10047       number = number.substring(1);
 10049     GsmPDUHelper.writeHexOctet(toa);
 10050     GsmPDUHelper.writeSwappedNibbleBCD(number);
 10051   },
 10053   readNumberWithLength: function() {
 10054     let Buf = this.context.Buf;
 10055     let number;
 10056     let numLen = this.context.GsmPDUHelper.readHexOctet();
 10057     if (numLen != 0xff) {
 10058       if (numLen > ADN_MAX_BCD_NUMBER_BYTES) {
 10059         throw new Error("invalid length of BCD number/SSC contents - " + numLen);
 10062       number = this.readDiallingNumber(numLen);
 10063       Buf.seekIncoming((ADN_MAX_BCD_NUMBER_BYTES - numLen) * Buf.PDU_HEX_OCTET_SIZE);
 10064     } else {
 10065       Buf.seekIncoming(ADN_MAX_BCD_NUMBER_BYTES * Buf.PDU_HEX_OCTET_SIZE);
 10068     return number;
 10069   },
 10071   writeNumberWithLength: function(number) {
 10072     let GsmPDUHelper = this.context.GsmPDUHelper;
 10074     if (number) {
 10075       let numStart = number[0] == "+" ? 1 : 0;
 10076       number = number.substring(0, numStart) +
 10077                number.substring(numStart)
 10078                      .replace(/[^0-9*#,]/g, "")
 10079                      .replace(/\*/g, "a")
 10080                      .replace(/\#/g, "b")
 10081                      .replace(/\,/g, "c");
 10083       let numDigits = number.length - numStart;
 10084       if (numDigits > ADN_MAX_NUMBER_DIGITS) {
 10085         number = number.substring(0, ADN_MAX_NUMBER_DIGITS + numStart);
 10086         numDigits = number.length - numStart;
 10089       // +1 for TON/NPI
 10090       let numLen = Math.ceil(numDigits / 2) + 1;
 10091       GsmPDUHelper.writeHexOctet(numLen);
 10092       this.writeDiallingNumber(number);
 10093       // Write trailing 0xff of Dialling Number.
 10094       for (let i = 0; i < ADN_MAX_BCD_NUMBER_BYTES - numLen; i++) {
 10095         GsmPDUHelper.writeHexOctet(0xff);
 10097     } else {
 10098       // +1 for numLen
 10099       for (let i = 0; i < ADN_MAX_BCD_NUMBER_BYTES + 1; i++) {
 10100         GsmPDUHelper.writeHexOctet(0xff);
 10104 };
 10106 function StkCommandParamsFactoryObject(aContext) {
 10107   this.context = aContext;
 10109 StkCommandParamsFactoryObject.prototype = {
 10110   context: null,
 10112   createParam: function(cmdDetails, ctlvs) {
 10113     let method = this[cmdDetails.typeOfCommand];
 10114     if (typeof method != "function") {
 10115       if (DEBUG) {
 10116         this.context.debug("Unknown proactive command " +
 10117                            cmdDetails.typeOfCommand.toString(16));
 10119       return null;
 10121     return method.call(this, cmdDetails, ctlvs);
 10122   },
 10124   /**
 10125    * Construct a param for Refresh.
 10127    * @param cmdDetails
 10128    *        The value object of CommandDetails TLV.
 10129    * @param ctlvs
 10130    *        The all TLVs in this proactive command.
 10131    */
 10132   processRefresh: function(cmdDetails, ctlvs) {
 10133     let refreshType = cmdDetails.commandQualifier;
 10134     switch (refreshType) {
 10135       case STK_REFRESH_FILE_CHANGE:
 10136       case STK_REFRESH_NAA_INIT_AND_FILE_CHANGE:
 10137         let ctlv = this.context.StkProactiveCmdHelper.searchForTag(
 10138           COMPREHENSIONTLV_TAG_FILE_LIST, ctlvs);
 10139         if (ctlv) {
 10140           let list = ctlv.value.fileList;
 10141           if (DEBUG) {
 10142             this.context.debug("Refresh, list = " + list);
 10144           this.context.ICCRecordHelper.fetchICCRecords();
 10146         break;
 10148     return null;
 10149   },
 10151   /**
 10152    * Construct a param for Poll Interval.
 10154    * @param cmdDetails
 10155    *        The value object of CommandDetails TLV.
 10156    * @param ctlvs
 10157    *        The all TLVs in this proactive command.
 10158    */
 10159   processPollInterval: function(cmdDetails, ctlvs) {
 10160     let ctlv = this.context.StkProactiveCmdHelper.searchForTag(
 10161         COMPREHENSIONTLV_TAG_DURATION, ctlvs);
 10162     if (!ctlv) {
 10163       this.context.RIL.sendStkTerminalResponse({
 10164         command: cmdDetails,
 10165         resultCode: STK_RESULT_REQUIRED_VALUES_MISSING});
 10166       throw new Error("Stk Poll Interval: Required value missing : Duration");
 10169     return ctlv.value;
 10170   },
 10172   /**
 10173    * Construct a param for Poll Off.
 10175    * @param cmdDetails
 10176    *        The value object of CommandDetails TLV.
 10177    * @param ctlvs
 10178    *        The all TLVs in this proactive command.
 10179    */
 10180   processPollOff: function(cmdDetails, ctlvs) {
 10181     return null;
 10182   },
 10184   /**
 10185    * Construct a param for Set Up Event list.
 10187    * @param cmdDetails
 10188    *        The value object of CommandDetails TLV.
 10189    * @param ctlvs
 10190    *        The all TLVs in this proactive command.
 10191    */
 10192   processSetUpEventList: function(cmdDetails, ctlvs) {
 10193     let ctlv = this.context.StkProactiveCmdHelper.searchForTag(
 10194         COMPREHENSIONTLV_TAG_EVENT_LIST, ctlvs);
 10195     if (!ctlv) {
 10196       this.context.RIL.sendStkTerminalResponse({
 10197         command: cmdDetails,
 10198         resultCode: STK_RESULT_REQUIRED_VALUES_MISSING});
 10199       throw new Error("Stk Event List: Required value missing : Event List");
 10202     return ctlv.value || {eventList: null};
 10203   },
 10205   /**
 10206    * Construct a param for Select Item.
 10208    * @param cmdDetails
 10209    *        The value object of CommandDetails TLV.
 10210    * @param ctlvs
 10211    *        The all TLVs in this proactive command.
 10212    */
 10213   processSelectItem: function(cmdDetails, ctlvs) {
 10214     let StkProactiveCmdHelper = this.context.StkProactiveCmdHelper;
 10215     let menu = {};
 10217     let ctlv = StkProactiveCmdHelper.searchForTag(COMPREHENSIONTLV_TAG_ALPHA_ID, ctlvs);
 10218     if (ctlv) {
 10219       menu.title = ctlv.value.identifier;
 10222     menu.items = [];
 10223     for (let i = 0; i < ctlvs.length; i++) {
 10224       let ctlv = ctlvs[i];
 10225       if (ctlv.tag == COMPREHENSIONTLV_TAG_ITEM) {
 10226         menu.items.push(ctlv.value);
 10230     if (menu.items.length === 0) {
 10231       this.context.RIL.sendStkTerminalResponse({
 10232         command: cmdDetails,
 10233         resultCode: STK_RESULT_REQUIRED_VALUES_MISSING});
 10234       throw new Error("Stk Menu: Required value missing : items");
 10237     ctlv = StkProactiveCmdHelper.searchForTag(COMPREHENSIONTLV_TAG_ITEM_ID, ctlvs);
 10238     if (ctlv) {
 10239       menu.defaultItem = ctlv.value.identifier - 1;
 10242     // The 1st bit and 2nd bit determines the presentation type.
 10243     menu.presentationType = cmdDetails.commandQualifier & 0x03;
 10245     // Help information available.
 10246     if (cmdDetails.commandQualifier & 0x80) {
 10247       menu.isHelpAvailable = true;
 10250     ctlv = StkProactiveCmdHelper.searchForTag(COMPREHENSIONTLV_TAG_NEXT_ACTION_IND, ctlvs);
 10251     if (ctlv) {
 10252       menu.nextActionList = ctlv.value;
 10255     return menu;
 10256   },
 10258   processDisplayText: function(cmdDetails, ctlvs) {
 10259     let StkProactiveCmdHelper = this.context.StkProactiveCmdHelper;
 10260     let textMsg = {};
 10262     let ctlv = StkProactiveCmdHelper.searchForTag(COMPREHENSIONTLV_TAG_TEXT_STRING, ctlvs);
 10263     if (!ctlv) {
 10264       this.context.RIL.sendStkTerminalResponse({
 10265         command: cmdDetails,
 10266         resultCode: STK_RESULT_REQUIRED_VALUES_MISSING});
 10267       throw new Error("Stk Display Text: Required value missing : Text String");
 10269     textMsg.text = ctlv.value.textString;
 10271     ctlv = StkProactiveCmdHelper.searchForTag(COMPREHENSIONTLV_TAG_IMMEDIATE_RESPONSE, ctlvs);
 10272     if (ctlv) {
 10273       textMsg.responseNeeded = true;
 10276     ctlv = StkProactiveCmdHelper.searchForTag(COMPREHENSIONTLV_TAG_DURATION, ctlvs);
 10277     if (ctlv) {
 10278       textMsg.duration = ctlv.value;
 10281     // High priority.
 10282     if (cmdDetails.commandQualifier & 0x01) {
 10283       textMsg.isHighPriority = true;
 10286     // User clear.
 10287     if (cmdDetails.commandQualifier & 0x80) {
 10288       textMsg.userClear = true;
 10291     return textMsg;
 10292   },
 10294   processSetUpIdleModeText: function(cmdDetails, ctlvs) {
 10295     let textMsg = {};
 10297     let ctlv = this.context.StkProactiveCmdHelper.searchForTag(
 10298         COMPREHENSIONTLV_TAG_TEXT_STRING, ctlvs);
 10299     if (!ctlv) {
 10300       this.context.RIL.sendStkTerminalResponse({
 10301         command: cmdDetails,
 10302         resultCode: STK_RESULT_REQUIRED_VALUES_MISSING});
 10303       throw new Error("Stk Set Up Idle Text: Required value missing : Text String");
 10305     textMsg.text = ctlv.value.textString;
 10307     return textMsg;
 10308   },
 10310   processGetInkey: function(cmdDetails, ctlvs) {
 10311     let StkProactiveCmdHelper = this.context.StkProactiveCmdHelper;
 10312     let input = {};
 10314     let ctlv = StkProactiveCmdHelper.searchForTag(COMPREHENSIONTLV_TAG_TEXT_STRING, ctlvs);
 10315     if (!ctlv) {
 10316       this.context.RIL.sendStkTerminalResponse({
 10317         command: cmdDetails,
 10318         resultCode: STK_RESULT_REQUIRED_VALUES_MISSING});
 10319       throw new Error("Stk Get InKey: Required value missing : Text String");
 10321     input.text = ctlv.value.textString;
 10323     // duration
 10324     ctlv = StkProactiveCmdHelper.searchForTag(
 10325         COMPREHENSIONTLV_TAG_DURATION, ctlvs);
 10326     if (ctlv) {
 10327       input.duration = ctlv.value;
 10330     input.minLength = 1;
 10331     input.maxLength = 1;
 10333     // isAlphabet
 10334     if (cmdDetails.commandQualifier & 0x01) {
 10335       input.isAlphabet = true;
 10338     // UCS2
 10339     if (cmdDetails.commandQualifier & 0x02) {
 10340       input.isUCS2 = true;
 10343     // Character sets defined in bit 1 and bit 2 are disable and
 10344     // the YES/NO reponse is required.
 10345     if (cmdDetails.commandQualifier & 0x04) {
 10346       input.isYesNoRequested = true;
 10349     // Help information available.
 10350     if (cmdDetails.commandQualifier & 0x80) {
 10351       input.isHelpAvailable = true;
 10354     return input;
 10355   },
 10357   processGetInput: function(cmdDetails, ctlvs) {
 10358     let StkProactiveCmdHelper = this.context.StkProactiveCmdHelper;
 10359     let input = {};
 10361     let ctlv = StkProactiveCmdHelper.searchForTag(COMPREHENSIONTLV_TAG_TEXT_STRING, ctlvs);
 10362     if (!ctlv) {
 10363       this.context.RIL.sendStkTerminalResponse({
 10364         command: cmdDetails,
 10365         resultCode: STK_RESULT_REQUIRED_VALUES_MISSING});
 10366       throw new Error("Stk Get Input: Required value missing : Text String");
 10368     input.text = ctlv.value.textString;
 10370     ctlv = StkProactiveCmdHelper.searchForTag(COMPREHENSIONTLV_TAG_RESPONSE_LENGTH, ctlvs);
 10371     if (ctlv) {
 10372       input.minLength = ctlv.value.minLength;
 10373       input.maxLength = ctlv.value.maxLength;
 10376     ctlv = StkProactiveCmdHelper.searchForTag(COMPREHENSIONTLV_TAG_DEFAULT_TEXT, ctlvs);
 10377     if (ctlv) {
 10378       input.defaultText = ctlv.value.textString;
 10381     // Alphabet only
 10382     if (cmdDetails.commandQualifier & 0x01) {
 10383       input.isAlphabet = true;
 10386     // UCS2
 10387     if (cmdDetails.commandQualifier & 0x02) {
 10388       input.isUCS2 = true;
 10391     // User input shall not be revealed
 10392     if (cmdDetails.commandQualifier & 0x04) {
 10393       input.hideInput = true;
 10396     // User input in SMS packed format
 10397     if (cmdDetails.commandQualifier & 0x08) {
 10398       input.isPacked = true;
 10401     // Help information available.
 10402     if (cmdDetails.commandQualifier & 0x80) {
 10403       input.isHelpAvailable = true;
 10406     return input;
 10407   },
 10409   processEventNotify: function(cmdDetails, ctlvs) {
 10410     let textMsg = {};
 10412     let ctlv = this.context.StkProactiveCmdHelper.searchForTag(
 10413         COMPREHENSIONTLV_TAG_ALPHA_ID, ctlvs);
 10414     if (!ctlv) {
 10415       this.context.RIL.sendStkTerminalResponse({
 10416         command: cmdDetails,
 10417         resultCode: STK_RESULT_REQUIRED_VALUES_MISSING});
 10418       throw new Error("Stk Event Notfiy: Required value missing : Alpha ID");
 10420     textMsg.text = ctlv.value.identifier;
 10422     return textMsg;
 10423   },
 10425   processSetupCall: function(cmdDetails, ctlvs) {
 10426     let StkProactiveCmdHelper = this.context.StkProactiveCmdHelper;
 10427     let call = {};
 10428     let iter = Iterator(ctlvs);
 10430     let ctlv = StkProactiveCmdHelper.searchForNextTag(COMPREHENSIONTLV_TAG_ALPHA_ID, iter);
 10431     if (ctlv) {
 10432       call.confirmMessage = ctlv.value.identifier;
 10435     ctlv = StkProactiveCmdHelper.searchForNextTag(COMPREHENSIONTLV_TAG_ALPHA_ID, iter);
 10436     if (ctlv) {
 10437       call.callMessage = ctlv.value.identifier;
 10440     ctlv = StkProactiveCmdHelper.searchForTag(COMPREHENSIONTLV_TAG_ADDRESS, ctlvs);
 10441     if (!ctlv) {
 10442       this.context.RIL.sendStkTerminalResponse({
 10443         command: cmdDetails,
 10444         resultCode: STK_RESULT_REQUIRED_VALUES_MISSING});
 10445       throw new Error("Stk Set Up Call: Required value missing : Adress");
 10447     call.address = ctlv.value.number;
 10449     // see 3GPP TS 31.111 section 6.4.13
 10450     ctlv = StkProactiveCmdHelper.searchForTag(COMPREHENSIONTLV_TAG_DURATION, ctlvs);
 10451     if (ctlv) {
 10452       call.duration = ctlv.value;
 10455     return call;
 10456   },
 10458   processLaunchBrowser: function(cmdDetails, ctlvs) {
 10459     let StkProactiveCmdHelper = this.context.StkProactiveCmdHelper;
 10460     let browser = {};
 10462     let ctlv = StkProactiveCmdHelper.searchForTag(COMPREHENSIONTLV_TAG_URL, ctlvs);
 10463     if (!ctlv) {
 10464       this.context.RIL.sendStkTerminalResponse({
 10465         command: cmdDetails,
 10466         resultCode: STK_RESULT_REQUIRED_VALUES_MISSING});
 10467       throw new Error("Stk Launch Browser: Required value missing : URL");
 10469     browser.url = ctlv.value.url;
 10471     ctlv = StkProactiveCmdHelper.searchForTag(COMPREHENSIONTLV_TAG_ALPHA_ID, ctlvs);
 10472     if (ctlv) {
 10473       browser.confirmMessage = ctlv.value.identifier;
 10476     browser.mode = cmdDetails.commandQualifier & 0x03;
 10478     return browser;
 10479   },
 10481   processPlayTone: function(cmdDetails, ctlvs) {
 10482     let StkProactiveCmdHelper = this.context.StkProactiveCmdHelper;
 10483     let playTone = {};
 10485     let ctlv = StkProactiveCmdHelper.searchForTag(
 10486         COMPREHENSIONTLV_TAG_ALPHA_ID, ctlvs);
 10487     if (ctlv) {
 10488       playTone.text = ctlv.value.identifier;
 10491     ctlv = StkProactiveCmdHelper.searchForTag(COMPREHENSIONTLV_TAG_TONE, ctlvs);
 10492     if (ctlv) {
 10493       playTone.tone = ctlv.value.tone;
 10496     ctlv = StkProactiveCmdHelper.searchForTag(
 10497         COMPREHENSIONTLV_TAG_DURATION, ctlvs);
 10498     if (ctlv) {
 10499       playTone.duration = ctlv.value;
 10502     // vibrate is only defined in TS 102.223
 10503     playTone.isVibrate = (cmdDetails.commandQualifier & 0x01) !== 0x00;
 10505     return playTone;
 10506   },
 10508   /**
 10509    * Construct a param for Provide Local Information
 10511    * @param cmdDetails
 10512    *        The value object of CommandDetails TLV.
 10513    * @param ctlvs
 10514    *        The all TLVs in this proactive command.
 10515    */
 10516   processProvideLocalInfo: function(cmdDetails, ctlvs) {
 10517     let provideLocalInfo = {
 10518       localInfoType: cmdDetails.commandQualifier
 10519     };
 10520     return provideLocalInfo;
 10521   },
 10523   processTimerManagement: function(cmdDetails, ctlvs) {
 10524     let StkProactiveCmdHelper = this.context.StkProactiveCmdHelper;
 10525     let timer = {
 10526       timerAction: cmdDetails.commandQualifier
 10527     };
 10529     let ctlv = StkProactiveCmdHelper.searchForTag(
 10530         COMPREHENSIONTLV_TAG_TIMER_IDENTIFIER, ctlvs);
 10531     if (ctlv) {
 10532       timer.timerId = ctlv.value.timerId;
 10535     ctlv = StkProactiveCmdHelper.searchForTag(
 10536         COMPREHENSIONTLV_TAG_TIMER_VALUE, ctlvs);
 10537     if (ctlv) {
 10538       timer.timerValue = ctlv.value.timerValue;
 10541     return timer;
 10542   },
 10544    /**
 10545     * Construct a param for BIP commands.
 10547     * @param cmdDetails
 10548     *        The value object of CommandDetails TLV.
 10549     * @param ctlvs
 10550     *        The all TLVs in this proactive command.
 10551     */
 10552   processBipMessage: function(cmdDetails, ctlvs) {
 10553     let bipMsg = {};
 10555     let ctlv = this.context.StkProactiveCmdHelper.searchForTag(
 10556         COMPREHENSIONTLV_TAG_ALPHA_ID, ctlvs);
 10557     if (ctlv) {
 10558       bipMsg.text = ctlv.value.identifier;
 10561     return bipMsg;
 10563 };
 10564 StkCommandParamsFactoryObject.prototype[STK_CMD_REFRESH] = function STK_CMD_REFRESH(cmdDetails, ctlvs) {
 10565   return this.processRefresh(cmdDetails, ctlvs);
 10566 };
 10567 StkCommandParamsFactoryObject.prototype[STK_CMD_POLL_INTERVAL] = function STK_CMD_POLL_INTERVAL(cmdDetails, ctlvs) {
 10568   return this.processPollInterval(cmdDetails, ctlvs);
 10569 };
 10570 StkCommandParamsFactoryObject.prototype[STK_CMD_POLL_OFF] = function STK_CMD_POLL_OFF(cmdDetails, ctlvs) {
 10571   return this.processPollOff(cmdDetails, ctlvs);
 10572 };
 10573 StkCommandParamsFactoryObject.prototype[STK_CMD_PROVIDE_LOCAL_INFO] = function STK_CMD_PROVIDE_LOCAL_INFO(cmdDetails, ctlvs) {
 10574   return this.processProvideLocalInfo(cmdDetails, ctlvs);
 10575 };
 10576 StkCommandParamsFactoryObject.prototype[STK_CMD_SET_UP_EVENT_LIST] = function STK_CMD_SET_UP_EVENT_LIST(cmdDetails, ctlvs) {
 10577   return this.processSetUpEventList(cmdDetails, ctlvs);
 10578 };
 10579 StkCommandParamsFactoryObject.prototype[STK_CMD_SET_UP_MENU] = function STK_CMD_SET_UP_MENU(cmdDetails, ctlvs) {
 10580   return this.processSelectItem(cmdDetails, ctlvs);
 10581 };
 10582 StkCommandParamsFactoryObject.prototype[STK_CMD_SELECT_ITEM] = function STK_CMD_SELECT_ITEM(cmdDetails, ctlvs) {
 10583   return this.processSelectItem(cmdDetails, ctlvs);
 10584 };
 10585 StkCommandParamsFactoryObject.prototype[STK_CMD_DISPLAY_TEXT] = function STK_CMD_DISPLAY_TEXT(cmdDetails, ctlvs) {
 10586   return this.processDisplayText(cmdDetails, ctlvs);
 10587 };
 10588 StkCommandParamsFactoryObject.prototype[STK_CMD_SET_UP_IDLE_MODE_TEXT] = function STK_CMD_SET_UP_IDLE_MODE_TEXT(cmdDetails, ctlvs) {
 10589   return this.processSetUpIdleModeText(cmdDetails, ctlvs);
 10590 };
 10591 StkCommandParamsFactoryObject.prototype[STK_CMD_GET_INKEY] = function STK_CMD_GET_INKEY(cmdDetails, ctlvs) {
 10592   return this.processGetInkey(cmdDetails, ctlvs);
 10593 };
 10594 StkCommandParamsFactoryObject.prototype[STK_CMD_GET_INPUT] = function STK_CMD_GET_INPUT(cmdDetails, ctlvs) {
 10595   return this.processGetInput(cmdDetails, ctlvs);
 10596 };
 10597 StkCommandParamsFactoryObject.prototype[STK_CMD_SEND_SS] = function STK_CMD_SEND_SS(cmdDetails, ctlvs) {
 10598   return this.processEventNotify(cmdDetails, ctlvs);
 10599 };
 10600 StkCommandParamsFactoryObject.prototype[STK_CMD_SEND_USSD] = function STK_CMD_SEND_USSD(cmdDetails, ctlvs) {
 10601   return this.processEventNotify(cmdDetails, ctlvs);
 10602 };
 10603 StkCommandParamsFactoryObject.prototype[STK_CMD_SEND_SMS] = function STK_CMD_SEND_SMS(cmdDetails, ctlvs) {
 10604   return this.processEventNotify(cmdDetails, ctlvs);
 10605 };
 10606 StkCommandParamsFactoryObject.prototype[STK_CMD_SEND_DTMF] = function STK_CMD_SEND_DTMF(cmdDetails, ctlvs) {
 10607   return this.processEventNotify(cmdDetails, ctlvs);
 10608 };
 10609 StkCommandParamsFactoryObject.prototype[STK_CMD_SET_UP_CALL] = function STK_CMD_SET_UP_CALL(cmdDetails, ctlvs) {
 10610   return this.processSetupCall(cmdDetails, ctlvs);
 10611 };
 10612 StkCommandParamsFactoryObject.prototype[STK_CMD_LAUNCH_BROWSER] = function STK_CMD_LAUNCH_BROWSER(cmdDetails, ctlvs) {
 10613   return this.processLaunchBrowser(cmdDetails, ctlvs);
 10614 };
 10615 StkCommandParamsFactoryObject.prototype[STK_CMD_PLAY_TONE] = function STK_CMD_PLAY_TONE(cmdDetails, ctlvs) {
 10616   return this.processPlayTone(cmdDetails, ctlvs);
 10617 };
 10618 StkCommandParamsFactoryObject.prototype[STK_CMD_TIMER_MANAGEMENT] = function STK_CMD_TIMER_MANAGEMENT(cmdDetails, ctlvs) {
 10619   return this.processTimerManagement(cmdDetails, ctlvs);
 10620 };
 10621 StkCommandParamsFactoryObject.prototype[STK_CMD_OPEN_CHANNEL] = function STK_CMD_OPEN_CHANNEL(cmdDetails, ctlvs) {
 10622   return this.processBipMessage(cmdDetails, ctlvs);
 10623 };
 10624 StkCommandParamsFactoryObject.prototype[STK_CMD_CLOSE_CHANNEL] = function STK_CMD_CLOSE_CHANNEL(cmdDetails, ctlvs) {
 10625   return this.processBipMessage(cmdDetails, ctlvs);
 10626 };
 10627 StkCommandParamsFactoryObject.prototype[STK_CMD_RECEIVE_DATA] = function STK_CMD_RECEIVE_DATA(cmdDetails, ctlvs) {
 10628   return this.processBipMessage(cmdDetails, ctlvs);
 10629 };
 10630 StkCommandParamsFactoryObject.prototype[STK_CMD_SEND_DATA] = function STK_CMD_SEND_DATA(cmdDetails, ctlvs) {
 10631   return this.processBipMessage(cmdDetails, ctlvs);
 10632 };
 10634 function StkProactiveCmdHelperObject(aContext) {
 10635   this.context = aContext;
 10637 StkProactiveCmdHelperObject.prototype = {
 10638   context: null,
 10640   retrieve: function(tag, length) {
 10641     let method = this[tag];
 10642     if (typeof method != "function") {
 10643       if (DEBUG) {
 10644         this.context.debug("Unknown comprehension tag " + tag.toString(16));
 10646       let Buf = this.context.Buf;
 10647       Buf.seekIncoming(length * Buf.PDU_HEX_OCTET_SIZE);
 10648       return null;
 10650     return method.call(this, length);
 10651   },
 10653   /**
 10654    * Command Details.
 10656    * | Byte | Description         | Length |
 10657    * |  1   | Command details Tag |   1    |
 10658    * |  2   | Length = 03         |   1    |
 10659    * |  3   | Command number      |   1    |
 10660    * |  4   | Type of Command     |   1    |
 10661    * |  5   | Command Qualifier   |   1    |
 10662    */
 10663   retrieveCommandDetails: function(length) {
 10664     let GsmPDUHelper = this.context.GsmPDUHelper;
 10665     let cmdDetails = {
 10666       commandNumber: GsmPDUHelper.readHexOctet(),
 10667       typeOfCommand: GsmPDUHelper.readHexOctet(),
 10668       commandQualifier: GsmPDUHelper.readHexOctet()
 10669     };
 10670     return cmdDetails;
 10671   },
 10673   /**
 10674    * Device Identities.
 10676    * | Byte | Description            | Length |
 10677    * |  1   | Device Identity Tag    |   1    |
 10678    * |  2   | Length = 02            |   1    |
 10679    * |  3   | Source device Identity |   1    |
 10680    * |  4   | Destination device Id  |   1    |
 10681    */
 10682   retrieveDeviceId: function(length) {
 10683     let GsmPDUHelper = this.context.GsmPDUHelper;
 10684     let deviceId = {
 10685       sourceId: GsmPDUHelper.readHexOctet(),
 10686       destinationId: GsmPDUHelper.readHexOctet()
 10687     };
 10688     return deviceId;
 10689   },
 10691   /**
 10692    * Alpha Identifier.
 10694    * | Byte         | Description            | Length |
 10695    * |  1           | Alpha Identifier Tag   |   1    |
 10696    * | 2 ~ (Y-1)+2  | Length (X)             |   Y    |
 10697    * | (Y-1)+3 ~    | Alpha identfier        |   X    |
 10698    * | (Y-1)+X+2    |                        |        |
 10699    */
 10700   retrieveAlphaId: function(length) {
 10701     let alphaId = {
 10702       identifier: this.context.ICCPDUHelper.readAlphaIdentifier(length)
 10703     };
 10704     return alphaId;
 10705   },
 10707   /**
 10708    * Duration.
 10710    * | Byte | Description           | Length |
 10711    * |  1   | Response Length Tag   |   1    |
 10712    * |  2   | Lenth = 02            |   1    |
 10713    * |  3   | Time unit             |   1    |
 10714    * |  4   | Time interval         |   1    |
 10715    */
 10716   retrieveDuration: function(length) {
 10717     let GsmPDUHelper = this.context.GsmPDUHelper;
 10718     let duration = {
 10719       timeUnit: GsmPDUHelper.readHexOctet(),
 10720       timeInterval: GsmPDUHelper.readHexOctet(),
 10721     };
 10722     return duration;
 10723   },
 10725   /**
 10726    * Address.
 10728    * | Byte         | Description            | Length |
 10729    * |  1           | Alpha Identifier Tag   |   1    |
 10730    * | 2 ~ (Y-1)+2  | Length (X)             |   Y    |
 10731    * | (Y-1)+3      | TON and NPI            |   1    |
 10732    * | (Y-1)+4 ~    | Dialling number        |   X    |
 10733    * | (Y-1)+X+2    |                        |        |
 10734    */
 10735   retrieveAddress: function(length) {
 10736     let address = {
 10737       number : this.context.ICCPDUHelper.readDiallingNumber(length)
 10738     };
 10739     return address;
 10740   },
 10742   /**
 10743    * Text String.
 10745    * | Byte         | Description        | Length |
 10746    * |  1           | Text String Tag    |   1    |
 10747    * | 2 ~ (Y-1)+2  | Length (X)         |   Y    |
 10748    * | (Y-1)+3      | Data coding scheme |   1    |
 10749    * | (Y-1)+4~     | Text String        |   X    |
 10750    * | (Y-1)+X+2    |                    |        |
 10751    */
 10752   retrieveTextString: function(length) {
 10753     if (!length) {
 10754       // null string.
 10755       return {textString: null};
 10758     let GsmPDUHelper = this.context.GsmPDUHelper;
 10759     let text = {
 10760       codingScheme: GsmPDUHelper.readHexOctet()
 10761     };
 10763     length--; // -1 for the codingScheme.
 10764     switch (text.codingScheme & 0x0f) {
 10765       case STK_TEXT_CODING_GSM_7BIT_PACKED:
 10766         text.textString = GsmPDUHelper.readSeptetsToString(length * 8 / 7, 0, 0, 0);
 10767         break;
 10768       case STK_TEXT_CODING_GSM_8BIT:
 10769         text.textString =
 10770           this.context.ICCPDUHelper.read8BitUnpackedToString(length);
 10771         break;
 10772       case STK_TEXT_CODING_UCS2:
 10773         text.textString = GsmPDUHelper.readUCS2String(length);
 10774         break;
 10776     return text;
 10777   },
 10779   /**
 10780    * Tone.
 10782    * | Byte | Description     | Length |
 10783    * |  1   | Tone Tag        |   1    |
 10784    * |  2   | Lenth = 01      |   1    |
 10785    * |  3   | Tone            |   1    |
 10786    */
 10787   retrieveTone: function(length) {
 10788     let tone = {
 10789       tone: this.context.GsmPDUHelper.readHexOctet(),
 10790     };
 10791     return tone;
 10792   },
 10794   /**
 10795    * Item.
 10797    * | Byte         | Description            | Length |
 10798    * |  1           | Item Tag               |   1    |
 10799    * | 2 ~ (Y-1)+2  | Length (X)             |   Y    |
 10800    * | (Y-1)+3      | Identifier of item     |   1    |
 10801    * | (Y-1)+4 ~    | Text string of item    |   X    |
 10802    * | (Y-1)+X+2    |                        |        |
 10803    */
 10804   retrieveItem: function(length) {
 10805     // TS 102.223 ,clause 6.6.7 SET-UP MENU
 10806     // If the "Item data object for item 1" is a null data object
 10807     // (i.e. length = '00' and no value part), this is an indication to the ME
 10808     // to remove the existing menu from the menu system in the ME.
 10809     if (!length) {
 10810       return null;
 10812     let item = {
 10813       identifier: this.context.GsmPDUHelper.readHexOctet(),
 10814       text: this.context.ICCPDUHelper.readAlphaIdentifier(length - 1)
 10815     };
 10816     return item;
 10817   },
 10819   /**
 10820    * Item Identifier.
 10822    * | Byte | Description               | Length |
 10823    * |  1   | Item Identifier Tag       |   1    |
 10824    * |  2   | Lenth = 01                |   1    |
 10825    * |  3   | Identifier of Item chosen |   1    |
 10826    */
 10827   retrieveItemId: function(length) {
 10828     let itemId = {
 10829       identifier: this.context.GsmPDUHelper.readHexOctet()
 10830     };
 10831     return itemId;
 10832   },
 10834   /**
 10835    * Response Length.
 10837    * | Byte | Description                | Length |
 10838    * |  1   | Response Length Tag        |   1    |
 10839    * |  2   | Lenth = 02                 |   1    |
 10840    * |  3   | Minimum length of response |   1    |
 10841    * |  4   | Maximum length of response |   1    |
 10842    */
 10843   retrieveResponseLength: function(length) {
 10844     let GsmPDUHelper = this.context.GsmPDUHelper;
 10845     let rspLength = {
 10846       minLength : GsmPDUHelper.readHexOctet(),
 10847       maxLength : GsmPDUHelper.readHexOctet()
 10848     };
 10849     return rspLength;
 10850   },
 10852   /**
 10853    * File List.
 10855    * | Byte         | Description            | Length |
 10856    * |  1           | File List Tag          |   1    |
 10857    * | 2 ~ (Y-1)+2  | Length (X)             |   Y    |
 10858    * | (Y-1)+3      | Number of files        |   1    |
 10859    * | (Y-1)+4 ~    | Files                  |   X    |
 10860    * | (Y-1)+X+2    |                        |        |
 10861    */
 10862   retrieveFileList: function(length) {
 10863     let num = this.context.GsmPDUHelper.readHexOctet();
 10864     let fileList = "";
 10865     length--; // -1 for the num octet.
 10866     for (let i = 0; i < 2 * length; i++) {
 10867       // Didn't use readHexOctet here,
 10868       // otherwise 0x00 will be "0", not "00"
 10869       fileList += String.fromCharCode(this.context.Buf.readUint16());
 10871     return {
 10872       fileList: fileList
 10873     };
 10874   },
 10876   /**
 10877    * Default Text.
 10879    * Same as Text String.
 10880    */
 10881   retrieveDefaultText: function(length) {
 10882     return this.retrieveTextString(length);
 10883   },
 10885   /**
 10886    * Event List.
 10887    */
 10888   retrieveEventList: function(length) {
 10889     if (!length) {
 10890       // null means an indication to ME to remove the existing list of events
 10891       // in ME.
 10892       return null;
 10895     let GsmPDUHelper = this.context.GsmPDUHelper;
 10896     let eventList = [];
 10897     for (let i = 0; i < length; i++) {
 10898       eventList.push(GsmPDUHelper.readHexOctet());
 10900     return {
 10901       eventList: eventList
 10902     };
 10903   },
 10905   /**
 10906    * Timer Identifier.
 10908    * | Byte  | Description          | Length |
 10909    * |  1    | Timer Identifier Tag |   1    |
 10910    * |  2    | Length = 01          |   1    |
 10911    * |  3    | Timer Identifier     |   1    |
 10912    */
 10913   retrieveTimerId: function(length) {
 10914     let id = {
 10915       timerId: this.context.GsmPDUHelper.readHexOctet()
 10916     };
 10917     return id;
 10918   },
 10920   /**
 10921    * Timer Value.
 10923    * | Byte  | Description          | Length |
 10924    * |  1    | Timer Value Tag      |   1    |
 10925    * |  2    | Length = 03          |   1    |
 10926    * |  3    | Hour                 |   1    |
 10927    * |  4    | Minute               |   1    |
 10928    * |  5    | Second               |   1    |
 10929    */
 10930   retrieveTimerValue: function(length) {
 10931     let GsmPDUHelper = this.context.GsmPDUHelper;
 10932     let value = {
 10933       timerValue: (GsmPDUHelper.readSwappedNibbleBcdNum(1) * 60 * 60) +
 10934                   (GsmPDUHelper.readSwappedNibbleBcdNum(1) * 60) +
 10935                   (GsmPDUHelper.readSwappedNibbleBcdNum(1))
 10936     };
 10937     return value;
 10938   },
 10940   /**
 10941    * Immediate Response.
 10943    * | Byte  | Description            | Length |
 10944    * |  1    | Immediate Response Tag |   1    |
 10945    * |  2    | Length = 00            |   1    |
 10946    */
 10947   retrieveImmediaResponse: function(length) {
 10948     return {};
 10949   },
 10951   /**
 10952    * URL
 10954    * | Byte      | Description         | Length |
 10955    * |  1        | URL Tag             |   1    |
 10956    * | 2 ~ (Y+1) | Length(X)           |   Y    |
 10957    * | (Y+2) ~   | URL                 |   X    |
 10958    * | (Y+1+X)   |                     |        |
 10959    */
 10960   retrieveUrl: function(length) {
 10961     let GsmPDUHelper = this.context.GsmPDUHelper;
 10962     let s = "";
 10963     for (let i = 0; i < length; i++) {
 10964       s += String.fromCharCode(GsmPDUHelper.readHexOctet());
 10966     return {url: s};
 10967   },
 10969   /**
 10970    * Next Action Indicator List.
 10972    * | Byte  | Description      | Length |
 10973    * |  1    | Next Action tag  |   1    |
 10974    * |  1    | Length(X)        |   1    |
 10975    * |  3~   | Next Action List |   X    |
 10976    * | 3+X-1 |                  |        |
 10977    */
 10978   retrieveNextActionList: function(length) {
 10979     let GsmPDUHelper = this.context.GsmPDUHelper;
 10980     let nextActionList = [];
 10981     for (let i = 0; i < length; i++) {
 10982       nextActionList.push(GsmPDUHelper.readHexOctet());
 10984     return nextActionList;
 10985   },
 10987   searchForTag: function(tag, ctlvs) {
 10988     let iter = Iterator(ctlvs);
 10989     return this.searchForNextTag(tag, iter);
 10990   },
 10992   searchForNextTag: function(tag, iter) {
 10993     for (let [index, ctlv] in iter) {
 10994       if ((ctlv.tag & ~COMPREHENSIONTLV_FLAG_CR) == tag) {
 10995         return ctlv;
 10998     return null;
 10999   },
 11000 };
 11001 StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_COMMAND_DETAILS] = function COMPREHENSIONTLV_TAG_COMMAND_DETAILS(length) {
 11002   return this.retrieveCommandDetails(length);
 11003 };
 11004 StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_DEVICE_ID] = function COMPREHENSIONTLV_TAG_DEVICE_ID(length) {
 11005   return this.retrieveDeviceId(length);
 11006 };
 11007 StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_ALPHA_ID] = function COMPREHENSIONTLV_TAG_ALPHA_ID(length) {
 11008   return this.retrieveAlphaId(length);
 11009 };
 11010 StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_DURATION] = function COMPREHENSIONTLV_TAG_DURATION(length) {
 11011   return this.retrieveDuration(length);
 11012 };
 11013 StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_ADDRESS] = function COMPREHENSIONTLV_TAG_ADDRESS(length) {
 11014   return this.retrieveAddress(length);
 11015 };
 11016 StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_TEXT_STRING] = function COMPREHENSIONTLV_TAG_TEXT_STRING(length) {
 11017   return this.retrieveTextString(length);
 11018 };
 11019 StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_TONE] = function COMPREHENSIONTLV_TAG_TONE(length) {
 11020   return this.retrieveTone(length);
 11021 };
 11022 StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_ITEM] = function COMPREHENSIONTLV_TAG_ITEM(length) {
 11023   return this.retrieveItem(length);
 11024 };
 11025 StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_ITEM_ID] = function COMPREHENSIONTLV_TAG_ITEM_ID(length) {
 11026   return this.retrieveItemId(length);
 11027 };
 11028 StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_RESPONSE_LENGTH] = function COMPREHENSIONTLV_TAG_RESPONSE_LENGTH(length) {
 11029   return this.retrieveResponseLength(length);
 11030 };
 11031 StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_FILE_LIST] = function COMPREHENSIONTLV_TAG_FILE_LIST(length) {
 11032   return this.retrieveFileList(length);
 11033 };
 11034 StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_DEFAULT_TEXT] = function COMPREHENSIONTLV_TAG_DEFAULT_TEXT(length) {
 11035   return this.retrieveDefaultText(length);
 11036 };
 11037 StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_EVENT_LIST] = function COMPREHENSIONTLV_TAG_EVENT_LIST(length) {
 11038   return this.retrieveEventList(length);
 11039 };
 11040 StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_TIMER_IDENTIFIER] = function COMPREHENSIONTLV_TAG_TIMER_IDENTIFIER(length) {
 11041   return this.retrieveTimerId(length);
 11042 };
 11043 StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_TIMER_VALUE] = function COMPREHENSIONTLV_TAG_TIMER_VALUE(length) {
 11044   return this.retrieveTimerValue(length);
 11045 };
 11046 StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_IMMEDIATE_RESPONSE] = function COMPREHENSIONTLV_TAG_IMMEDIATE_RESPONSE(length) {
 11047   return this.retrieveImmediaResponse(length);
 11048 };
 11049 StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_URL] = function COMPREHENSIONTLV_TAG_URL(length) {
 11050   return this.retrieveUrl(length);
 11051 };
 11052 StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_NEXT_ACTION_IND] = function COMPREHENSIONTLV_TAG_NEXT_ACTION_IND(length) {
 11053   return this.retrieveNextActionList(length);
 11054 };
 11056 function ComprehensionTlvHelperObject(aContext) {
 11057   this.context = aContext;
 11059 ComprehensionTlvHelperObject.prototype = {
 11060   context: null,
 11062   /**
 11063    * Decode raw data to a Comprehension-TLV.
 11064    */
 11065   decode: function() {
 11066     let GsmPDUHelper = this.context.GsmPDUHelper;
 11068     let hlen = 0; // For header(tag field + length field) length.
 11069     let temp = GsmPDUHelper.readHexOctet();
 11070     hlen++;
 11072     // TS 101.220, clause 7.1.1
 11073     let tag, cr;
 11074     switch (temp) {
 11075       // TS 101.220, clause 7.1.1
 11076       case 0x0: // Not used.
 11077       case 0xff: // Not used.
 11078       case 0x80: // Reserved for future use.
 11079         throw new Error("Invalid octet when parsing Comprehension TLV :" + temp);
 11080       case 0x7f: // Tag is three byte format.
 11081         // TS 101.220 clause 7.1.1.2.
 11082         // | Byte 1 | Byte 2                        | Byte 3 |
 11083         // |        | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 |        |
 11084         // | 0x7f   |CR | Tag Value                          |
 11085         tag = (GsmPDUHelper.readHexOctet() << 8) | GsmPDUHelper.readHexOctet();
 11086         hlen += 2;
 11087         cr = (tag & 0x8000) !== 0;
 11088         tag &= ~0x8000;
 11089         break;
 11090       default: // Tag is single byte format.
 11091         tag = temp;
 11092         // TS 101.220 clause 7.1.1.1.
 11093         // | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 |
 11094         // |CR | Tag Value                 |
 11095         cr = (tag & 0x80) !== 0;
 11096         tag &= ~0x80;
 11099     // TS 101.220 clause 7.1.2, Length Encoding.
 11100     // Length   |  Byte 1  | Byte 2 | Byte 3 | Byte 4 |
 11101     // 0 - 127  |  00 - 7f | N/A    | N/A    | N/A    |
 11102     // 128-255  |  81      | 80 - ff| N/A    | N/A    |
 11103     // 256-65535|  82      | 0100 - ffff     | N/A    |
 11104     // 65536-   |  83      |     010000 - ffffff      |
 11105     // 16777215
 11106     //
 11107     // Length errors: TS 11.14, clause 6.10.6
 11109     let length; // Data length.
 11110     temp = GsmPDUHelper.readHexOctet();
 11111     hlen++;
 11112     if (temp < 0x80) {
 11113       length = temp;
 11114     } else if (temp == 0x81) {
 11115       length = GsmPDUHelper.readHexOctet();
 11116       hlen++;
 11117       if (length < 0x80) {
 11118         throw new Error("Invalid length in Comprehension TLV :" + length);
 11120     } else if (temp == 0x82) {
 11121       length = (GsmPDUHelper.readHexOctet() << 8) | GsmPDUHelper.readHexOctet();
 11122       hlen += 2;
 11123       if (lenth < 0x0100) {
 11124         throw new Error("Invalid length in 3-byte Comprehension TLV :" + length);
 11126     } else if (temp == 0x83) {
 11127       length = (GsmPDUHelper.readHexOctet() << 16) |
 11128                (GsmPDUHelper.readHexOctet() << 8)  |
 11129                 GsmPDUHelper.readHexOctet();
 11130       hlen += 3;
 11131       if (length < 0x010000) {
 11132         throw new Error("Invalid length in 4-byte Comprehension TLV :" + length);
 11134     } else {
 11135       throw new Error("Invalid octet in Comprehension TLV :" + temp);
 11138     let ctlv = {
 11139       tag: tag,
 11140       length: length,
 11141       value: this.context.StkProactiveCmdHelper.retrieve(tag, length),
 11142       cr: cr,
 11143       hlen: hlen
 11144     };
 11145     return ctlv;
 11146   },
 11148   decodeChunks: function(length) {
 11149     let chunks = [];
 11150     let index = 0;
 11151     while (index < length) {
 11152       let tlv = this.decode();
 11153       chunks.push(tlv);
 11154       index += tlv.length;
 11155       index += tlv.hlen;
 11157     return chunks;
 11158   },
 11160   /**
 11161    * Write Location Info Comprehension TLV.
 11163    * @param loc location Information.
 11164    */
 11165   writeLocationInfoTlv: function(loc) {
 11166     let GsmPDUHelper = this.context.GsmPDUHelper;
 11168     GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_LOCATION_INFO |
 11169                                COMPREHENSIONTLV_FLAG_CR);
 11170     GsmPDUHelper.writeHexOctet(loc.gsmCellId > 0xffff ? 9 : 7);
 11171     // From TS 11.14, clause 12.19
 11172     // "The mobile country code (MCC), the mobile network code (MNC),
 11173     // the location area code (LAC) and the cell ID are
 11174     // coded as in TS 04.08."
 11175     // And from TS 04.08 and TS 24.008,
 11176     // the format is as follows:
 11177     //
 11178     // MCC = MCC_digit_1 + MCC_digit_2 + MCC_digit_3
 11179     //
 11180     //   8  7  6  5    4  3  2  1
 11181     // +-------------+-------------+
 11182     // | MCC digit 2 | MCC digit 1 | octet 2
 11183     // | MNC digit 3 | MCC digit 3 | octet 3
 11184     // | MNC digit 2 | MNC digit 1 | octet 4
 11185     // +-------------+-------------+
 11186     //
 11187     // Also in TS 24.008
 11188     // "However a network operator may decide to
 11189     // use only two digits in the MNC in the LAI over the
 11190     // radio interface. In this case, bits 5 to 8 of octet 3
 11191     // shall be coded as '1111'".
 11193     // MCC & MNC, 3 octets
 11194     let mcc = loc.mcc, mnc;
 11195     if (loc.mnc.length == 2) {
 11196       mnc = "F" + loc.mnc;
 11197     } else {
 11198       mnc = loc.mnc[2] + loc.mnc[0] + loc.mnc[1];
 11200     GsmPDUHelper.writeSwappedNibbleBCD(mcc + mnc);
 11202     // LAC, 2 octets
 11203     GsmPDUHelper.writeHexOctet((loc.gsmLocationAreaCode >> 8) & 0xff);
 11204     GsmPDUHelper.writeHexOctet(loc.gsmLocationAreaCode & 0xff);
 11206     // Cell Id
 11207     if (loc.gsmCellId > 0xffff) {
 11208       // UMTS/WCDMA, gsmCellId is 28 bits.
 11209       GsmPDUHelper.writeHexOctet((loc.gsmCellId >> 24) & 0xff);
 11210       GsmPDUHelper.writeHexOctet((loc.gsmCellId >> 16) & 0xff);
 11211       GsmPDUHelper.writeHexOctet((loc.gsmCellId >> 8) & 0xff);
 11212       GsmPDUHelper.writeHexOctet(loc.gsmCellId & 0xff);
 11213     } else {
 11214       // GSM, gsmCellId is 16 bits.
 11215       GsmPDUHelper.writeHexOctet((loc.gsmCellId >> 8) & 0xff);
 11216       GsmPDUHelper.writeHexOctet(loc.gsmCellId & 0xff);
 11218   },
 11220   /**
 11221    * Given a geckoError string, this function translates it into cause value
 11222    * and write the value into buffer.
 11224    * @param geckoError Error string that is passed to gecko.
 11225    */
 11226   writeCauseTlv: function(geckoError) {
 11227     let GsmPDUHelper = this.context.GsmPDUHelper;
 11229     let cause = -1;
 11230     for (let errorNo in RIL_ERROR_TO_GECKO_ERROR) {
 11231       if (geckoError == RIL_ERROR_TO_GECKO_ERROR[errorNo]) {
 11232         cause = errorNo;
 11233         break;
 11236     cause = (cause == -1) ? ERROR_SUCCESS : cause;
 11238     GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_CAUSE |
 11239                                COMPREHENSIONTLV_FLAG_CR);
 11240     GsmPDUHelper.writeHexOctet(2);  // For single cause value.
 11242     // TS 04.08, clause 10.5.4.11: National standard code + user location.
 11243     GsmPDUHelper.writeHexOctet(0x60);
 11245     // TS 04.08, clause 10.5.4.11: ext bit = 1 + 7 bits for cause.
 11246     // +-----------------+----------------------------------+
 11247     // | Ext = 1 (1 bit) |          Cause (7 bits)          |
 11248     // +-----------------+----------------------------------+
 11249     GsmPDUHelper.writeHexOctet(0x80 | cause);
 11250   },
 11252   writeDateTimeZoneTlv: function(date) {
 11253     let GsmPDUHelper = this.context.GsmPDUHelper;
 11255     GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_DATE_TIME_ZONE);
 11256     GsmPDUHelper.writeHexOctet(7);
 11257     GsmPDUHelper.writeTimestamp(date);
 11258   },
 11260   writeLanguageTlv: function(language) {
 11261     let GsmPDUHelper = this.context.GsmPDUHelper;
 11263     GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_LANGUAGE);
 11264     GsmPDUHelper.writeHexOctet(2);
 11266     // ISO 639-1, Alpha-2 code
 11267     // TS 123.038, clause 6.2.1, GSM 7 bit Default Alphabet
 11268     GsmPDUHelper.writeHexOctet(
 11269       PDU_NL_LOCKING_SHIFT_TABLES[PDU_NL_IDENTIFIER_DEFAULT].indexOf(language[0]));
 11270     GsmPDUHelper.writeHexOctet(
 11271       PDU_NL_LOCKING_SHIFT_TABLES[PDU_NL_IDENTIFIER_DEFAULT].indexOf(language[1]));
 11272   },
 11274   /**
 11275    * Write Timer Value Comprehension TLV.
 11277    * @param seconds length of time during of the timer.
 11278    * @param cr Comprehension Required or not
 11279    */
 11280   writeTimerValueTlv: function(seconds, cr) {
 11281     let GsmPDUHelper = this.context.GsmPDUHelper;
 11283     GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_TIMER_VALUE |
 11284                                (cr ? COMPREHENSIONTLV_FLAG_CR : 0));
 11285     GsmPDUHelper.writeHexOctet(3);
 11287     // TS 102.223, clause 8.38
 11288     // +----------------+------------------+-------------------+
 11289     // | hours (1 byte) | minutes (1 btye) | secounds (1 byte) |
 11290     // +----------------+------------------+-------------------+
 11291     GsmPDUHelper.writeSwappedNibbleBCDNum(Math.floor(seconds / 60 / 60));
 11292     GsmPDUHelper.writeSwappedNibbleBCDNum(Math.floor(seconds / 60) % 60);
 11293     GsmPDUHelper.writeSwappedNibbleBCDNum(seconds % 60);
 11294   },
 11296   getSizeOfLengthOctets: function(length) {
 11297     if (length >= 0x10000) {
 11298       return 4; // 0x83, len_1, len_2, len_3
 11299     } else if (length >= 0x100) {
 11300       return 3; // 0x82, len_1, len_2
 11301     } else if (length >= 0x80) {
 11302       return 2; // 0x81, len
 11303     } else {
 11304       return 1; // len
 11306   },
 11308   writeLength: function(length) {
 11309     let GsmPDUHelper = this.context.GsmPDUHelper;
 11311     // TS 101.220 clause 7.1.2, Length Encoding.
 11312     // Length   |  Byte 1  | Byte 2 | Byte 3 | Byte 4 |
 11313     // 0 - 127  |  00 - 7f | N/A    | N/A    | N/A    |
 11314     // 128-255  |  81      | 80 - ff| N/A    | N/A    |
 11315     // 256-65535|  82      | 0100 - ffff     | N/A    |
 11316     // 65536-   |  83      |     010000 - ffffff      |
 11317     // 16777215
 11318     if (length < 0x80) {
 11319       GsmPDUHelper.writeHexOctet(length);
 11320     } else if (0x80 <= length && length < 0x100) {
 11321       GsmPDUHelper.writeHexOctet(0x81);
 11322       GsmPDUHelper.writeHexOctet(length);
 11323     } else if (0x100 <= length && length < 0x10000) {
 11324       GsmPDUHelper.writeHexOctet(0x82);
 11325       GsmPDUHelper.writeHexOctet((length >> 8) & 0xff);
 11326       GsmPDUHelper.writeHexOctet(length & 0xff);
 11327     } else if (0x10000 <= length && length < 0x1000000) {
 11328       GsmPDUHelper.writeHexOctet(0x83);
 11329       GsmPDUHelper.writeHexOctet((length >> 16) & 0xff);
 11330       GsmPDUHelper.writeHexOctet((length >> 8) & 0xff);
 11331       GsmPDUHelper.writeHexOctet(length & 0xff);
 11332     } else {
 11333       throw new Error("Invalid length value :" + length);
 11335   },
 11336 };
 11338 function BerTlvHelperObject(aContext) {
 11339   this.context = aContext;
 11341 BerTlvHelperObject.prototype = {
 11342   context: null,
 11344   /**
 11345    * Decode Ber TLV.
 11347    * @param dataLen
 11348    *        The length of data in bytes.
 11349    */
 11350   decode: function(dataLen) {
 11351     let GsmPDUHelper = this.context.GsmPDUHelper;
 11353     let hlen = 0;
 11354     let tag = GsmPDUHelper.readHexOctet();
 11355     hlen++;
 11357     // The length is coded onto 1 or 2 bytes.
 11358     // Length    | Byte 1                | Byte 2
 11359     // 0 - 127   | length ('00' to '7f') | N/A
 11360     // 128 - 255 | '81'                  | length ('80' to 'ff')
 11361     let length;
 11362     let temp = GsmPDUHelper.readHexOctet();
 11363     hlen++;
 11364     if (temp < 0x80) {
 11365       length = temp;
 11366     } else if (temp === 0x81) {
 11367       length = GsmPDUHelper.readHexOctet();
 11368       hlen++;
 11369       if (length < 0x80) {
 11370         throw new Error("Invalid length " + length);
 11372     } else {
 11373       throw new Error("Invalid length octet " + temp);
 11376     // Header + body length check.
 11377     if (dataLen - hlen !== length) {
 11378       throw new Error("Unexpected BerTlvHelper value length!!");
 11381     let method = this[tag];
 11382     if (typeof method != "function") {
 11383       throw new Error("Unknown Ber tag 0x" + tag.toString(16));
 11386     let value = method.call(this, length);
 11388     return {
 11389       tag: tag,
 11390       length: length,
 11391       value: value
 11392     };
 11393   },
 11395   /**
 11396    * Process the value part for FCP template TLV.
 11398    * @param length
 11399    *        The length of data in bytes.
 11400    */
 11401   processFcpTemplate: function(length) {
 11402     let tlvs = this.decodeChunks(length);
 11403     return tlvs;
 11404   },
 11406   /**
 11407    * Process the value part for proactive command TLV.
 11409    * @param length
 11410    *        The length of data in bytes.
 11411    */
 11412   processProactiveCommand: function(length) {
 11413     let ctlvs = this.context.ComprehensionTlvHelper.decodeChunks(length);
 11414     return ctlvs;
 11415   },
 11417   /**
 11418    * Decode raw data to a Ber-TLV.
 11419    */
 11420   decodeInnerTlv: function() {
 11421     let GsmPDUHelper = this.context.GsmPDUHelper;
 11422     let tag = GsmPDUHelper.readHexOctet();
 11423     let length = GsmPDUHelper.readHexOctet();
 11424     return {
 11425       tag: tag,
 11426       length: length,
 11427       value: this.retrieve(tag, length)
 11428     };
 11429   },
 11431   decodeChunks: function(length) {
 11432     let chunks = [];
 11433     let index = 0;
 11434     while (index < length) {
 11435       let tlv = this.decodeInnerTlv();
 11436       if (tlv.value) {
 11437         chunks.push(tlv);
 11439       index += tlv.length;
 11440       // tag + length fields consume 2 bytes.
 11441       index += 2;
 11443     return chunks;
 11444   },
 11446   retrieve: function(tag, length) {
 11447     let method = this[tag];
 11448     if (typeof method != "function") {
 11449       if (DEBUG) {
 11450         this.context.debug("Unknown Ber tag : 0x" + tag.toString(16));
 11452       let Buf = this.context.Buf;
 11453       Buf.seekIncoming(length * Buf.PDU_HEX_OCTET_SIZE);
 11454       return null;
 11456     return method.call(this, length);
 11457   },
 11459   /**
 11460    * File Size Data.
 11462    * | Byte        | Description                                | Length |
 11463    * |  1          | Tag                                        |   1    |
 11464    * |  2          | Length                                     |   1    |
 11465    * |  3 to X+24  | Number of allocated data bytes in the file |   X    |
 11466    * |             | , excluding structural information         |        |
 11467    */
 11468   retrieveFileSizeData: function(length) {
 11469     let GsmPDUHelper = this.context.GsmPDUHelper;
 11470     let fileSizeData = 0;
 11471     for (let i = 0; i < length; i++) {
 11472       fileSizeData = fileSizeData << 8;
 11473       fileSizeData += GsmPDUHelper.readHexOctet();
 11476     return {fileSizeData: fileSizeData};
 11477   },
 11479   /**
 11480    * File Descriptor.
 11482    * | Byte    | Description           | Length |
 11483    * |  1      | Tag                   |   1    |
 11484    * |  2      | Length                |   1    |
 11485    * |  3      | File descriptor byte  |   1    |
 11486    * |  4      | Data coding byte      |   1    |
 11487    * |  5 ~ 6  | Record length         |   2    |
 11488    * |  7      | Number of records     |   1    |
 11489    */
 11490   retrieveFileDescriptor: function(length) {
 11491     let GsmPDUHelper = this.context.GsmPDUHelper;
 11492     let fileDescriptorByte = GsmPDUHelper.readHexOctet();
 11493     let dataCodingByte = GsmPDUHelper.readHexOctet();
 11494     // See TS 102 221 Table 11.5, we only care the least 3 bits for the
 11495     // structure of file.
 11496     let fileStructure = fileDescriptorByte & 0x07;
 11498     let fileDescriptor = {
 11499       fileStructure: fileStructure
 11500     };
 11501     // byte 5 ~ 7 are mandatory for linear fixed and cyclic files, otherwise
 11502     // they are not applicable.
 11503     if (fileStructure === UICC_EF_STRUCTURE[EF_TYPE_LINEAR_FIXED] ||
 11504         fileStructure === UICC_EF_STRUCTURE[EF_TYPE_CYCLIC]) {
 11505       fileDescriptor.recordLength = (GsmPDUHelper.readHexOctet() << 8) +
 11506                                      GsmPDUHelper.readHexOctet();
 11507       fileDescriptor.numOfRecords = GsmPDUHelper.readHexOctet();
 11510     return fileDescriptor;
 11511   },
 11513   /**
 11514    * File identifier.
 11516    * | Byte    | Description      | Length |
 11517    * |  1      | Tag              |   1    |
 11518    * |  2      | Length           |   1    |
 11519    * |  3 ~ 4  | File identifier  |   2    |
 11520    */
 11521   retrieveFileIdentifier: function(length) {
 11522     let GsmPDUHelper = this.context.GsmPDUHelper;
 11523     return {fileId : (GsmPDUHelper.readHexOctet() << 8) +
 11524                       GsmPDUHelper.readHexOctet()};
 11525   },
 11527   searchForNextTag: function(tag, iter) {
 11528     for (let [index, tlv] in iter) {
 11529       if (tlv.tag === tag) {
 11530         return tlv;
 11533     return null;
 11535 };
 11536 BerTlvHelperObject.prototype[BER_FCP_TEMPLATE_TAG] = function BER_FCP_TEMPLATE_TAG(length) {
 11537   return this.processFcpTemplate(length);
 11538 };
 11539 BerTlvHelperObject.prototype[BER_PROACTIVE_COMMAND_TAG] = function BER_PROACTIVE_COMMAND_TAG(length) {
 11540   return this.processProactiveCommand(length);
 11541 };
 11542 BerTlvHelperObject.prototype[BER_FCP_FILE_SIZE_DATA_TAG] = function BER_FCP_FILE_SIZE_DATA_TAG(length) {
 11543   return this.retrieveFileSizeData(length);
 11544 };
 11545 BerTlvHelperObject.prototype[BER_FCP_FILE_DESCRIPTOR_TAG] = function BER_FCP_FILE_DESCRIPTOR_TAG(length) {
 11546   return this.retrieveFileDescriptor(length);
 11547 };
 11548 BerTlvHelperObject.prototype[BER_FCP_FILE_IDENTIFIER_TAG] = function BER_FCP_FILE_IDENTIFIER_TAG(length) {
 11549   return this.retrieveFileIdentifier(length);
 11550 };
 11552 /**
 11553  * ICC Helper for getting EF path.
 11554  */
 11555 function ICCFileHelperObject(aContext) {
 11556   this.context = aContext;
 11558 ICCFileHelperObject.prototype = {
 11559   context: null,
 11561   /**
 11562    * This function handles only EFs that are common to RUIM, SIM, USIM
 11563    * and other types of ICC cards.
 11564    */
 11565   getCommonEFPath: function(fileId) {
 11566     switch (fileId) {
 11567       case ICC_EF_ICCID:
 11568         return EF_PATH_MF_SIM;
 11569       case ICC_EF_ADN:
 11570         return EF_PATH_MF_SIM + EF_PATH_DF_TELECOM;
 11571       case ICC_EF_PBR:
 11572         return EF_PATH_MF_SIM + EF_PATH_DF_TELECOM + EF_PATH_DF_PHONEBOOK;
 11574     return null;
 11575   },
 11577   /**
 11578    * This function handles EFs for SIM.
 11579    */
 11580   getSimEFPath: function(fileId) {
 11581     switch (fileId) {
 11582       case ICC_EF_FDN:
 11583       case ICC_EF_MSISDN:
 11584       case ICC_EF_SMS:
 11585         return EF_PATH_MF_SIM + EF_PATH_DF_TELECOM;
 11586       case ICC_EF_AD:
 11587       case ICC_EF_MBDN:
 11588       case ICC_EF_MWIS:
 11589       case ICC_EF_PLMNsel:
 11590       case ICC_EF_SPN:
 11591       case ICC_EF_SPDI:
 11592       case ICC_EF_SST:
 11593       case ICC_EF_PHASE:
 11594       case ICC_EF_CBMI:
 11595       case ICC_EF_CBMID:
 11596       case ICC_EF_CBMIR:
 11597       case ICC_EF_OPL:
 11598       case ICC_EF_PNN:
 11599         return EF_PATH_MF_SIM + EF_PATH_DF_GSM;
 11600       default:
 11601         return null;
 11603   },
 11605   /**
 11606    * This function handles EFs for USIM.
 11607    */
 11608   getUSimEFPath: function(fileId) {
 11609     switch (fileId) {
 11610       case ICC_EF_AD:
 11611       case ICC_EF_FDN:
 11612       case ICC_EF_MBDN:
 11613       case ICC_EF_MWIS:
 11614       case ICC_EF_UST:
 11615       case ICC_EF_MSISDN:
 11616       case ICC_EF_SPN:
 11617       case ICC_EF_SPDI:
 11618       case ICC_EF_CBMI:
 11619       case ICC_EF_CBMID:
 11620       case ICC_EF_CBMIR:
 11621       case ICC_EF_OPL:
 11622       case ICC_EF_PNN:
 11623       case ICC_EF_SMS:
 11624         return EF_PATH_MF_SIM + EF_PATH_ADF_USIM;
 11625       default:
 11626         // The file ids in USIM phone book entries are decided by the
 11627         // card manufacturer. So if we don't match any of the cases
 11628         // above and if its a USIM return the phone book path.
 11629         return EF_PATH_MF_SIM + EF_PATH_DF_TELECOM + EF_PATH_DF_PHONEBOOK;
 11631   },
 11633   /**
 11634    * This function handles EFs for RUIM
 11635    */
 11636   getRuimEFPath: function(fileId) {
 11637     switch(fileId) {
 11638       case ICC_EF_CSIM_IMSI_M:
 11639       case ICC_EF_CSIM_CDMAHOME:
 11640       case ICC_EF_CSIM_CST:
 11641       case ICC_EF_CSIM_SPN:
 11642         return EF_PATH_MF_SIM + EF_PATH_DF_CDMA;
 11643       case ICC_EF_FDN:
 11644         return EF_PATH_MF_SIM + EF_PATH_DF_TELECOM;
 11645       default:
 11646         return null;
 11648   },
 11650   /**
 11651    * Helper function for getting the pathId for the specific ICC record
 11652    * depeding on which type of ICC card we are using.
 11654    * @param fileId
 11655    *        File id.
 11656    * @return The pathId or null in case of an error or invalid input.
 11657    */
 11658   getEFPath: function(fileId) {
 11659     let appType = this.context.RIL.appType;
 11660     if (appType == null) {
 11661       return null;
 11664     let path = this.getCommonEFPath(fileId);
 11665     if (path) {
 11666       return path;
 11669     switch (appType) {
 11670       case CARD_APPTYPE_SIM:
 11671         return this.getSimEFPath(fileId);
 11672       case CARD_APPTYPE_USIM:
 11673         return this.getUSimEFPath(fileId);
 11674       case CARD_APPTYPE_RUIM:
 11675         return this.getRuimEFPath(fileId);
 11676       default:
 11677         return null;
 11680 };
 11682 /**
 11683  * Helper for ICC IO functionalities.
 11684  */
 11685 function ICCIOHelperObject(aContext) {
 11686   this.context = aContext;
 11688 ICCIOHelperObject.prototype = {
 11689   context: null,
 11691   /**
 11692    * Load EF with type 'Linear Fixed'.
 11694    * @param fileId
 11695    *        The file to operate on, one of the ICC_EF_* constants.
 11696    * @param recordNumber [optional]
 11697    *        The number of the record shall be loaded.
 11698    * @param recordSize [optional]
 11699    *        The size of the record.
 11700    * @param callback [optional]
 11701    *        The callback function shall be called when the record(s) is read.
 11702    * @param onerror [optional]
 11703    *        The callback function shall be called when failure.
 11704    */
 11705   loadLinearFixedEF: function(options) {
 11706     let cb;
 11707     let readRecord = (function(options) {
 11708       options.command = ICC_COMMAND_READ_RECORD;
 11709       options.p1 = options.recordNumber || 1; // Record number
 11710       options.p2 = READ_RECORD_ABSOLUTE_MODE;
 11711       options.p3 = options.recordSize;
 11712       options.callback = cb || options.callback;
 11713       this.context.RIL.iccIO(options);
 11714     }).bind(this);
 11716     options.type = EF_TYPE_LINEAR_FIXED;
 11717     options.pathId = this.context.ICCFileHelper.getEFPath(options.fileId);
 11718     if (options.recordSize) {
 11719       readRecord(options);
 11720       return;
 11723     cb = options.callback;
 11724     options.callback = readRecord;
 11725     this.getResponse(options);
 11726   },
 11728   /**
 11729    * Load next record from current record Id.
 11730    */
 11731   loadNextRecord: function(options) {
 11732     options.p1++;
 11733     this.context.RIL.iccIO(options);
 11734   },
 11736   /**
 11737    * Update EF with type 'Linear Fixed'.
 11739    * @param fileId
 11740    *        The file to operate on, one of the ICC_EF_* constants.
 11741    * @param recordNumber
 11742    *        The number of the record shall be updated.
 11743    * @param dataWriter [optional]
 11744    *        The function for writing string parameter for the ICC_COMMAND_UPDATE_RECORD.
 11745    * @param pin2 [optional]
 11746    *        PIN2 is required when updating ICC_EF_FDN.
 11747    * @param callback [optional]
 11748    *        The callback function shall be called when the record is updated.
 11749    * @param onerror [optional]
 11750    *        The callback function shall be called when failure.
 11751    */
 11752   updateLinearFixedEF: function(options) {
 11753     if (!options.fileId || !options.recordNumber) {
 11754       throw new Error("Unexpected fileId " + options.fileId +
 11755                       " or recordNumber " + options.recordNumber);
 11758     options.type = EF_TYPE_LINEAR_FIXED;
 11759     options.pathId = this.context.ICCFileHelper.getEFPath(options.fileId);
 11760     let cb = options.callback;
 11761     options.callback = function callback(options) {
 11762       options.callback = cb;
 11763       options.command = ICC_COMMAND_UPDATE_RECORD;
 11764       options.p1 = options.recordNumber;
 11765       options.p2 = READ_RECORD_ABSOLUTE_MODE;
 11766       options.p3 = options.recordSize;
 11767       this.context.RIL.iccIO(options);
 11768     }.bind(this);
 11769     this.getResponse(options);
 11770   },
 11772   /**
 11773    * Load EF with type 'Transparent'.
 11775    * @param fileId
 11776    *        The file to operate on, one of the ICC_EF_* constants.
 11777    * @param callback [optional]
 11778    *        The callback function shall be called when the record(s) is read.
 11779    * @param onerror [optional]
 11780    *        The callback function shall be called when failure.
 11781    */
 11782   loadTransparentEF: function(options) {
 11783     options.type = EF_TYPE_TRANSPARENT;
 11784     let cb = options.callback;
 11785     options.callback = function callback(options) {
 11786       options.callback = cb;
 11787       options.command = ICC_COMMAND_READ_BINARY;
 11788       options.p3 = options.fileSize;
 11789       this.context.RIL.iccIO(options);
 11790     }.bind(this);
 11791     this.getResponse(options);
 11792   },
 11794   /**
 11795    * Use ICC_COMMAND_GET_RESPONSE to query the EF.
 11797    * @param fileId
 11798    *        The file to operate on, one of the ICC_EF_* constants.
 11799    */
 11800   getResponse: function(options) {
 11801     options.command = ICC_COMMAND_GET_RESPONSE;
 11802     options.pathId = options.pathId ||
 11803                      this.context.ICCFileHelper.getEFPath(options.fileId);
 11804     if (!options.pathId) {
 11805       throw new Error("Unknown pathId for " + options.fileId.toString(16));
 11807     options.p1 = 0; // For GET_RESPONSE, p1 = 0
 11808     options.p2 = 0; // For GET_RESPONSE, p2 = 0
 11809     options.p3 = GET_RESPONSE_EF_SIZE_BYTES;
 11810     this.context.RIL.iccIO(options);
 11811   },
 11813   /**
 11814    * Process ICC I/O response.
 11815    */
 11816   processICCIO: function(options) {
 11817     let func = this[options.command];
 11818     func.call(this, options);
 11819   },
 11821   /**
 11822    * Process a ICC_COMMAND_GET_RESPONSE type command for REQUEST_SIM_IO.
 11823    */
 11824   processICCIOGetResponse: function(options) {
 11825     let Buf = this.context.Buf;
 11826     let strLen = Buf.readInt32();
 11828     let peek = this.context.GsmPDUHelper.readHexOctet();
 11829     Buf.seekIncoming(-1 * Buf.PDU_HEX_OCTET_SIZE);
 11830     if (peek === BER_FCP_TEMPLATE_TAG) {
 11831       this.processUSimGetResponse(options, strLen / 2);
 11832     } else {
 11833       this.processSimGetResponse(options);
 11835     Buf.readStringDelimiter(strLen);
 11837     if (options.callback) {
 11838       options.callback(options);
 11840   },
 11842   /**
 11843    * Helper function for processing USIM get response.
 11844    */
 11845   processUSimGetResponse: function(options, octetLen) {
 11846     let BerTlvHelper = this.context.BerTlvHelper;
 11848     let berTlv = BerTlvHelper.decode(octetLen);
 11849     // See TS 102 221 Table 11.4 for the content order of getResponse.
 11850     let iter = Iterator(berTlv.value);
 11851     let tlv = BerTlvHelper.searchForNextTag(BER_FCP_FILE_DESCRIPTOR_TAG,
 11852                                             iter);
 11853     if (!tlv || (tlv.value.fileStructure !== UICC_EF_STRUCTURE[options.type])) {
 11854       throw new Error("Expected EF type " + UICC_EF_STRUCTURE[options.type] +
 11855                       " but read " + tlv.value.fileStructure);
 11858     if (tlv.value.fileStructure === UICC_EF_STRUCTURE[EF_TYPE_LINEAR_FIXED] ||
 11859         tlv.value.fileStructure === UICC_EF_STRUCTURE[EF_TYPE_CYCLIC]) {
 11860       options.recordSize = tlv.value.recordLength;
 11861       options.totalRecords = tlv.value.numOfRecords;
 11864     tlv = BerTlvHelper.searchForNextTag(BER_FCP_FILE_IDENTIFIER_TAG, iter);
 11865     if (!tlv || (tlv.value.fileId !== options.fileId)) {
 11866       throw new Error("Expected file ID " + options.fileId.toString(16) +
 11867                       " but read " + fileId.toString(16));
 11870     tlv = BerTlvHelper.searchForNextTag(BER_FCP_FILE_SIZE_DATA_TAG, iter);
 11871     if (!tlv) {
 11872       throw new Error("Unexpected file size data");
 11874     options.fileSize = tlv.value.fileSizeData;
 11875   },
 11877   /**
 11878    * Helper function for processing SIM get response.
 11879    */
 11880   processSimGetResponse: function(options) {
 11881     let Buf = this.context.Buf;
 11882     let GsmPDUHelper = this.context.GsmPDUHelper;
 11884     // The format is from TS 51.011, clause 9.2.1
 11886     // Skip RFU, data[0] data[1].
 11887     Buf.seekIncoming(2 * Buf.PDU_HEX_OCTET_SIZE);
 11889     // File size, data[2], data[3]
 11890     options.fileSize = (GsmPDUHelper.readHexOctet() << 8) |
 11891                         GsmPDUHelper.readHexOctet();
 11893     // 2 bytes File id. data[4], data[5]
 11894     let fileId = (GsmPDUHelper.readHexOctet() << 8) |
 11895                   GsmPDUHelper.readHexOctet();
 11896     if (fileId != options.fileId) {
 11897       throw new Error("Expected file ID " + options.fileId.toString(16) +
 11898                       " but read " + fileId.toString(16));
 11901     // Type of file, data[6]
 11902     let fileType = GsmPDUHelper.readHexOctet();
 11903     if (fileType != TYPE_EF) {
 11904       throw new Error("Unexpected file type " + fileType);
 11907     // Skip 1 byte RFU, data[7],
 11908     //      3 bytes Access conditions, data[8] data[9] data[10],
 11909     //      1 byte File status, data[11],
 11910     //      1 byte Length of the following data, data[12].
 11911     Buf.seekIncoming(((RESPONSE_DATA_STRUCTURE - RESPONSE_DATA_FILE_TYPE - 1) *
 11912         Buf.PDU_HEX_OCTET_SIZE));
 11914     // Read Structure of EF, data[13]
 11915     let efType = GsmPDUHelper.readHexOctet();
 11916     if (efType != options.type) {
 11917       throw new Error("Expected EF type " + options.type + " but read " + efType);
 11920     // TODO: Bug 952025.
 11921     // Length of a record, data[14].
 11922     // Only available for LINEAR_FIXED and CYCLIC.
 11923     if (efType == EF_TYPE_LINEAR_FIXED || efType == EF_TYPE_CYCLIC) {
 11924       options.recordSize = GsmPDUHelper.readHexOctet();
 11925       options.totalRecords = options.fileSize / options.recordSize;
 11926     } else {
 11927       Buf.seekIncoming(1 * Buf.PDU_HEX_OCTET_SIZE);
 11929   },
 11931   /**
 11932    * Process a ICC_COMMAND_READ_RECORD type command for REQUEST_SIM_IO.
 11933    */
 11934   processICCIOReadRecord: function(options) {
 11935     if (options.callback) {
 11936       options.callback(options);
 11938   },
 11940   /**
 11941    * Process a ICC_COMMAND_READ_BINARY type command for REQUEST_SIM_IO.
 11942    */
 11943   processICCIOReadBinary: function(options) {
 11944     if (options.callback) {
 11945       options.callback(options);
 11947   },
 11949   /**
 11950    * Process a ICC_COMMAND_UPDATE_RECORD type command for REQUEST_SIM_IO.
 11951    */
 11952   processICCIOUpdateRecord: function(options) {
 11953     if (options.callback) {
 11954       options.callback(options);
 11956   },
 11958   /**
 11959    * Process ICC IO error.
 11960    */
 11961   processICCIOError: function(options) {
 11962     let requestError = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError];
 11963     if (DEBUG) {
 11964       // See GSM11.11, TS 51.011 clause 9.4, and ISO 7816-4 for the error
 11965       // description.
 11966       let errorMsg = "ICC I/O Error code " + requestError +
 11967                      " EF id = " + options.fileId.toString(16) +
 11968                      " command = " + options.command.toString(16);
 11969       if (options.sw1 && options.sw2) {
 11970         errorMsg += "(" + options.sw1.toString(16) +
 11971                     "/" + options.sw2.toString(16) + ")";
 11973       this.context.debug(errorMsg);
 11975     if (options.onerror) {
 11976       options.onerror(requestError);
 11978   },
 11979 };
 11980 ICCIOHelperObject.prototype[ICC_COMMAND_SEEK] = null;
 11981 ICCIOHelperObject.prototype[ICC_COMMAND_READ_BINARY] = function ICC_COMMAND_READ_BINARY(options) {
 11982   this.processICCIOReadBinary(options);
 11983 };
 11984 ICCIOHelperObject.prototype[ICC_COMMAND_READ_RECORD] = function ICC_COMMAND_READ_RECORD(options) {
 11985   this.processICCIOReadRecord(options);
 11986 };
 11987 ICCIOHelperObject.prototype[ICC_COMMAND_GET_RESPONSE] = function ICC_COMMAND_GET_RESPONSE(options) {
 11988   this.processICCIOGetResponse(options);
 11989 };
 11990 ICCIOHelperObject.prototype[ICC_COMMAND_UPDATE_BINARY] = null;
 11991 ICCIOHelperObject.prototype[ICC_COMMAND_UPDATE_RECORD] = function ICC_COMMAND_UPDATE_RECORD(options) {
 11992   this.processICCIOUpdateRecord(options);
 11993 };
 11995 /**
 11996  * Helper for ICC records.
 11997  */
 11998 function ICCRecordHelperObject(aContext) {
 11999   this.context = aContext;
 12001 ICCRecordHelperObject.prototype = {
 12002   context: null,
 12004   /**
 12005    * Fetch ICC records.
 12006    */
 12007   fetchICCRecords: function() {
 12008     switch (this.context.RIL.appType) {
 12009       case CARD_APPTYPE_SIM:
 12010       case CARD_APPTYPE_USIM:
 12011         this.context.SimRecordHelper.fetchSimRecords();
 12012         break;
 12013       case CARD_APPTYPE_RUIM:
 12014         this.context.RuimRecordHelper.fetchRuimRecords();
 12015         break;
 12017   },
 12019   /**
 12020    * Read the ICCID.
 12021    */
 12022   readICCID: function() {
 12023     function callback() {
 12024       let Buf = this.context.Buf;
 12025       let RIL = this.context.RIL;
 12027       let strLen = Buf.readInt32();
 12028       let octetLen = strLen / 2;
 12029       RIL.iccInfo.iccid =
 12030         this.context.GsmPDUHelper.readSwappedNibbleBcdString(octetLen, true);
 12031       // Consumes the remaining buffer if any.
 12032       let unReadBuffer = this.context.Buf.getReadAvailable() -
 12033                          this.context.Buf.PDU_HEX_OCTET_SIZE;
 12034       if (unReadBuffer > 0) {
 12035         this.context.Buf.seekIncoming(unReadBuffer);
 12037       Buf.readStringDelimiter(strLen);
 12039       if (DEBUG) this.context.debug("ICCID: " + RIL.iccInfo.iccid);
 12040       if (RIL.iccInfo.iccid) {
 12041         this.context.ICCUtilsHelper.handleICCInfoChange();
 12042         RIL.reportStkServiceIsRunning();
 12046     this.context.ICCIOHelper.loadTransparentEF({
 12047       fileId: ICC_EF_ICCID,
 12048       callback: callback.bind(this)
 12049     });
 12050   },
 12052   /**
 12053    * Read ICC ADN like EF, i.e. EF_ADN, EF_FDN.
 12055    * @param fileId      EF id of the ADN or FDN.
 12056    * @param onsuccess   Callback to be called when success.
 12057    * @param onerror     Callback to be called when error.
 12058    */
 12059   readADNLike: function(fileId, onsuccess, onerror) {
 12060     let ICCIOHelper = this.context.ICCIOHelper;
 12062     function callback(options) {
 12063       let contact =
 12064         this.context.ICCPDUHelper.readAlphaIdDiallingNumber(options.recordSize);
 12065       if (contact) {
 12066         contact.recordId = options.p1;
 12067         contacts.push(contact);
 12070       if (options.p1 < options.totalRecords) {
 12071         ICCIOHelper.loadNextRecord(options);
 12072       } else {
 12073         if (DEBUG) {
 12074           for (let i = 0; i < contacts.length; i++) {
 12075             this.context.debug("contact [" + i + "] " +
 12076                                JSON.stringify(contacts[i]));
 12079         if (onsuccess) {
 12080           onsuccess(contacts);
 12085     let contacts = [];
 12086     ICCIOHelper.loadLinearFixedEF({fileId: fileId,
 12087                                    callback: callback.bind(this),
 12088                                    onerror: onerror});
 12089   },
 12091   /**
 12092    * Update ICC ADN like EFs, like EF_ADN, EF_FDN.
 12094    * @param fileId      EF id of the ADN or FDN.
 12095    * @param contact     The contact will be updated. (Shall have recordId property)
 12096    * @param pin2        PIN2 is required when updating ICC_EF_FDN.
 12097    * @param onsuccess   Callback to be called when success.
 12098    * @param onerror     Callback to be called when error.
 12099    */
 12100   updateADNLike: function(fileId, contact, pin2, onsuccess, onerror) {
 12101     function dataWriter(recordSize) {
 12102       this.context.ICCPDUHelper.writeAlphaIdDiallingNumber(recordSize,
 12103                                                            contact.alphaId,
 12104                                                            contact.number);
 12107     function callback(options) {
 12108       if (onsuccess) {
 12109         onsuccess();
 12113     if (!contact || !contact.recordId) {
 12114       if (onerror) onerror(GECKO_ERROR_INVALID_PARAMETER);
 12115       return;
 12118     this.context.ICCIOHelper.updateLinearFixedEF({
 12119       fileId: fileId,
 12120       recordNumber: contact.recordId,
 12121       dataWriter: dataWriter.bind(this),
 12122       pin2: pin2,
 12123       callback: callback.bind(this),
 12124       onerror: onerror
 12125     });
 12126   },
 12128   /**
 12129    * Read USIM/RUIM Phonebook.
 12131    * @param onsuccess   Callback to be called when success.
 12132    * @param onerror     Callback to be called when error.
 12133    */
 12134   readPBR: function(onsuccess, onerror) {
 12135     let Buf = this.context.Buf;
 12136     let GsmPDUHelper = this.context.GsmPDUHelper;
 12137     let ICCIOHelper = this.context.ICCIOHelper;
 12138     let ICCUtilsHelper = this.context.ICCUtilsHelper;
 12139     let RIL = this.context.RIL;
 12141     function callback(options) {
 12142       let strLen = Buf.readInt32();
 12143       let octetLen = strLen / 2, readLen = 0;
 12145       let pbrTlvs = [];
 12146       while (readLen < octetLen) {
 12147         let tag = GsmPDUHelper.readHexOctet();
 12148         if (tag == 0xff) {
 12149           readLen++;
 12150           Buf.seekIncoming((octetLen - readLen) * Buf.PDU_HEX_OCTET_SIZE);
 12151           break;
 12154         let tlvLen = GsmPDUHelper.readHexOctet();
 12155         let tlvs = ICCUtilsHelper.decodeSimTlvs(tlvLen);
 12156         pbrTlvs.push({tag: tag,
 12157                       length: tlvLen,
 12158                       value: tlvs});
 12160         readLen += tlvLen + 2; // +2 for tag and tlvLen
 12162       Buf.readStringDelimiter(strLen);
 12164       if (pbrTlvs.length > 0) {
 12165         let pbr = ICCUtilsHelper.parsePbrTlvs(pbrTlvs);
 12166         // EF_ADN is mandatory if and only if DF_PHONEBOOK is present.
 12167         if (!pbr.adn) {
 12168           if (onerror) onerror("Cannot access ADN.");
 12169           return;
 12171         pbrs.push(pbr);
 12174       if (options.p1 < options.totalRecords) {
 12175         ICCIOHelper.loadNextRecord(options);
 12176       } else {
 12177         if (onsuccess) {
 12178           RIL.iccInfoPrivate.pbrs = pbrs;
 12179           onsuccess(pbrs);
 12184     if (RIL.iccInfoPrivate.pbrs) {
 12185       onsuccess(RIL.iccInfoPrivate.pbrs);
 12186       return;
 12189     let pbrs = [];
 12190     ICCIOHelper.loadLinearFixedEF({fileId : ICC_EF_PBR,
 12191                                    callback: callback.bind(this),
 12192                                    onerror: onerror});
 12193   },
 12195   /**
 12196    * Cache EF_IAP record size.
 12197    */
 12198   _iapRecordSize: null,
 12200   /**
 12201    * Read ICC EF_IAP. (Index Administration Phonebook)
 12203    * @see TS 131.102, clause 4.4.2.2
 12205    * @param fileId       EF id of the IAP.
 12206    * @param recordNumber The number of the record shall be loaded.
 12207    * @param onsuccess    Callback to be called when success.
 12208    * @param onerror      Callback to be called when error.
 12209    */
 12210   readIAP: function(fileId, recordNumber, onsuccess, onerror) {
 12211     function callback(options) {
 12212       let Buf = this.context.Buf;
 12213       let strLen = Buf.readInt32();
 12214       let octetLen = strLen / 2;
 12215       this._iapRecordSize = options.recordSize;
 12217       let iap = this.context.GsmPDUHelper.readHexOctetArray(octetLen);
 12218       Buf.readStringDelimiter(strLen);
 12220       if (onsuccess) {
 12221         onsuccess(iap);
 12225     this.context.ICCIOHelper.loadLinearFixedEF({
 12226       fileId: fileId,
 12227       recordNumber: recordNumber,
 12228       recordSize: this._iapRecordSize,
 12229       callback: callback.bind(this),
 12230       onerror: onerror
 12231     });
 12232   },
 12234   /**
 12235    * Update USIM/RUIM Phonebook EF_IAP.
 12237    * @see TS 131.102, clause 4.4.2.13
 12239    * @param fileId       EF id of the IAP.
 12240    * @param recordNumber The identifier of the record shall be updated.
 12241    * @param iap          The IAP value to be written.
 12242    * @param onsuccess    Callback to be called when success.
 12243    * @param onerror      Callback to be called when error.
 12244    */
 12245   updateIAP: function(fileId, recordNumber, iap, onsuccess, onerror) {
 12246     let dataWriter = function dataWriter(recordSize) {
 12247       let Buf = this.context.Buf;
 12248       let GsmPDUHelper = this.context.GsmPDUHelper;
 12250       // Write String length
 12251       let strLen = recordSize * 2;
 12252       Buf.writeInt32(strLen);
 12254       for (let i = 0; i < iap.length; i++) {
 12255         GsmPDUHelper.writeHexOctet(iap[i]);
 12258       Buf.writeStringDelimiter(strLen);
 12259     }.bind(this);
 12261     this.context.ICCIOHelper.updateLinearFixedEF({
 12262       fileId: fileId,
 12263       recordNumber: recordNumber,
 12264       dataWriter: dataWriter,
 12265       callback: onsuccess,
 12266       onerror: onerror
 12267     });
 12268   },
 12270   /**
 12271    * Cache EF_Email record size.
 12272    */
 12273   _emailRecordSize: null,
 12275   /**
 12276    * Read USIM/RUIM Phonebook EF_EMAIL.
 12278    * @see TS 131.102, clause 4.4.2.13
 12280    * @param fileId       EF id of the EMAIL.
 12281    * @param fileType     The type of the EMAIL, one of the ICC_USIM_TYPE* constants.
 12282    * @param recordNumber The number of the record shall be loaded.
 12283    * @param onsuccess    Callback to be called when success.
 12284    * @param onerror      Callback to be called when error.
 12285    */
 12286   readEmail: function(fileId, fileType, recordNumber, onsuccess, onerror) {
 12287     function callback(options) {
 12288       let Buf = this.context.Buf;
 12289       let ICCPDUHelper = this.context.ICCPDUHelper;
 12291       let strLen = Buf.readInt32();
 12292       let octetLen = strLen / 2;
 12293       let email = null;
 12294       this._emailRecordSize = options.recordSize;
 12296       // Read contact's email
 12297       //
 12298       // | Byte     | Description                 | Length | M/O
 12299       // | 1 ~ X    | E-mail Address              |   X    |  M
 12300       // | X+1      | ADN file SFI                |   1    |  C
 12301       // | X+2      | ADN file Record Identifier  |   1    |  C
 12302       // Note: The fields marked as C above are mandatort if the file
 12303       //       is not type 1 (as specified in EF_PBR)
 12304       if (fileType == ICC_USIM_TYPE1_TAG) {
 12305         email = ICCPDUHelper.read8BitUnpackedToString(octetLen);
 12306       } else {
 12307         email = ICCPDUHelper.read8BitUnpackedToString(octetLen - 2);
 12309         // Consumes the remaining buffer
 12310         Buf.seekIncoming(2 * Buf.PDU_HEX_OCTET_SIZE); // For ADN SFI and Record Identifier
 12313       Buf.readStringDelimiter(strLen);
 12315       if (onsuccess) {
 12316         onsuccess(email);
 12320     this.context.ICCIOHelper.loadLinearFixedEF({
 12321       fileId: fileId,
 12322       recordNumber: recordNumber,
 12323       recordSize: this._emailRecordSize,
 12324       callback: callback.bind(this),
 12325       onerror: onerror
 12326     });
 12327   },
 12329   /**
 12330    * Update USIM/RUIM Phonebook EF_EMAIL.
 12332    * @see TS 131.102, clause 4.4.2.13
 12334    * @param pbr          Phonebook Reference File.
 12335    * @param recordNumber The identifier of the record shall be updated.
 12336    * @param email        The value to be written.
 12337    * @param adnRecordId  The record Id of ADN, only needed if the fileType of Email is TYPE2.
 12338    * @param onsuccess    Callback to be called when success.
 12339    * @param onerror      Callback to be called when error.
 12340    */
 12341   updateEmail: function(pbr, recordNumber, email, adnRecordId, onsuccess, onerror) {
 12342     let fileId = pbr[USIM_PBR_EMAIL].fileId;
 12343     let fileType = pbr[USIM_PBR_EMAIL].fileType;
 12344     let dataWriter = function dataWriter(recordSize) {
 12345       let Buf = this.context.Buf;
 12346       let GsmPDUHelper = this.context.GsmPDUHelper;
 12347       let ICCPDUHelper = this.context.ICCPDUHelper;
 12349       // Write String length
 12350       let strLen = recordSize * 2;
 12351       Buf.writeInt32(strLen);
 12353       if (fileType == ICC_USIM_TYPE1_TAG) {
 12354         ICCPDUHelper.writeStringTo8BitUnpacked(recordSize, email);
 12355       } else {
 12356         ICCPDUHelper.writeStringTo8BitUnpacked(recordSize - 2, email);
 12357         GsmPDUHelper.writeHexOctet(pbr.adn.sfi || 0xff);
 12358         GsmPDUHelper.writeHexOctet(adnRecordId);
 12361       Buf.writeStringDelimiter(strLen);
 12362     }.bind(this);
 12364     this.context.ICCIOHelper.updateLinearFixedEF({
 12365       fileId: fileId,
 12366       recordNumber: recordNumber,
 12367       dataWriter: dataWriter,
 12368       callback: onsuccess,
 12369       onerror: onerror
 12370     });
 12371   },
 12373   /**
 12374    * Cache EF_ANR record size.
 12375    */
 12376   _anrRecordSize: null,
 12378   /**
 12379    * Read USIM/RUIM Phonebook EF_ANR.
 12381    * @see TS 131.102, clause 4.4.2.9
 12383    * @param fileId       EF id of the ANR.
 12384    * @param fileType     One of the ICC_USIM_TYPE* constants.
 12385    * @param recordNumber The number of the record shall be loaded.
 12386    * @param onsuccess    Callback to be called when success.
 12387    * @param onerror      Callback to be called when error.
 12388    */
 12389   readANR: function(fileId, fileType, recordNumber, onsuccess, onerror) {
 12390     function callback(options) {
 12391       let Buf = this.context.Buf;
 12392       let strLen = Buf.readInt32();
 12393       let number = null;
 12394       this._anrRecordSize = options.recordSize;
 12396       // Skip EF_AAS Record ID.
 12397       Buf.seekIncoming(1 * Buf.PDU_HEX_OCTET_SIZE);
 12399       number = this.context.ICCPDUHelper.readNumberWithLength();
 12401       // Skip 2 unused octets, CCP and EXT1.
 12402       Buf.seekIncoming(2 * Buf.PDU_HEX_OCTET_SIZE);
 12404       // For Type 2 there are two extra octets.
 12405       if (fileType == ICC_USIM_TYPE2_TAG) {
 12406         // Skip 2 unused octets, ADN SFI and Record Identifier.
 12407         Buf.seekIncoming(2 * Buf.PDU_HEX_OCTET_SIZE);
 12410       Buf.readStringDelimiter(strLen);
 12412       if (onsuccess) {
 12413         onsuccess(number);
 12417     this.context.ICCIOHelper.loadLinearFixedEF({
 12418       fileId: fileId,
 12419       recordNumber: recordNumber,
 12420       recordSize: this._anrRecordSize,
 12421       callback: callback.bind(this),
 12422       onerror: onerror
 12423     });
 12424   },
 12426   /**
 12427    * Update USIM/RUIM Phonebook EF_ANR.
 12429    * @see TS 131.102, clause 4.4.2.9
 12431    * @param pbr          Phonebook Reference File.
 12432    * @param recordNumber The identifier of the record shall be updated.
 12433    * @param number       The value to be written.
 12434    * @param adnRecordId  The record Id of ADN, only needed if the fileType of Email is TYPE2.
 12435    * @param onsuccess    Callback to be called when success.
 12436    * @param onerror      Callback to be called when error.
 12437    */
 12438   updateANR: function(pbr, recordNumber, number, adnRecordId, onsuccess, onerror) {
 12439     let fileId = pbr[USIM_PBR_ANR0].fileId;
 12440     let fileType = pbr[USIM_PBR_ANR0].fileType;
 12441     let dataWriter = function dataWriter(recordSize) {
 12442       let Buf = this.context.Buf;
 12443       let GsmPDUHelper = this.context.GsmPDUHelper;
 12445       // Write String length
 12446       let strLen = recordSize * 2;
 12447       Buf.writeInt32(strLen);
 12449       // EF_AAS record Id. Unused for now.
 12450       GsmPDUHelper.writeHexOctet(0xff);
 12452       this.context.ICCPDUHelper.writeNumberWithLength(number);
 12454       // Write unused octets 0xff, CCP and EXT1.
 12455       GsmPDUHelper.writeHexOctet(0xff);
 12456       GsmPDUHelper.writeHexOctet(0xff);
 12458       // For Type 2 there are two extra octets.
 12459       if (fileType == ICC_USIM_TYPE2_TAG) {
 12460         GsmPDUHelper.writeHexOctet(pbr.adn.sfi || 0xff);
 12461         GsmPDUHelper.writeHexOctet(adnRecordId);
 12464       Buf.writeStringDelimiter(strLen);
 12465     }.bind(this);
 12467     this.context.ICCIOHelper.updateLinearFixedEF({
 12468       fileId: fileId,
 12469       recordNumber: recordNumber,
 12470       dataWriter: dataWriter,
 12471       callback: onsuccess,
 12472       onerror: onerror
 12473     });
 12474   },
 12476   /**
 12477    * Find free record id.
 12479    * @param fileId      EF id.
 12480    * @param onsuccess   Callback to be called when success.
 12481    * @param onerror     Callback to be called when error.
 12482    */
 12483   findFreeRecordId: function(fileId, onsuccess, onerror) {
 12484     let ICCIOHelper = this.context.ICCIOHelper;
 12486     function callback(options) {
 12487       let Buf = this.context.Buf;
 12488       let GsmPDUHelper = this.context.GsmPDUHelper;
 12490       let strLen = Buf.readInt32();
 12491       let octetLen = strLen / 2;
 12492       let readLen = 0;
 12494       while (readLen < octetLen) {
 12495         let octet = GsmPDUHelper.readHexOctet();
 12496         readLen++;
 12497         if (octet != 0xff) {
 12498           break;
 12502       if (readLen == octetLen) {
 12503         // Find free record.
 12504         if (onsuccess) {
 12505           onsuccess(options.p1);
 12507         return;
 12508       } else {
 12509         Buf.seekIncoming((octetLen - readLen) * Buf.PDU_HEX_OCTET_SIZE);
 12512       Buf.readStringDelimiter(strLen);
 12514       if (options.p1 < options.totalRecords) {
 12515         ICCIOHelper.loadNextRecord(options);
 12516       } else {
 12517         // No free record found.
 12518         if (DEBUG) {
 12519           this.context.debug(CONTACT_ERR_NO_FREE_RECORD_FOUND);
 12521         onerror(CONTACT_ERR_NO_FREE_RECORD_FOUND);
 12525     ICCIOHelper.loadLinearFixedEF({fileId: fileId,
 12526                                    callback: callback.bind(this),
 12527                                    onerror: onerror});
 12528   },
 12529 };
 12531 /**
 12532  * Helper for (U)SIM Records.
 12533  */
 12534 function SimRecordHelperObject(aContext) {
 12535   this.context = aContext;
 12537 SimRecordHelperObject.prototype = {
 12538   context: null,
 12540   /**
 12541    * Fetch (U)SIM records.
 12542    */
 12543   fetchSimRecords: function() {
 12544     this.context.RIL.getIMSI();
 12545     this.readAD();
 12546     this.readSST();
 12547   },
 12549   /**
 12550    * Read EF_phase.
 12551    * This EF is only available in SIM.
 12552    */
 12553   readSimPhase: function() {
 12554     function callback() {
 12555       let Buf = this.context.Buf;
 12556       let strLen = Buf.readInt32();
 12558       let GsmPDUHelper = this.context.GsmPDUHelper;
 12559       let phase = GsmPDUHelper.readHexOctet();
 12560       // If EF_phase is coded '03' or greater, an ME supporting STK shall
 12561       // perform the PROFILE DOWNLOAD procedure.
 12562       if (RILQUIRKS_SEND_STK_PROFILE_DOWNLOAD &&
 12563           phase >= ICC_PHASE_2_PROFILE_DOWNLOAD_REQUIRED) {
 12564         this.context.RIL.sendStkTerminalProfile(STK_SUPPORTED_TERMINAL_PROFILE);
 12567       Buf.readStringDelimiter(strLen);
 12570     this.context.ICCIOHelper.loadTransparentEF({
 12571       fileId: ICC_EF_PHASE,
 12572       callback: callback.bind(this)
 12573     });
 12574   },
 12576   /**
 12577    * Read the MSISDN from the (U)SIM.
 12578    */
 12579   readMSISDN: function() {
 12580     function callback(options) {
 12581       let RIL = this.context.RIL;
 12583       let contact =
 12584         this.context.ICCPDUHelper.readAlphaIdDiallingNumber(options.recordSize);
 12585       if (!contact ||
 12586           (RIL.iccInfo.msisdn !== undefined &&
 12587            RIL.iccInfo.msisdn === contact.number)) {
 12588         return;
 12590       RIL.iccInfo.msisdn = contact.number;
 12591       if (DEBUG) this.context.debug("MSISDN: " + RIL.iccInfo.msisdn);
 12592       this.context.ICCUtilsHelper.handleICCInfoChange();
 12595     this.context.ICCIOHelper.loadLinearFixedEF({
 12596       fileId: ICC_EF_MSISDN,
 12597       callback: callback.bind(this)
 12598     });
 12599   },
 12601   /**
 12602    * Read the AD (Administrative Data) from the (U)SIM.
 12603    */
 12604   readAD: function() {
 12605     function callback() {
 12606       let Buf = this.context.Buf;
 12607       let strLen = Buf.readInt32();
 12608       // Each octet is encoded into two chars.
 12609       let octetLen = strLen / 2;
 12610       let ad = this.context.GsmPDUHelper.readHexOctetArray(octetLen);
 12611       Buf.readStringDelimiter(strLen);
 12613       if (DEBUG) {
 12614         let str = "";
 12615         for (let i = 0; i < ad.length; i++) {
 12616           str += ad[i] + ", ";
 12618         this.context.debug("AD: " + str);
 12621       let ICCUtilsHelper = this.context.ICCUtilsHelper;
 12622       let RIL = this.context.RIL;
 12623       // The 4th byte of the response is the length of MNC.
 12624       let mccMnc = ICCUtilsHelper.parseMccMncFromImsi(RIL.iccInfoPrivate.imsi,
 12625                                                       ad && ad[3]);
 12626       if (mccMnc) {
 12627         RIL.iccInfo.mcc = mccMnc.mcc;
 12628         RIL.iccInfo.mnc = mccMnc.mnc;
 12629         ICCUtilsHelper.handleICCInfoChange();
 12633     this.context.ICCIOHelper.loadTransparentEF({
 12634       fileId: ICC_EF_AD,
 12635       callback: callback.bind(this)
 12636     });
 12637   },
 12639   /**
 12640    * Read the SPN (Service Provider Name) from the (U)SIM.
 12641    */
 12642   readSPN: function() {
 12643     function callback() {
 12644       let Buf = this.context.Buf;
 12645       let strLen = Buf.readInt32();
 12646       // Each octet is encoded into two chars.
 12647       let octetLen = strLen / 2;
 12648       let spnDisplayCondition = this.context.GsmPDUHelper.readHexOctet();
 12649       // Minus 1 because the first octet is used to store display condition.
 12650       let spn = this.context.ICCPDUHelper.readAlphaIdentifier(octetLen - 1);
 12651       Buf.readStringDelimiter(strLen);
 12653       if (DEBUG) {
 12654         this.context.debug("SPN: spn = " + spn +
 12655                            ", spnDisplayCondition = " + spnDisplayCondition);
 12658       let RIL = this.context.RIL;
 12659       RIL.iccInfoPrivate.spnDisplayCondition = spnDisplayCondition;
 12660       RIL.iccInfo.spn = spn;
 12661       let ICCUtilsHelper = this.context.ICCUtilsHelper;
 12662       ICCUtilsHelper.updateDisplayCondition();
 12663       ICCUtilsHelper.handleICCInfoChange();
 12666     this.context.ICCIOHelper.loadTransparentEF({
 12667       fileId: ICC_EF_SPN,
 12668       callback: callback.bind(this)
 12669     });
 12670   },
 12672   /**
 12673    * Read the (U)SIM Service Table from the (U)SIM.
 12674    */
 12675   readSST: function() {
 12676     function callback() {
 12677       let Buf = this.context.Buf;
 12678       let RIL = this.context.RIL;
 12680       let strLen = Buf.readInt32();
 12681       // Each octet is encoded into two chars.
 12682       let octetLen = strLen / 2;
 12683       let sst = this.context.GsmPDUHelper.readHexOctetArray(octetLen);
 12684       Buf.readStringDelimiter(strLen);
 12685       RIL.iccInfoPrivate.sst = sst;
 12686       if (DEBUG) {
 12687         let str = "";
 12688         for (let i = 0; i < sst.length; i++) {
 12689           str += sst[i] + ", ";
 12691         this.context.debug("SST: " + str);
 12694       let ICCUtilsHelper = this.context.ICCUtilsHelper;
 12695       if (ICCUtilsHelper.isICCServiceAvailable("MSISDN")) {
 12696         if (DEBUG) this.context.debug("MSISDN: MSISDN is available");
 12697         this.readMSISDN();
 12698       } else {
 12699         if (DEBUG) this.context.debug("MSISDN: MSISDN service is not available");
 12702       // Fetch SPN and PLMN list, if some of them are available.
 12703       if (ICCUtilsHelper.isICCServiceAvailable("SPN")) {
 12704         if (DEBUG) this.context.debug("SPN: SPN is available");
 12705         this.readSPN();
 12706       } else {
 12707         if (DEBUG) this.context.debug("SPN: SPN service is not available");
 12710       if (ICCUtilsHelper.isICCServiceAvailable("MDN")) {
 12711         if (DEBUG) this.context.debug("MDN: MDN available.");
 12712         this.readMBDN();
 12713       } else {
 12714         if (DEBUG) this.context.debug("MDN: MDN service is not available");
 12717       if (ICCUtilsHelper.isICCServiceAvailable("MWIS")) {
 12718         if (DEBUG) this.context.debug("MWIS: MWIS is available");
 12719         this.readMWIS();
 12720       } else {
 12721         if (DEBUG) this.context.debug("MWIS: MWIS is not available");
 12724       if (ICCUtilsHelper.isICCServiceAvailable("SPDI")) {
 12725         if (DEBUG) this.context.debug("SPDI: SPDI available.");
 12726         this.readSPDI();
 12727       } else {
 12728         if (DEBUG) this.context.debug("SPDI: SPDI service is not available");
 12731       if (ICCUtilsHelper.isICCServiceAvailable("PNN")) {
 12732         if (DEBUG) this.context.debug("PNN: PNN is available");
 12733         this.readPNN();
 12734       } else {
 12735         if (DEBUG) this.context.debug("PNN: PNN is not available");
 12738       if (ICCUtilsHelper.isICCServiceAvailable("OPL")) {
 12739         if (DEBUG) this.context.debug("OPL: OPL is available");
 12740         this.readOPL();
 12741       } else {
 12742         if (DEBUG) this.context.debug("OPL: OPL is not available");
 12745       if (ICCUtilsHelper.isICCServiceAvailable("CBMI")) {
 12746         this.readCBMI();
 12747       } else {
 12748         RIL.cellBroadcastConfigs.CBMI = null;
 12750       if (ICCUtilsHelper.isICCServiceAvailable("DATA_DOWNLOAD_SMS_CB")) {
 12751         this.readCBMID();
 12752       } else {
 12753         RIL.cellBroadcastConfigs.CBMID = null;
 12755       if (ICCUtilsHelper.isICCServiceAvailable("CBMIR")) {
 12756         this.readCBMIR();
 12757       } else {
 12758         RIL.cellBroadcastConfigs.CBMIR = null;
 12760       RIL._mergeAllCellBroadcastConfigs();
 12763     // ICC_EF_UST has the same value with ICC_EF_SST.
 12764     this.context.ICCIOHelper.loadTransparentEF({
 12765       fileId: ICC_EF_SST,
 12766       callback: callback.bind(this)
 12767     });
 12768   },
 12770   /**
 12771    * Read (U)SIM MBDN. (Mailbox Dialling Number)
 12773    * @see TS 131.102, clause 4.2.60
 12774    */
 12775   readMBDN: function() {
 12776     function callback(options) {
 12777       let RIL = this.context.RIL;
 12778       let contact =
 12779         this.context.ICCPDUHelper.readAlphaIdDiallingNumber(options.recordSize);
 12780       if (!contact ||
 12781           (RIL.iccInfoPrivate.mbdn !== undefined &&
 12782            RIL.iccInfoPrivate.mbdn === contact.number)) {
 12783         return;
 12785       RIL.iccInfoPrivate.mbdn = contact.number;
 12786       if (DEBUG) {
 12787         this.context.debug("MBDN, alphaId=" + contact.alphaId +
 12788                            " number=" + contact.number);
 12790       contact.rilMessageType = "iccmbdn";
 12791       RIL.sendChromeMessage(contact);
 12794     this.context.ICCIOHelper.loadLinearFixedEF({
 12795       fileId: ICC_EF_MBDN,
 12796       callback: callback.bind(this)
 12797     });
 12798   },
 12800   /**
 12801    * Read ICC MWIS. (Message Waiting Indication Status)
 12803    * @see TS 31.102, clause 4.2.63 for USIM and TS 51.011, clause 10.3.45 for SIM.
 12804    */
 12805   readMWIS: function() {
 12806     function callback(options) {
 12807       let Buf = this.context.Buf;
 12808       let RIL = this.context.RIL;
 12810       let strLen = Buf.readInt32();
 12811       // Each octet is encoded into two chars.
 12812       let octetLen = strLen / 2;
 12813       let mwis = this.context.GsmPDUHelper.readHexOctetArray(octetLen);
 12814       Buf.readStringDelimiter(strLen);
 12815       if (!mwis) {
 12816         return;
 12818       RIL.iccInfoPrivate.mwis = mwis; //Keep raw MWIS for updateMWIS()
 12820       let mwi = {};
 12821       // b8 b7 B6 b5 b4 b3 b2 b1   4.2.63, TS 31.102 version 11.6.0
 12822       //  |  |  |  |  |  |  |  |__ Voicemail
 12823       //  |  |  |  |  |  |  |_____ Fax
 12824       //  |  |  |  |  |  |________ Electronic Mail
 12825       //  |  |  |  |  |___________ Other
 12826       //  |  |  |  |______________ Videomail
 12827       //  |__|__|_________________ RFU
 12828       mwi.active = ((mwis[0] & 0x01) != 0);
 12830       if (mwi.active) {
 12831         // In TS 23.040 msgCount is in the range from 0 to 255.
 12832         // The value 255 shall be taken to mean 255 or greater.
 12833         //
 12834         // However, There is no definition about 0 when MWI is active.
 12835         //
 12836         // Normally, when mwi is active, the msgCount must be larger than 0.
 12837         // Refer to other reference phone,
 12838         // 0 is usually treated as UNKNOWN for storing 2nd level MWI status (DCS).
 12839         mwi.msgCount = (mwis[1] === 0) ? GECKO_VOICEMAIL_MESSAGE_COUNT_UNKNOWN
 12840                                        : mwis[1];
 12841       } else {
 12842         mwi.msgCount = 0;
 12845       RIL.sendChromeMessage({ rilMessageType: "iccmwis",
 12846                               mwi: mwi });
 12849     this.context.ICCIOHelper.loadLinearFixedEF({
 12850       fileId: ICC_EF_MWIS,
 12851       recordNumber: 1, // Get 1st Subscriber Profile.
 12852       callback: callback.bind(this)
 12853     });
 12854   },
 12856   /**
 12857    * Update ICC MWIS. (Message Waiting Indication Status)
 12859    * @see TS 31.102, clause 4.2.63 for USIM and TS 51.011, clause 10.3.45 for SIM.
 12860    */
 12861   updateMWIS: function(mwi) {
 12862     let RIL = this.context.RIL;
 12863     if (!RIL.iccInfoPrivate.mwis) {
 12864       return;
 12867     function dataWriter(recordSize) {
 12868       let mwis = RIL.iccInfoPrivate.mwis;
 12870       let msgCount =
 12871           (mwi.msgCount === GECKO_VOICEMAIL_MESSAGE_COUNT_UNKNOWN) ? 0 : mwi.msgCount;
 12873       [mwis[0], mwis[1]] = (mwi.active) ? [(mwis[0] | 0x01), msgCount]
 12874                                         : [(mwis[0] & 0xFE), 0];
 12876       let strLen = recordSize * 2;
 12877       let Buf = this.context.Buf;
 12878       Buf.writeInt32(strLen);
 12880       let GsmPDUHelper = this.context.GsmPDUHelper;
 12881       for (let i = 0; i < mwis.length; i++) {
 12882         GsmPDUHelper.writeHexOctet(mwis[i]);
 12885       Buf.writeStringDelimiter(strLen);
 12888     this.context.ICCIOHelper.updateLinearFixedEF({
 12889       fileId: ICC_EF_MWIS,
 12890       recordNumber: 1, // Update 1st Subscriber Profile.
 12891       dataWriter: dataWriter.bind(this)
 12892     });
 12893   },
 12895   /**
 12896    * Read the SPDI (Service Provider Display Information) from the (U)SIM.
 12898    * See TS 131.102 section 4.2.66 for USIM and TS 51.011 section 10.3.50
 12899    * for SIM.
 12900    */
 12901   readSPDI: function() {
 12902     function callback() {
 12903       let Buf = this.context.Buf;
 12904       let strLen = Buf.readInt32();
 12905       let octetLen = strLen / 2;
 12906       let readLen = 0;
 12907       let endLoop = false;
 12909       let RIL = this.context.RIL;
 12910       RIL.iccInfoPrivate.SPDI = null;
 12912       let GsmPDUHelper = this.context.GsmPDUHelper;
 12913       while ((readLen < octetLen) && !endLoop) {
 12914         let tlvTag = GsmPDUHelper.readHexOctet();
 12915         let tlvLen = GsmPDUHelper.readHexOctet();
 12916         readLen += 2; // For tag and length fields.
 12917         switch (tlvTag) {
 12918         case SPDI_TAG_SPDI:
 12919           // The value part itself is a TLV.
 12920           continue;
 12921         case SPDI_TAG_PLMN_LIST:
 12922           // This PLMN list is what we want.
 12923           RIL.iccInfoPrivate.SPDI = this.readPLMNEntries(tlvLen / 3);
 12924           readLen += tlvLen;
 12925           endLoop = true;
 12926           break;
 12927         default:
 12928           // We don't care about its content if its tag is not SPDI nor
 12929           // PLMN_LIST.
 12930           endLoop = true;
 12931           break;
 12935       // Consume unread octets.
 12936       Buf.seekIncoming((octetLen - readLen) * Buf.PDU_HEX_OCTET_SIZE);
 12937       Buf.readStringDelimiter(strLen);
 12939       if (DEBUG) {
 12940         this.context.debug("SPDI: " + JSON.stringify(RIL.iccInfoPrivate.SPDI));
 12942       let ICCUtilsHelper = this.context.ICCUtilsHelper;
 12943       if (ICCUtilsHelper.updateDisplayCondition()) {
 12944         ICCUtilsHelper.handleICCInfoChange();
 12948     // PLMN List is Servive 51 in USIM, EF_SPDI
 12949     this.context.ICCIOHelper.loadTransparentEF({
 12950       fileId: ICC_EF_SPDI,
 12951       callback: callback.bind(this)
 12952     });
 12953   },
 12955   _readCbmiHelper: function(which) {
 12956     let RIL = this.context.RIL;
 12958     function callback() {
 12959       let Buf = this.context.Buf;
 12960       let strLength = Buf.readInt32();
 12962       // Each Message Identifier takes two octets and each octet is encoded
 12963       // into two chars.
 12964       let numIds = strLength / 4, list = null;
 12965       if (numIds) {
 12966         list = [];
 12967         let GsmPDUHelper = this.context.GsmPDUHelper;
 12968         for (let i = 0, id; i < numIds; i++) {
 12969           id = GsmPDUHelper.readHexOctet() << 8 | GsmPDUHelper.readHexOctet();
 12970           // `Unused entries shall be set to 'FF FF'.`
 12971           if (id != 0xFFFF) {
 12972             list.push(id);
 12973             list.push(id + 1);
 12977       if (DEBUG) {
 12978         this.context.debug(which + ": " + JSON.stringify(list));
 12981       Buf.readStringDelimiter(strLength);
 12983       RIL.cellBroadcastConfigs[which] = list;
 12984       RIL._mergeAllCellBroadcastConfigs();
 12987     function onerror() {
 12988       RIL.cellBroadcastConfigs[which] = null;
 12989       RIL._mergeAllCellBroadcastConfigs();
 12992     let fileId = GLOBAL["ICC_EF_" + which];
 12993     this.context.ICCIOHelper.loadTransparentEF({
 12994       fileId: fileId,
 12995       callback: callback.bind(this),
 12996       onerror: onerror.bind(this)
 12997     });
 12998   },
 13000   /**
 13001    * Read EFcbmi (Cell Broadcast Message Identifier selection)
 13003    * @see 3GPP TS 31.102 v110.02.0 section 4.2.14 EFcbmi
 13004    * @see 3GPP TS 51.011 v5.0.0 section 10.3.13 EFcbmi
 13005    */
 13006   readCBMI: function() {
 13007     this._readCbmiHelper("CBMI");
 13008   },
 13010   /**
 13011    * Read EFcbmid (Cell Broadcast Message Identifier for Data Download)
 13013    * @see 3GPP TS 31.102 v110.02.0 section 4.2.20 EFcbmid
 13014    * @see 3GPP TS 51.011 v5.0.0 section 10.3.26 EFcbmid
 13015    */
 13016   readCBMID: function() {
 13017     this._readCbmiHelper("CBMID");
 13018   },
 13020   /**
 13021    * Read EFcbmir (Cell Broadcast Message Identifier Range selection)
 13023    * @see 3GPP TS 31.102 v110.02.0 section 4.2.22 EFcbmir
 13024    * @see 3GPP TS 51.011 v5.0.0 section 10.3.28 EFcbmir
 13025    */
 13026   readCBMIR: function() {
 13027     let RIL = this.context.RIL;
 13029     function callback() {
 13030       let Buf = this.context.Buf;
 13031       let strLength = Buf.readInt32();
 13033       // Each Message Identifier range takes four octets and each octet is
 13034       // encoded into two chars.
 13035       let numIds = strLength / 8, list = null;
 13036       if (numIds) {
 13037         list = [];
 13038         let GsmPDUHelper = this.context.GsmPDUHelper;
 13039         for (let i = 0, from, to; i < numIds; i++) {
 13040           // `Bytes one and two of each range identifier equal the lower value
 13041           // of a cell broadcast range, bytes three and four equal the upper
 13042           // value of a cell broadcast range.`
 13043           from = GsmPDUHelper.readHexOctet() << 8 | GsmPDUHelper.readHexOctet();
 13044           to = GsmPDUHelper.readHexOctet() << 8 | GsmPDUHelper.readHexOctet();
 13045           // `Unused entries shall be set to 'FF FF'.`
 13046           if ((from != 0xFFFF) && (to != 0xFFFF)) {
 13047             list.push(from);
 13048             list.push(to + 1);
 13052       if (DEBUG) {
 13053         this.context.debug("CBMIR: " + JSON.stringify(list));
 13056       Buf.readStringDelimiter(strLength);
 13058       RIL.cellBroadcastConfigs.CBMIR = list;
 13059       RIL._mergeAllCellBroadcastConfigs();
 13062     function onerror() {
 13063       RIL.cellBroadcastConfigs.CBMIR = null;
 13064       RIL._mergeAllCellBroadcastConfigs();
 13067     this.context.ICCIOHelper.loadTransparentEF({
 13068       fileId: ICC_EF_CBMIR,
 13069       callback: callback.bind(this),
 13070       onerror: onerror.bind(this)
 13071     });
 13072   },
 13074   /**
 13075    * Read OPL (Operator PLMN List) from (U)SIM.
 13077    * See 3GPP TS 31.102 Sec. 4.2.59 for USIM
 13078    *     3GPP TS 51.011 Sec. 10.3.42 for SIM.
 13079    */
 13080   readOPL: function() {
 13081     let ICCIOHelper = this.context.ICCIOHelper;
 13082     let opl = [];
 13083     function callback(options) {
 13084       let Buf = this.context.Buf;
 13085       let GsmPDUHelper = this.context.GsmPDUHelper;
 13087       let strLen = Buf.readInt32();
 13088       // The first 7 bytes are LAI (for UMTS) and the format of LAI is defined
 13089       // in 3GPP TS 23.003, Sec 4.1
 13090       //    +-------------+---------+
 13091       //    | Octet 1 - 3 | MCC/MNC |
 13092       //    +-------------+---------+
 13093       //    | Octet 4 - 7 |   LAC   |
 13094       //    +-------------+---------+
 13095       let mccMnc = [GsmPDUHelper.readHexOctet(),
 13096                     GsmPDUHelper.readHexOctet(),
 13097                     GsmPDUHelper.readHexOctet()];
 13098       if (mccMnc[0] != 0xFF || mccMnc[1] != 0xFF || mccMnc[2] != 0xFF) {
 13099         let oplElement = {};
 13100         let semiOctets = [];
 13101         for (let i = 0; i < mccMnc.length; i++) {
 13102           semiOctets.push((mccMnc[i] & 0xf0) >> 4);
 13103           semiOctets.push(mccMnc[i] & 0x0f);
 13105         let reformat = [semiOctets[1], semiOctets[0], semiOctets[3],
 13106                         semiOctets[5], semiOctets[4], semiOctets[2]];
 13107         let buf = "";
 13108         for (let i = 0; i < reformat.length; i++) {
 13109           if (reformat[i] != 0xF) {
 13110             buf += GsmPDUHelper.semiOctetToBcdChar(reformat[i]);
 13112           if (i === 2) {
 13113             // 0-2: MCC
 13114             oplElement.mcc = buf;
 13115             buf = "";
 13116           } else if (i === 5) {
 13117             // 3-5: MNC
 13118             oplElement.mnc = buf;
 13121         // LAC/TAC
 13122         oplElement.lacTacStart =
 13123           (GsmPDUHelper.readHexOctet() << 8) | GsmPDUHelper.readHexOctet();
 13124         oplElement.lacTacEnd =
 13125           (GsmPDUHelper.readHexOctet() << 8) | GsmPDUHelper.readHexOctet();
 13126         // PLMN Network Name Record Identifier
 13127         oplElement.pnnRecordId = GsmPDUHelper.readHexOctet();
 13128         if (DEBUG) {
 13129           this.context.debug("OPL: [" + (opl.length + 1) + "]: " +
 13130                              JSON.stringify(oplElement));
 13132         opl.push(oplElement);
 13133       } else {
 13134         Buf.seekIncoming(5 * Buf.PDU_HEX_OCTET_SIZE);
 13136       Buf.readStringDelimiter(strLen);
 13138       if (options.p1 < options.totalRecords) {
 13139         ICCIOHelper.loadNextRecord(options);
 13140       } else {
 13141         this.context.RIL.iccInfoPrivate.OPL = opl;
 13145     ICCIOHelper.loadLinearFixedEF({fileId: ICC_EF_OPL,
 13146                                    callback: callback.bind(this)});
 13147   },
 13149   /**
 13150    * Read PNN (PLMN Network Name) from (U)SIM.
 13152    * See 3GPP TS 31.102 Sec. 4.2.58 for USIM
 13153    *     3GPP TS 51.011 Sec. 10.3.41 for SIM.
 13154    */
 13155   readPNN: function() {
 13156     let ICCIOHelper = this.context.ICCIOHelper;
 13157     function callback(options) {
 13158       let pnnElement;
 13159       let Buf = this.context.Buf;
 13160       let strLen = Buf.readInt32();
 13161       let octetLen = strLen / 2;
 13162       let readLen = 0;
 13164       let GsmPDUHelper = this.context.GsmPDUHelper;
 13165       while (readLen < octetLen) {
 13166         let tlvTag = GsmPDUHelper.readHexOctet();
 13168         if (tlvTag == 0xFF) {
 13169           // Unused byte
 13170           readLen++;
 13171           Buf.seekIncoming((octetLen - readLen) * Buf.PDU_HEX_OCTET_SIZE);
 13172           break;
 13175         // Needs this check to avoid initializing twice.
 13176         pnnElement = pnnElement || {};
 13178         let tlvLen = GsmPDUHelper.readHexOctet();
 13180         switch (tlvTag) {
 13181           case PNN_IEI_FULL_NETWORK_NAME:
 13182             pnnElement.fullName = GsmPDUHelper.readNetworkName(tlvLen);
 13183             break;
 13184           case PNN_IEI_SHORT_NETWORK_NAME:
 13185             pnnElement.shortName = GsmPDUHelper.readNetworkName(tlvLen);
 13186             break;
 13187           default:
 13188             Buf.seekIncoming(tlvLen * Buf.PDU_HEX_OCTET_SIZE);
 13189             break;
 13192         readLen += (tlvLen + 2); // +2 for tlvTag and tlvLen
 13194       Buf.readStringDelimiter(strLen);
 13196       if (pnnElement) {
 13197         pnn.push(pnnElement);
 13200       // Will ignore remaining records when got the contents of a record are all 0xff.
 13201       if (pnnElement && options.p1 < options.totalRecords) {
 13202         ICCIOHelper.loadNextRecord(options);
 13203       } else {
 13204         if (DEBUG) {
 13205           for (let i = 0; i < pnn.length; i++) {
 13206             this.context.debug("PNN: [" + i + "]: " + JSON.stringify(pnn[i]));
 13209         this.context.RIL.iccInfoPrivate.PNN = pnn;
 13213     let pnn = [];
 13214     ICCIOHelper.loadLinearFixedEF({fileId: ICC_EF_PNN,
 13215                                    callback: callback.bind(this)});
 13216   },
 13218   /**
 13219    *  Read the list of PLMN (Public Land Mobile Network) entries
 13220    *  We cannot directly rely on readSwappedNibbleBcdToString(),
 13221    *  since it will no correctly handle some corner-cases that are
 13222    *  not a problem in our case (0xFF 0xFF 0xFF).
 13224    *  @param length The number of PLMN records.
 13225    *  @return An array of string corresponding to the PLMNs.
 13226    */
 13227   readPLMNEntries: function(length) {
 13228     let plmnList = [];
 13229     // Each PLMN entry has 3 bytes.
 13230     if (DEBUG) {
 13231       this.context.debug("PLMN entries length = " + length);
 13233     let GsmPDUHelper = this.context.GsmPDUHelper;
 13234     let index = 0;
 13235     while (index < length) {
 13236       // Unused entries will be 0xFFFFFF, according to EF_SPDI
 13237       // specs (TS 131 102, section 4.2.66)
 13238       try {
 13239         let plmn = [GsmPDUHelper.readHexOctet(),
 13240                     GsmPDUHelper.readHexOctet(),
 13241                     GsmPDUHelper.readHexOctet()];
 13242         if (DEBUG) {
 13243           this.context.debug("Reading PLMN entry: [" + index + "]: '" + plmn + "'");
 13245         if (plmn[0] != 0xFF &&
 13246             plmn[1] != 0xFF &&
 13247             plmn[2] != 0xFF) {
 13248           let semiOctets = [];
 13249           for (let idx = 0; idx < plmn.length; idx++) {
 13250             semiOctets.push((plmn[idx] & 0xF0) >> 4);
 13251             semiOctets.push(plmn[idx] & 0x0F);
 13254           // According to TS 24.301, 9.9.3.12, the semi octets is arranged
 13255           // in format:
 13256           // Byte 1: MCC[2] | MCC[1]
 13257           // Byte 2: MNC[3] | MCC[3]
 13258           // Byte 3: MNC[2] | MNC[1]
 13259           // Therefore, we need to rearrange them.
 13260           let reformat = [semiOctets[1], semiOctets[0], semiOctets[3],
 13261                           semiOctets[5], semiOctets[4], semiOctets[2]];
 13262           let buf = "";
 13263           let plmnEntry = {};
 13264           for (let i = 0; i < reformat.length; i++) {
 13265             if (reformat[i] != 0xF) {
 13266               buf += GsmPDUHelper.semiOctetToBcdChar(reformat[i]);
 13268             if (i === 2) {
 13269               // 0-2: MCC
 13270               plmnEntry.mcc = buf;
 13271               buf = "";
 13272             } else if (i === 5) {
 13273               // 3-5: MNC
 13274               plmnEntry.mnc = buf;
 13277           if (DEBUG) {
 13278             this.context.debug("PLMN = " + plmnEntry.mcc + ", " + plmnEntry.mnc);
 13280           plmnList.push(plmnEntry);
 13282       } catch (e) {
 13283         if (DEBUG) {
 13284           this.context.debug("PLMN entry " + index + " is invalid.");
 13286         break;
 13288       index ++;
 13290     return plmnList;
 13291   },
 13293   /**
 13294    * Read the SMS from the ICC.
 13296    * @param recordNumber The number of the record shall be loaded.
 13297    * @param onsuccess    Callback to be called when success.
 13298    * @param onerror      Callback to be called when error.
 13299    */
 13300   readSMS: function(recordNumber, onsuccess, onerror) {
 13301     function callback(options) {
 13302       let Buf = this.context.Buf;
 13303       let strLen = Buf.readInt32();
 13305       // TS 51.011, 10.5.3 EF_SMS
 13306       // b3 b2 b1
 13307       //  0  0  1 message received by MS from network; message read
 13308       //  0  1  1 message received by MS from network; message to be read
 13309       //  1  1  1 MS originating message; message to be sent
 13310       //  1  0  1 MS originating message; message sent to the network:
 13311       let GsmPDUHelper = this.context.GsmPDUHelper;
 13312       let status = GsmPDUHelper.readHexOctet();
 13314       let message = GsmPDUHelper.readMessage();
 13315       message.simStatus = status;
 13317       // Consumes the remaining buffer
 13318       Buf.seekIncoming(Buf.getReadAvailable() - Buf.PDU_HEX_OCTET_SIZE);
 13320       Buf.readStringDelimiter(strLen);
 13322       if (message) {
 13323         onsuccess(message);
 13324       } else {
 13325         onerror("Failed to decode SMS on SIM #" + recordNumber);
 13329     this.context.ICCIOHelper.loadLinearFixedEF({
 13330       fileId: ICC_EF_SMS,
 13331       recordNumber: recordNumber,
 13332       callback: callback.bind(this),
 13333       onerror: onerror
 13334     });
 13335   },
 13336 };
 13338 function RuimRecordHelperObject(aContext) {
 13339   this.context = aContext;
 13341 RuimRecordHelperObject.prototype = {
 13342   context: null,
 13344   fetchRuimRecords: function() {
 13345     this.getIMSI_M();
 13346     this.readCST();
 13347     this.readCDMAHome();
 13348     this.context.RIL.getCdmaSubscription();
 13349   },
 13351   /**
 13352    * Get IMSI_M from CSIM/RUIM.
 13353    * See 3GPP2 C.S0065 Sec. 5.2.2
 13354    */
 13355   getIMSI_M: function() {
 13356     function callback() {
 13357       let Buf = this.context.Buf;
 13358       let strLen = Buf.readInt32();
 13359       let encodedImsi = this.context.GsmPDUHelper.readHexOctetArray(strLen / 2);
 13360       Buf.readStringDelimiter(strLen);
 13362       if ((encodedImsi[CSIM_IMSI_M_PROGRAMMED_BYTE] & 0x80)) { // IMSI_M programmed
 13363         let RIL = this.context.RIL;
 13364         RIL.iccInfoPrivate.imsi = this.decodeIMSI(encodedImsi);
 13365         RIL.sendChromeMessage({rilMessageType: "iccimsi",
 13366                                imsi: RIL.iccInfoPrivate.imsi});
 13368         let ICCUtilsHelper = this.context.ICCUtilsHelper;
 13369         let mccMnc = ICCUtilsHelper.parseMccMncFromImsi(RIL.iccInfoPrivate.imsi);
 13370         if (mccMnc) {
 13371           RIL.iccInfo.mcc = mccMnc.mcc;
 13372           RIL.iccInfo.mnc = mccMnc.mnc;
 13373           ICCUtilsHelper.handleICCInfoChange();
 13378     this.context.ICCIOHelper.loadTransparentEF({
 13379       fileId: ICC_EF_CSIM_IMSI_M,
 13380       callback: callback.bind(this)
 13381     });
 13382   },
 13384   /**
 13385    * Decode IMSI from IMSI_M
 13386    * See 3GPP2 C.S0005 Sec. 2.3.1
 13387    * +---+---------+------------+---+--------+---------+---+---------+--------+
 13388    * |RFU|   MCC   | programmed |RFU|  MNC   |  MIN1   |RFU|   MIN2  |  CLASS |
 13389    * +---+---------+------------+---+--------+---------+---+---------+--------+
 13390    * | 6 | 10 bits |   8 bits   | 1 | 7 bits | 24 bits | 6 | 10 bits | 8 bits |
 13391    * +---+---------+------------+---+--------+---------+---+---------+--------+
 13392    */
 13393   decodeIMSI: function(encodedImsi) {
 13394     // MCC: 10 bits, 3 digits
 13395     let encodedMCC = ((encodedImsi[CSIM_IMSI_M_MCC_BYTE + 1] & 0x03) << 8) +
 13396                       (encodedImsi[CSIM_IMSI_M_MCC_BYTE] & 0xff);
 13397     let mcc = this.decodeIMSIValue(encodedMCC, 3);
 13399     // MNC: 7 bits, 2 digits
 13400     let encodedMNC =  encodedImsi[CSIM_IMSI_M_MNC_BYTE] & 0x7f;
 13401     let mnc = this.decodeIMSIValue(encodedMNC, 2);
 13403     // MIN2: 10 bits, 3 digits
 13404     let encodedMIN2 = ((encodedImsi[CSIM_IMSI_M_MIN2_BYTE + 1] & 0x03) << 8) +
 13405                        (encodedImsi[CSIM_IMSI_M_MIN2_BYTE] & 0xff);
 13406     let min2 = this.decodeIMSIValue(encodedMIN2, 3);
 13408     // MIN1: 10+4+10 bits, 3+1+3 digits
 13409     let encodedMIN1First3 = ((encodedImsi[CSIM_IMSI_M_MIN1_BYTE + 2] & 0xff) << 2) +
 13410                              ((encodedImsi[CSIM_IMSI_M_MIN1_BYTE + 1] & 0xc0) >> 6);
 13411     let min1First3 = this.decodeIMSIValue(encodedMIN1First3, 3);
 13413     let encodedFourthDigit = (encodedImsi[CSIM_IMSI_M_MIN1_BYTE + 1] & 0x3c) >> 2;
 13414     if (encodedFourthDigit > 9) {
 13415       encodedFourthDigit = 0;
 13417     let fourthDigit = encodedFourthDigit.toString();
 13419     let encodedMIN1Last3 = ((encodedImsi[CSIM_IMSI_M_MIN1_BYTE + 1] & 0x03) << 8) +
 13420                             (encodedImsi[CSIM_IMSI_M_MIN1_BYTE] & 0xff);
 13421     let min1Last3 = this.decodeIMSIValue(encodedMIN1Last3, 3);
 13423     return mcc + mnc + min2 + min1First3 + fourthDigit + min1Last3;
 13424   },
 13426   /**
 13427    * Decode IMSI Helper function
 13428    * See 3GPP2 C.S0005 section 2.3.1.1
 13429    */
 13430   decodeIMSIValue: function(encoded, length) {
 13431     let offset = length === 3 ? 111 : 11;
 13432     let value = encoded + offset;
 13434     for (let base = 10, temp = value, i = 0; i < length; i++) {
 13435       if (temp % 10 === 0) {
 13436         value -= base;
 13438       temp = Math.floor(value / base);
 13439       base = base * 10;
 13442     let s = value.toString();
 13443     while (s.length < length) {
 13444       s = "0" + s;
 13447     return s;
 13448   },
 13450   /**
 13451    * Read CDMAHOME for CSIM.
 13452    * See 3GPP2 C.S0023 Sec. 3.4.8.
 13453    */
 13454   readCDMAHome: function() {
 13455     let ICCIOHelper = this.context.ICCIOHelper;
 13457     function callback(options) {
 13458       let Buf = this.context.Buf;
 13459       let GsmPDUHelper = this.context.GsmPDUHelper;
 13461       let strLen = Buf.readInt32();
 13462       let tempOctet = GsmPDUHelper.readHexOctet();
 13463       cdmaHomeSystemId.push(((GsmPDUHelper.readHexOctet() & 0x7f) << 8) | tempOctet);
 13464       tempOctet = GsmPDUHelper.readHexOctet();
 13465       cdmaHomeNetworkId.push(((GsmPDUHelper.readHexOctet() & 0xff) << 8) | tempOctet);
 13467       // Consuming the last octet: band class.
 13468       Buf.seekIncoming(Buf.PDU_HEX_OCTET_SIZE);
 13470       Buf.readStringDelimiter(strLen);
 13471       if (options.p1 < options.totalRecords) {
 13472         ICCIOHelper.loadNextRecord(options);
 13473       } else {
 13474         if (DEBUG) {
 13475           this.context.debug("CDMAHome system id: " +
 13476                              JSON.stringify(cdmaHomeSystemId));
 13477           this.context.debug("CDMAHome network id: " +
 13478                              JSON.stringify(cdmaHomeNetworkId));
 13480         this.context.RIL.cdmaHome = {
 13481           systemId: cdmaHomeSystemId,
 13482           networkId: cdmaHomeNetworkId
 13483         };
 13487     let cdmaHomeSystemId = [], cdmaHomeNetworkId = [];
 13488     ICCIOHelper.loadLinearFixedEF({fileId: ICC_EF_CSIM_CDMAHOME,
 13489                                    callback: callback.bind(this)});
 13490   },
 13492   /**
 13493    * Read CDMA Service Table.
 13494    * See 3GPP2 C.S0023 Sec. 3.4.18
 13495    */
 13496   readCST: function() {
 13497     function callback() {
 13498       let Buf = this.context.Buf;
 13499       let RIL = this.context.RIL;
 13501       let strLen = Buf.readInt32();
 13502       // Each octet is encoded into two chars.
 13503       RIL.iccInfoPrivate.cst =
 13504         this.context.GsmPDUHelper.readHexOctetArray(strLen / 2);
 13505       Buf.readStringDelimiter(strLen);
 13507       if (DEBUG) {
 13508         let str = "";
 13509         for (let i = 0; i < RIL.iccInfoPrivate.cst.length; i++) {
 13510           str += RIL.iccInfoPrivate.cst[i] + ", ";
 13512         this.context.debug("CST: " + str);
 13515       if (this.context.ICCUtilsHelper.isICCServiceAvailable("SPN")) {
 13516         if (DEBUG) this.context.debug("SPN: SPN is available");
 13517         this.readSPN();
 13520     this.context.ICCIOHelper.loadTransparentEF({
 13521       fileId: ICC_EF_CSIM_CST,
 13522       callback: callback.bind(this)
 13523     });
 13524   },
 13526   readSPN: function() {
 13527     function callback() {
 13528       let Buf = this.context.Buf;
 13529       let strLen = Buf.readInt32();
 13530       let octetLen = strLen / 2;
 13532       let GsmPDUHelper = this.context.GsmPDUHelper;
 13533       let displayCondition = GsmPDUHelper.readHexOctet();
 13534       let codingScheme = GsmPDUHelper.readHexOctet();
 13535       // Skip one octet: language indicator.
 13536       Buf.seekIncoming(Buf.PDU_HEX_OCTET_SIZE);
 13537       let readLen = 3;
 13539       // SPN String ends up with 0xff.
 13540       let userDataBuffer = [];
 13542       while (readLen < octetLen) {
 13543         let octet = GsmPDUHelper.readHexOctet();
 13544         readLen++;
 13545         if (octet == 0xff) {
 13546           break;
 13548         userDataBuffer.push(octet);
 13551       this.context.BitBufferHelper.startRead(userDataBuffer);
 13553       let CdmaPDUHelper = this.context.CdmaPDUHelper;
 13554       let msgLen;
 13555       switch (CdmaPDUHelper.getCdmaMsgEncoding(codingScheme)) {
 13556       case PDU_DCS_MSG_CODING_7BITS_ALPHABET:
 13557         msgLen = Math.floor(userDataBuffer.length * 8 / 7);
 13558         break;
 13559       case PDU_DCS_MSG_CODING_8BITS_ALPHABET:
 13560         msgLen = userDataBuffer.length;
 13561         break;
 13562       case PDU_DCS_MSG_CODING_16BITS_ALPHABET:
 13563         msgLen = Math.floor(userDataBuffer.length / 2);
 13564         break;
 13567       let RIL = this.context.RIL;
 13568       RIL.iccInfo.spn = CdmaPDUHelper.decodeCdmaPDUMsg(codingScheme, null, msgLen);
 13569       if (DEBUG) {
 13570         this.context.debug("CDMA SPN: " + RIL.iccInfo.spn +
 13571                            ", Display condition: " + displayCondition);
 13573       RIL.iccInfoPrivate.spnDisplayCondition = displayCondition;
 13574       Buf.seekIncoming((octetLen - readLen) * Buf.PDU_HEX_OCTET_SIZE);
 13575       Buf.readStringDelimiter(strLen);
 13578     this.context.ICCIOHelper.loadTransparentEF({
 13579       fileId: ICC_EF_CSIM_SPN,
 13580       callback: callback.bind(this)
 13581     });
 13583 };
 13585 /**
 13586  * Helper functions for ICC utilities.
 13587  */
 13588 function ICCUtilsHelperObject(aContext) {
 13589   this.context = aContext;
 13591 ICCUtilsHelperObject.prototype = {
 13592   context: null,
 13594   /**
 13595    * Get network names by using EF_OPL and EF_PNN
 13597    * @See 3GPP TS 31.102 sec. 4.2.58 and sec. 4.2.59 for USIM,
 13598    *      3GPP TS 51.011 sec. 10.3.41 and sec. 10.3.42 for SIM.
 13600    * @param mcc   The mobile country code of the network.
 13601    * @param mnc   The mobile network code of the network.
 13602    * @param lac   The location area code of the network.
 13603    */
 13604   getNetworkNameFromICC: function(mcc, mnc, lac) {
 13605     let RIL = this.context.RIL;
 13606     let iccInfoPriv = RIL.iccInfoPrivate;
 13607     let iccInfo = RIL.iccInfo;
 13608     let pnnEntry;
 13610     if (!mcc || !mnc || !lac) {
 13611       return null;
 13614     // We won't get network name if there is no PNN file.
 13615     if (!iccInfoPriv.PNN) {
 13616       return null;
 13619     if (!iccInfoPriv.OPL) {
 13620       // When OPL is not present:
 13621       // According to 3GPP TS 31.102 Sec. 4.2.58 and 3GPP TS 51.011 Sec. 10.3.41,
 13622       // If EF_OPL is not present, the first record in this EF is used for the
 13623       // default network name when registered to the HPLMN.
 13624       // If we haven't get pnnEntry assigned, we should try to assign default
 13625       // value to it.
 13626       if (mcc == iccInfo.mcc && mnc == iccInfo.mnc) {
 13627         pnnEntry = iccInfoPriv.PNN[0];
 13629     } else {
 13630       // According to 3GPP TS 31.102 Sec. 4.2.59 and 3GPP TS 51.011 Sec. 10.3.42,
 13631       // the ME shall use this EF_OPL in association with the EF_PNN in place
 13632       // of any network name stored within the ME's internal list and any network
 13633       // name received when registered to the PLMN.
 13634       let length = iccInfoPriv.OPL ? iccInfoPriv.OPL.length : 0;
 13635       for (let i = 0; i < length; i++) {
 13636         let opl = iccInfoPriv.OPL[i];
 13637         // Try to match the MCC/MNC.
 13638         if (mcc != opl.mcc || mnc != opl.mnc) {
 13639           continue;
 13641         // Try to match the location area code. If current local area code is
 13642         // covered by lac range that specified in the OPL entry, use the PNN
 13643         // that specified in the OPL entry.
 13644         if ((opl.lacTacStart === 0x0 && opl.lacTacEnd == 0xFFFE) ||
 13645             (opl.lacTacStart <= lac && opl.lacTacEnd >= lac)) {
 13646           if (opl.pnnRecordId === 0) {
 13647             // See 3GPP TS 31.102 Sec. 4.2.59 and 3GPP TS 51.011 Sec. 10.3.42,
 13648             // A value of '00' indicates that the name is to be taken from other
 13649             // sources.
 13650             return null;
 13652           pnnEntry = iccInfoPriv.PNN[opl.pnnRecordId - 1];
 13653           break;
 13658     if (!pnnEntry) {
 13659       return null;
 13662     // Return a new object to avoid global variable, PNN, be modified by accident.
 13663     return { fullName: pnnEntry.fullName || "",
 13664              shortName: pnnEntry.shortName || "" };
 13665   },
 13667   /**
 13668    * This will compute the spnDisplay field of the network.
 13669    * See TS 22.101 Annex A and TS 51.011 10.3.11 for details.
 13671    * @return True if some of iccInfo is changed in by this function.
 13672    */
 13673   updateDisplayCondition: function() {
 13674     let RIL = this.context.RIL;
 13676     // If EFspn isn't existed in SIM or it haven't been read yet, we should
 13677     // just set isDisplayNetworkNameRequired = true and
 13678     // isDisplaySpnRequired = false
 13679     let iccInfo = RIL.iccInfo;
 13680     let iccInfoPriv = RIL.iccInfoPrivate;
 13681     let displayCondition = iccInfoPriv.spnDisplayCondition;
 13682     let origIsDisplayNetworkNameRequired = iccInfo.isDisplayNetworkNameRequired;
 13683     let origIsDisplaySPNRequired = iccInfo.isDisplaySpnRequired;
 13685     if (displayCondition === undefined) {
 13686       iccInfo.isDisplayNetworkNameRequired = true;
 13687       iccInfo.isDisplaySpnRequired = false;
 13688     } else if (RIL._isCdma) {
 13689       // CDMA family display rule.
 13690       let cdmaHome = RIL.cdmaHome;
 13691       let cell = RIL.voiceRegistrationState.cell;
 13692       let sid = cell && cell.cdmaSystemId;
 13693       let nid = cell && cell.cdmaNetworkId;
 13695       iccInfo.isDisplayNetworkNameRequired = false;
 13697       // If display condition is 0x0, we don't even need to check network id
 13698       // or system id.
 13699       if (displayCondition === 0x0) {
 13700         iccInfo.isDisplaySpnRequired = false;
 13701       } else {
 13702         // CDMA SPN Display condition dosen't specify whenever network name is
 13703         // reqired.
 13704         if (!cdmaHome ||
 13705             !cdmaHome.systemId ||
 13706             cdmaHome.systemId.length === 0 ||
 13707             cdmaHome.systemId.length != cdmaHome.networkId.length ||
 13708             !sid || !nid) {
 13709           // CDMA Home haven't been ready, or we haven't got the system id and
 13710           // network id of the network we register to, assuming we are in home
 13711           // network.
 13712           iccInfo.isDisplaySpnRequired = true;
 13713         } else {
 13714           // Determine if we are registered in the home service area.
 13715           // System ID and Network ID are described in 3GPP2 C.S0005 Sec. 2.6.5.2.
 13716           let inHomeArea = false;
 13717           for (let i = 0; i < cdmaHome.systemId.length; i++) {
 13718             let homeSid = cdmaHome.systemId[i],
 13719                 homeNid = cdmaHome.networkId[i];
 13720             if (homeSid === 0 || homeNid === 0 // Reserved system id/network id
 13721                || homeSid != sid) {
 13722               continue;
 13724             // According to 3GPP2 C.S0005 Sec. 2.6.5.2, NID number 65535 means
 13725             // all networks in the system should be considered as home.
 13726             if (homeNid == 65535 || homeNid == nid) {
 13727               inHomeArea = true;
 13728               break;
 13731           iccInfo.isDisplaySpnRequired = inHomeArea;
 13734     } else {
 13735       // GSM family display rule.
 13736       let operatorMnc = RIL.operator.mnc;
 13737       let operatorMcc = RIL.operator.mcc;
 13739       // First detect if we are on HPLMN or one of the PLMN
 13740       // specified by the SIM card.
 13741       let isOnMatchingPlmn = false;
 13743       // If the current network is the one defined as mcc/mnc
 13744       // in SIM card, it's okay.
 13745       if (iccInfo.mcc == operatorMcc && iccInfo.mnc == operatorMnc) {
 13746         isOnMatchingPlmn = true;
 13749       // Test to see if operator's mcc/mnc match mcc/mnc of PLMN.
 13750       if (!isOnMatchingPlmn && iccInfoPriv.SPDI) {
 13751         let iccSpdi = iccInfoPriv.SPDI; // PLMN list
 13752         for (let plmn in iccSpdi) {
 13753           let plmnMcc = iccSpdi[plmn].mcc;
 13754           let plmnMnc = iccSpdi[plmn].mnc;
 13755           isOnMatchingPlmn = (plmnMcc == operatorMcc) && (plmnMnc == operatorMnc);
 13756           if (isOnMatchingPlmn) {
 13757             break;
 13762       if (isOnMatchingPlmn) {
 13763         // The first bit of display condition tells us if we should display
 13764         // registered PLMN.
 13765         if (DEBUG) {
 13766           this.context.debug("PLMN is HPLMN or PLMN " + "is in PLMN list");
 13769         // TS 31.102 Sec. 4.2.66 and TS 51.011 Sec. 10.3.50
 13770         // EF_SPDI contains a list of PLMNs in which the Service Provider Name
 13771         // shall be displayed.
 13772         iccInfo.isDisplaySpnRequired = true;
 13773         iccInfo.isDisplayNetworkNameRequired = (displayCondition & 0x01) !== 0;
 13774       } else {
 13775         // The second bit of display condition tells us if we should display
 13776         // registered PLMN.
 13777         if (DEBUG) {
 13778           this.context.debug("PLMN isn't HPLMN and PLMN isn't in PLMN list");
 13781         // We didn't found the requirement of displaying network name if
 13782         // current PLMN isn't HPLMN nor one of PLMN in SPDI. So we keep
 13783         // isDisplayNetworkNameRequired false.
 13784         iccInfo.isDisplayNetworkNameRequired = false;
 13785         iccInfo.isDisplaySpnRequired = (displayCondition & 0x02) === 0;
 13789     if (DEBUG) {
 13790       this.context.debug("isDisplayNetworkNameRequired = " +
 13791                          iccInfo.isDisplayNetworkNameRequired);
 13792       this.context.debug("isDisplaySpnRequired = " + iccInfo.isDisplaySpnRequired);
 13795     return ((origIsDisplayNetworkNameRequired !== iccInfo.isDisplayNetworkNameRequired) ||
 13796             (origIsDisplaySPNRequired !== iccInfo.isDisplaySpnRequired));
 13797   },
 13799   decodeSimTlvs: function(tlvsLen) {
 13800     let GsmPDUHelper = this.context.GsmPDUHelper;
 13802     let index = 0;
 13803     let tlvs = [];
 13804     while (index < tlvsLen) {
 13805       let simTlv = {
 13806         tag : GsmPDUHelper.readHexOctet(),
 13807         length : GsmPDUHelper.readHexOctet(),
 13808       };
 13809       simTlv.value = GsmPDUHelper.readHexOctetArray(simTlv.length);
 13810       tlvs.push(simTlv);
 13811       index += simTlv.length + 2; // The length of 'tag' and 'length' field.
 13813     return tlvs;
 13814   },
 13816   /**
 13817    * Parse those TLVs and convert it to an object.
 13818    */
 13819   parsePbrTlvs: function(pbrTlvs) {
 13820     let pbr = {};
 13821     for (let i = 0; i < pbrTlvs.length; i++) {
 13822       let pbrTlv = pbrTlvs[i];
 13823       let anrIndex = 0;
 13824       for (let j = 0; j < pbrTlv.value.length; j++) {
 13825         let tlv = pbrTlv.value[j];
 13826         let tagName = USIM_TAG_NAME[tlv.tag];
 13828         // ANR could have multiple files. We save it as anr0, anr1,...etc.
 13829         if (tlv.tag == ICC_USIM_EFANR_TAG) {
 13830           tagName += anrIndex;
 13831           anrIndex++;
 13833         pbr[tagName] = tlv;
 13834         pbr[tagName].fileType = pbrTlv.tag;
 13835         pbr[tagName].fileId = (tlv.value[0] << 8) | tlv.value[1];
 13836         pbr[tagName].sfi = tlv.value[2];
 13838         // For Type 2, the order of files is in the same order in IAP.
 13839         if (pbrTlv.tag == ICC_USIM_TYPE2_TAG) {
 13840           pbr[tagName].indexInIAP = j;
 13845     return pbr;
 13846   },
 13848   /**
 13849    * Update the ICC information to RadioInterfaceLayer.
 13850    */
 13851   handleICCInfoChange: function() {
 13852     let RIL = this.context.RIL;
 13853     RIL.iccInfo.rilMessageType = "iccinfochange";
 13854     RIL.sendChromeMessage(RIL.iccInfo);
 13855   },
 13857   /**
 13858    * Get whether specificed (U)SIM service is available.
 13860    * @param geckoService
 13861    *        Service name like "ADN", "BDN", etc.
 13863    * @return true if the service is enabled, false otherwise.
 13864    */
 13865   isICCServiceAvailable: function(geckoService) {
 13866     let RIL = this.context.RIL;
 13867     let serviceTable = RIL._isCdma ? RIL.iccInfoPrivate.cst:
 13868                                      RIL.iccInfoPrivate.sst;
 13869     let index, bitmask;
 13870     if (RIL.appType == CARD_APPTYPE_SIM || RIL.appType == CARD_APPTYPE_RUIM) {
 13871       /**
 13872        * Service id is valid in 1..N, and 2 bits are used to code each service.
 13874        * +----+--  --+----+----+
 13875        * | b8 | ...  | b2 | b1 |
 13876        * +----+--  --+----+----+
 13878        * b1 = 0, service not allocated.
 13879        *      1, service allocated.
 13880        * b2 = 0, service not activatd.
 13881        *      1, service allocated.
 13883        * @see 3GPP TS 51.011 10.3.7.
 13884        */
 13885       let simService;
 13886       if (RIL.appType == CARD_APPTYPE_SIM) {
 13887         simService = GECKO_ICC_SERVICES.sim[geckoService];
 13888       } else {
 13889         simService = GECKO_ICC_SERVICES.ruim[geckoService];
 13891       if (!simService) {
 13892         return false;
 13894       simService -= 1;
 13895       index = Math.floor(simService / 4);
 13896       bitmask = 2 << ((simService % 4) << 1);
 13897     } else if (RIL.appType == CARD_APPTYPE_USIM) {
 13898       /**
 13899        * Service id is valid in 1..N, and 1 bit is used to code each service.
 13901        * +----+--  --+----+----+
 13902        * | b8 | ...  | b2 | b1 |
 13903        * +----+--  --+----+----+
 13905        * b1 = 0, service not avaiable.
 13906        *      1, service available.
 13907        * b2 = 0, service not avaiable.
 13908        *      1, service available.
 13910        * @see 3GPP TS 31.102 4.2.8.
 13911        */
 13912       let usimService = GECKO_ICC_SERVICES.usim[geckoService];
 13913       if (!usimService) {
 13914         return false;
 13916       usimService -= 1;
 13917       index = Math.floor(usimService / 8);
 13918       bitmask = 1 << ((usimService % 8) << 0);
 13921     return (serviceTable !== null) &&
 13922            (index < serviceTable.length) &&
 13923            ((serviceTable[index] & bitmask) !== 0);
 13924   },
 13926   /**
 13927    * Check if the string is of GSM default 7-bit coded alphabets with bit 8
 13928    * set to 0.
 13930    * @param str  String to be checked.
 13931    */
 13932   isGsm8BitAlphabet: function(str) {
 13933     if (!str) {
 13934       return false;
 13937     const langTable = PDU_NL_LOCKING_SHIFT_TABLES[PDU_NL_IDENTIFIER_DEFAULT];
 13938     const langShiftTable = PDU_NL_SINGLE_SHIFT_TABLES[PDU_NL_IDENTIFIER_DEFAULT];
 13940     for (let i = 0; i < str.length; i++) {
 13941       let c = str.charAt(i);
 13942       let octet = langTable.indexOf(c);
 13943       if (octet == -1) {
 13944         octet = langShiftTable.indexOf(c);
 13945         if (octet == -1) {
 13946           return false;
 13951     return true;
 13952   },
 13954   /**
 13955    * Parse MCC/MNC from IMSI. If there is no available value for the length of
 13956    * mnc, it will use the data in MCC table to parse.
 13958    * @param imsi
 13959    *        The imsi of icc.
 13960    * @param mncLength [optional]
 13961    *        The length of mnc.
 13963    * @return An object contains the parsing result of mcc and mnc.
 13964    *         Or null if any error occurred.
 13965    */
 13966   parseMccMncFromImsi: function(imsi, mncLength) {
 13967     if (!imsi) {
 13968       return null;
 13971     // MCC is the first 3 digits of IMSI.
 13972     let mcc = imsi.substr(0,3);
 13973     if (!mncLength) {
 13974       // Check the MCC table to decide the length of MNC.
 13975       let index = MCC_TABLE_FOR_MNC_LENGTH_IS_3.indexOf(mcc);
 13976       mncLength = (index !== -1) ? 3 : 2;
 13978     let mnc = imsi.substr(3, mncLength);
 13979     if (DEBUG) {
 13980       this.context.debug("IMSI: " + imsi + " MCC: " + mcc + " MNC: " + mnc);
 13983     return { mcc: mcc, mnc: mnc};
 13984   },
 13985 };
 13987 /**
 13988  * Helper for ICC Contacts.
 13989  */
 13990 function ICCContactHelperObject(aContext) {
 13991   this.context = aContext;
 13993 ICCContactHelperObject.prototype = {
 13994   context: null,
 13996   /**
 13997    * Helper function to check DF_PHONEBOOK.
 13998    */
 13999   hasDfPhoneBook: function(appType) {
 14000     switch (appType) {
 14001       case CARD_APPTYPE_SIM:
 14002         return false;
 14003       case CARD_APPTYPE_USIM:
 14004         return true;
 14005       case CARD_APPTYPE_RUIM:
 14006         let ICCUtilsHelper = this.context.ICCUtilsHelper;
 14007         return ICCUtilsHelper.isICCServiceAvailable("ENHANCED_PHONEBOOK");
 14008       default:
 14009         return false;
 14011   },
 14013   /**
 14014    * Helper function to read ICC contacts.
 14016    * @param appType       One of CARD_APPTYPE_*.
 14017    * @param contactType   "adn" or "fdn".
 14018    * @param onsuccess     Callback to be called when success.
 14019    * @param onerror       Callback to be called when error.
 14020    */
 14021   readICCContacts: function(appType, contactType, onsuccess, onerror) {
 14022     let ICCRecordHelper = this.context.ICCRecordHelper;
 14024     switch (contactType) {
 14025       case "adn":
 14026         if (!this.hasDfPhoneBook(appType)) {
 14027           ICCRecordHelper.readADNLike(ICC_EF_ADN, onsuccess, onerror);
 14028         } else {
 14029           this.readUSimContacts(onsuccess, onerror);
 14031         break;
 14032       case "fdn":
 14033         ICCRecordHelper.readADNLike(ICC_EF_FDN, onsuccess, onerror);
 14034         break;
 14035       default:
 14036         if (DEBUG) {
 14037           this.context.debug("Unsupported contactType :" + contactType);
 14039         onerror(CONTACT_ERR_CONTACT_TYPE_NOT_SUPPORTED);
 14040         break;
 14042   },
 14044   /**
 14045    * Helper function to find free contact record.
 14047    * @param appType       One of CARD_APPTYPE_*.
 14048    * @param contactType   "adn" or "fdn".
 14049    * @param onsuccess     Callback to be called when success.
 14050    * @param onerror       Callback to be called when error.
 14051    */
 14052   findFreeICCContact: function(appType, contactType, onsuccess, onerror) {
 14053     let ICCRecordHelper = this.context.ICCRecordHelper;
 14055     switch (contactType) {
 14056       case "adn":
 14057         if (!this.hasDfPhoneBook(appType)) {
 14058           ICCRecordHelper.findFreeRecordId(ICC_EF_ADN, onsuccess.bind(null, 0), onerror);
 14059         } else {
 14060           let gotPbrCb = function gotPbrCb(pbrs) {
 14061             this.findUSimFreeADNRecordId(pbrs, onsuccess, onerror);
 14062           }.bind(this);
 14064           ICCRecordHelper.readPBR(gotPbrCb, onerror);
 14066         break;
 14067       case "fdn":
 14068         ICCRecordHelper.findFreeRecordId(ICC_EF_FDN, onsuccess.bind(null, 0), onerror);
 14069         break;
 14070       default:
 14071         if (DEBUG) {
 14072           this.context.debug("Unsupported contactType :" + contactType);
 14074         onerror(CONTACT_ERR_CONTACT_TYPE_NOT_SUPPORTED);
 14075         break;
 14077   },
 14079    /**
 14080     * Find free ADN record id in USIM.
 14082     * @param pbrs          All Phonebook Reference Files read.
 14083     * @param onsuccess     Callback to be called when success.
 14084     * @param onerror       Callback to be called when error.
 14085     */
 14086   findUSimFreeADNRecordId: function(pbrs, onsuccess, onerror) {
 14087     let ICCRecordHelper = this.context.ICCRecordHelper;
 14089     (function findFreeRecordId(pbrIndex) {
 14090       if (pbrIndex >= pbrs.length) {
 14091         if (DEBUG) {
 14092           this.context.debug(CONTACT_ERR_NO_FREE_RECORD_FOUND);
 14094         onerror(CONTACT_ERR_NO_FREE_RECORD_FOUND);
 14095         return;
 14098       let pbr = pbrs[pbrIndex];
 14099       ICCRecordHelper.findFreeRecordId(
 14100         pbr.adn.fileId,
 14101         onsuccess.bind(this, pbrIndex),
 14102         findFreeRecordId.bind(null, pbrIndex + 1));
 14103     })(0);
 14104   },
 14106   /**
 14107    * Helper function to add a new ICC contact.
 14109    * @param appType       One of CARD_APPTYPE_*.
 14110    * @param contactType   "adn" or "fdn".
 14111    * @param contact       The contact will be added.
 14112    * @param pin2          PIN2 is required for FDN.
 14113    * @param onsuccess     Callback to be called when success.
 14114    * @param onerror       Callback to be called when error.
 14115    */
 14116   addICCContact: function(appType, contactType, contact, pin2, onsuccess, onerror) {
 14117     let foundFreeCb = (function foundFreeCb(pbrIndex, recordId) {
 14118       contact.pbrIndex = pbrIndex;
 14119       contact.recordId = recordId;
 14120       this.updateICCContact(appType, contactType, contact, pin2, onsuccess, onerror);
 14121     }).bind(this);
 14123     // Find free record first.
 14124     this.findFreeICCContact(appType, contactType, foundFreeCb, onerror);
 14125   },
 14127   /**
 14128    * Helper function to update ICC contact.
 14130    * @param appType       One of CARD_APPTYPE_*.
 14131    * @param contactType   "adn" or "fdn".
 14132    * @param contact       The contact will be updated.
 14133    * @param pin2          PIN2 is required for FDN.
 14134    * @param onsuccess     Callback to be called when success.
 14135    * @param onerror       Callback to be called when error.
 14136    */
 14137   updateICCContact: function(appType, contactType, contact, pin2, onsuccess, onerror) {
 14138     let ICCRecordHelper = this.context.ICCRecordHelper;
 14140     switch (contactType) {
 14141       case "adn":
 14142         if (!this.hasDfPhoneBook(appType)) {
 14143           ICCRecordHelper.updateADNLike(ICC_EF_ADN, contact, null, onsuccess, onerror);
 14144         } else {
 14145           this.updateUSimContact(contact, onsuccess, onerror);
 14147         break;
 14148       case "fdn":
 14149         if (!pin2) {
 14150           onerror(GECKO_ERROR_SIM_PIN2);
 14151           return;
 14153         ICCRecordHelper.updateADNLike(ICC_EF_FDN, contact, pin2, onsuccess, onerror);
 14154         break;
 14155       default:
 14156         if (DEBUG) {
 14157           this.context.debug("Unsupported contactType :" + contactType);
 14159         onerror(CONTACT_ERR_CONTACT_TYPE_NOT_SUPPORTED);
 14160         break;
 14162   },
 14164   /**
 14165    * Read contacts from USIM.
 14167    * @param onsuccess     Callback to be called when success.
 14168    * @param onerror       Callback to be called when error.
 14169    */
 14170   readUSimContacts: function(onsuccess, onerror) {
 14171     let gotPbrCb = function gotPbrCb(pbrs) {
 14172       this.readAllPhonebookSets(pbrs, onsuccess, onerror);
 14173     }.bind(this);
 14175     this.context.ICCRecordHelper.readPBR(gotPbrCb, onerror);
 14176   },
 14178   /**
 14179    * Read all Phonebook sets.
 14181    * @param pbrs          All Phonebook Reference Files read.
 14182    * @param onsuccess     Callback to be called when success.
 14183    * @param onerror       Callback to be called when error.
 14184    */
 14185   readAllPhonebookSets: function(pbrs, onsuccess, onerror) {
 14186     let allContacts = [], pbrIndex = 0;
 14187     let readPhonebook = function readPhonebook(contacts) {
 14188       if (contacts) {
 14189         allContacts = allContacts.concat(contacts);
 14192       let cLen = contacts ? contacts.length : 0;
 14193       for (let i = 0; i < cLen; i++) {
 14194         contacts[i].pbrIndex = pbrIndex;
 14197       pbrIndex++;
 14198       if (pbrIndex >= pbrs.length) {
 14199         if (onsuccess) {
 14200           onsuccess(allContacts);
 14202         return;
 14205       this.readPhonebookSet(pbrs[pbrIndex], readPhonebook, onerror);
 14206     }.bind(this);
 14208     this.readPhonebookSet(pbrs[pbrIndex], readPhonebook, onerror);
 14209   },
 14211   /**
 14212    * Read from Phonebook Reference File.
 14214    * @param pbr           Phonebook Reference File to be read.
 14215    * @param onsuccess     Callback to be called when success.
 14216    * @param onerror       Callback to be called when error.
 14217    */
 14218   readPhonebookSet: function(pbr, onsuccess, onerror) {
 14219     let gotAdnCb = function gotAdnCb(contacts) {
 14220       this.readSupportedPBRFields(pbr, contacts, onsuccess, onerror);
 14221     }.bind(this);
 14223     this.context.ICCRecordHelper.readADNLike(pbr.adn.fileId, gotAdnCb, onerror);
 14224   },
 14226   /**
 14227    * Read supported Phonebook fields.
 14229    * @param pbr         Phone Book Reference file.
 14230    * @param contacts    Contacts stored on ICC.
 14231    * @param onsuccess   Callback to be called when success.
 14232    * @param onerror     Callback to be called when error.
 14233    */
 14234   readSupportedPBRFields: function(pbr, contacts, onsuccess, onerror) {
 14235     let fieldIndex = 0;
 14236     (function readField() {
 14237       let field = USIM_PBR_FIELDS[fieldIndex];
 14238       fieldIndex += 1;
 14239       if (!field) {
 14240         if (onsuccess) {
 14241           onsuccess(contacts);
 14243         return;
 14246       this.readPhonebookField(pbr, contacts, field, readField.bind(this), onerror);
 14247     }).call(this);
 14248   },
 14250   /**
 14251    * Read Phonebook field.
 14253    * @param pbr           The phonebook reference file.
 14254    * @param contacts      Contacts stored on ICC.
 14255    * @param field         Phonebook field to be retrieved.
 14256    * @param onsuccess     Callback to be called when success.
 14257    * @param onerror       Callback to be called when error.
 14258    */
 14259   readPhonebookField: function(pbr, contacts, field, onsuccess, onerror) {
 14260     if (!pbr[field]) {
 14261       if (onsuccess) {
 14262         onsuccess(contacts);
 14264       return;
 14267     (function doReadContactField(n) {
 14268       if (n >= contacts.length) {
 14269         // All contact's fields are read.
 14270         if (onsuccess) {
 14271           onsuccess(contacts);
 14273         return;
 14276       // get n-th contact's field.
 14277       this.readContactField(pbr, contacts[n], field,
 14278                             doReadContactField.bind(this, n + 1), onerror);
 14279     }).call(this, 0);
 14280   },
 14282   /**
 14283    * Read contact's field from USIM.
 14285    * @param pbr           The phonebook reference file.
 14286    * @param contact       The contact needs to get field.
 14287    * @param field         Phonebook field to be retrieved.
 14288    * @param onsuccess     Callback to be called when success.
 14289    * @param onerror       Callback to be called when error.
 14290    */
 14291   readContactField: function(pbr, contact, field, onsuccess, onerror) {
 14292     let gotRecordIdCb = function gotRecordIdCb(recordId) {
 14293       if (recordId == 0xff) {
 14294         if (onsuccess) {
 14295           onsuccess();
 14297         return;
 14300       let fileId = pbr[field].fileId;
 14301       let fileType = pbr[field].fileType;
 14302       let gotFieldCb = function gotFieldCb(value) {
 14303         if (value) {
 14304           // Move anr0 anr1,.. into anr[].
 14305           if (field.startsWith(USIM_PBR_ANR)) {
 14306             if (!contact[USIM_PBR_ANR]) {
 14307               contact[USIM_PBR_ANR] = [];
 14309             contact[USIM_PBR_ANR].push(value);
 14310           } else {
 14311             contact[field] = value;
 14315         if (onsuccess) {
 14316           onsuccess();
 14318       }.bind(this);
 14320       let ICCRecordHelper = this.context.ICCRecordHelper;
 14321       // Detect EF to be read, for anr, it could have anr0, anr1,...
 14322       let ef = field.startsWith(USIM_PBR_ANR) ? USIM_PBR_ANR : field;
 14323       switch (ef) {
 14324         case USIM_PBR_EMAIL:
 14325           ICCRecordHelper.readEmail(fileId, fileType, recordId, gotFieldCb, onerror);
 14326           break;
 14327         case USIM_PBR_ANR:
 14328           ICCRecordHelper.readANR(fileId, fileType, recordId, gotFieldCb, onerror);
 14329           break;
 14330         default:
 14331           if (DEBUG) {
 14332             this.context.debug("Unsupported field :" + field);
 14334           onerror(CONTACT_ERR_FIELD_NOT_SUPPORTED);
 14335           break;
 14337     }.bind(this);
 14339     this.getContactFieldRecordId(pbr, contact, field, gotRecordIdCb, onerror);
 14340   },
 14342   /**
 14343    * Get the recordId.
 14345    * If the fileType of field is ICC_USIM_TYPE1_TAG, use corresponding ADN recordId.
 14346    * otherwise get the recordId from IAP.
 14348    * @see TS 131.102, clause 4.4.2.2
 14350    * @param pbr          The phonebook reference file.
 14351    * @param contact      The contact will be updated.
 14352    * @param onsuccess    Callback to be called when success.
 14353    * @param onerror      Callback to be called when error.
 14354    */
 14355   getContactFieldRecordId: function(pbr, contact, field, onsuccess, onerror) {
 14356     if (pbr[field].fileType == ICC_USIM_TYPE1_TAG) {
 14357       // If the file type is ICC_USIM_TYPE1_TAG, use corresponding ADN recordId.
 14358       if (onsuccess) {
 14359         onsuccess(contact.recordId);
 14361     } else if (pbr[field].fileType == ICC_USIM_TYPE2_TAG) {
 14362       // If the file type is ICC_USIM_TYPE2_TAG, the recordId shall be got from IAP.
 14363       let gotIapCb = function gotIapCb(iap) {
 14364         let indexInIAP = pbr[field].indexInIAP;
 14365         let recordId = iap[indexInIAP];
 14367         if (onsuccess) {
 14368           onsuccess(recordId);
 14370       }.bind(this);
 14372       this.context.ICCRecordHelper.readIAP(pbr.iap.fileId, contact.recordId,
 14373                                            gotIapCb, onerror);
 14374     } else {
 14375       if (DEBUG) {
 14376         this.context.debug("USIM PBR files in Type 3 format are not supported.");
 14378       onerror(CONTACT_ERR_REQUEST_NOT_SUPPORTED);
 14380   },
 14382   /**
 14383    * Update USIM contact.
 14385    * @param contact       The contact will be updated.
 14386    * @param onsuccess     Callback to be called when success.
 14387    * @param onerror       Callback to be called when error.
 14388    */
 14389   updateUSimContact: function(contact, onsuccess, onerror) {
 14390     let gotPbrCb = function gotPbrCb(pbrs) {
 14391       let pbr = pbrs[contact.pbrIndex];
 14392       if (!pbr) {
 14393         if (DEBUG) {
 14394           this.context.debug(CONTACT_ERR_CANNOT_ACCESS_PHONEBOOK);
 14396         onerror(CONTACT_ERR_CANNOT_ACCESS_PHONEBOOK);
 14397         return;
 14399       this.updatePhonebookSet(pbr, contact, onsuccess, onerror);
 14400     }.bind(this);
 14402     this.context.ICCRecordHelper.readPBR(gotPbrCb, onerror);
 14403   },
 14405   /**
 14406    * Update fields in Phonebook Reference File.
 14408    * @param pbr           Phonebook Reference File to be read.
 14409    * @param onsuccess     Callback to be called when success.
 14410    * @param onerror       Callback to be called when error.
 14411    */
 14412   updatePhonebookSet: function(pbr, contact, onsuccess, onerror) {
 14413     let updateAdnCb = function() {
 14414       this.updateSupportedPBRFields(pbr, contact, onsuccess, onerror);
 14415     }.bind(this);
 14417     this.context.ICCRecordHelper.updateADNLike(pbr.adn.fileId, contact, null,
 14418                                                updateAdnCb, onerror);
 14419   },
 14421   /**
 14422    * Update supported Phonebook fields.
 14424    * @param pbr         Phone Book Reference file.
 14425    * @param contact     Contact to be updated.
 14426    * @param onsuccess   Callback to be called when success.
 14427    * @param onerror     Callback to be called when error.
 14428    */
 14429   updateSupportedPBRFields: function(pbr, contact, onsuccess, onerror) {
 14430     let fieldIndex = 0;
 14431     (function updateField() {
 14432       let field = USIM_PBR_FIELDS[fieldIndex];
 14433       fieldIndex += 1;
 14434       if (!field) {
 14435         if (onsuccess) {
 14436           onsuccess();
 14438         return;
 14441       // Check if PBR has this field.
 14442       if (!pbr[field]) {
 14443         updateField.call(this);
 14444         return;
 14447       this.updateContactField(pbr, contact, field, updateField.bind(this), onerror);
 14448     }).call(this);
 14449   },
 14451   /**
 14452    * Update contact's field from USIM.
 14454    * @param pbr           The phonebook reference file.
 14455    * @param contact       The contact needs to be updated.
 14456    * @param field         Phonebook field to be updated.
 14457    * @param onsuccess     Callback to be called when success.
 14458    * @param onerror       Callback to be called when error.
 14459    */
 14460   updateContactField: function(pbr, contact, field, onsuccess, onerror) {
 14461     if (pbr[field].fileType === ICC_USIM_TYPE1_TAG) {
 14462       this.updateContactFieldType1(pbr, contact, field, onsuccess, onerror);
 14463     } else if (pbr[field].fileType === ICC_USIM_TYPE2_TAG) {
 14464       this.updateContactFieldType2(pbr, contact, field, onsuccess, onerror);
 14465     } else {
 14466       if (DEBUG) {
 14467         this.context.debug("USIM PBR files in Type 3 format are not supported.");
 14469       onerror(CONTACT_ERR_REQUEST_NOT_SUPPORTED);
 14471   },
 14473   /**
 14474    * Update Type 1 USIM contact fields.
 14476    * @param pbr           The phonebook reference file.
 14477    * @param contact       The contact needs to be updated.
 14478    * @param field         Phonebook field to be updated.
 14479    * @param onsuccess     Callback to be called when success.
 14480    * @param onerror       Callback to be called when error.
 14481    */
 14482   updateContactFieldType1: function(pbr, contact, field, onsuccess, onerror) {
 14483     let ICCRecordHelper = this.context.ICCRecordHelper;
 14485     if (field === USIM_PBR_EMAIL) {
 14486       ICCRecordHelper.updateEmail(pbr, contact.recordId, contact.email, null, onsuccess, onerror);
 14487     } else if (field === USIM_PBR_ANR0) {
 14488       let anr = Array.isArray(contact.anr) ? contact.anr[0] : null;
 14489       ICCRecordHelper.updateANR(pbr, contact.recordId, anr, null, onsuccess, onerror);
 14490     } else {
 14491      if (DEBUG) {
 14492        this.context.debug("Unsupported field :" + field);
 14494      onerror(CONTACT_ERR_FIELD_NOT_SUPPORTED);
 14496   },
 14498   /**
 14499    * Update Type 2 USIM contact fields.
 14501    * @param pbr           The phonebook reference file.
 14502    * @param contact       The contact needs to be updated.
 14503    * @param field         Phonebook field to be updated.
 14504    * @param onsuccess     Callback to be called when success.
 14505    * @param onerror       Callback to be called when error.
 14506    */
 14507   updateContactFieldType2: function(pbr, contact, field, onsuccess, onerror) {
 14508     let ICCRecordHelper = this.context.ICCRecordHelper;
 14510     // Case 1 : EF_IAP[adnRecordId] doesn't have a value(0xff)
 14511     //   Find a free recordId for EF_field
 14512     //   Update field with that free recordId.
 14513     //   Update IAP.
 14514     //
 14515     // Case 2: EF_IAP[adnRecordId] has a value
 14516     //   update EF_field[iap[field.indexInIAP]]
 14518     let gotIapCb = function gotIapCb(iap) {
 14519       let recordId = iap[pbr[field].indexInIAP];
 14520       if (recordId === 0xff) {
 14521         // If the value in IAP[index] is 0xff, which means the contact stored on
 14522         // the SIM doesn't have the additional attribute (email or anr).
 14523         // So if the contact to be updated doesn't have the attribute either,
 14524         // we don't have to update it.
 14525         if ((field === USIM_PBR_EMAIL && contact.email) ||
 14526             (field === USIM_PBR_ANR0 &&
 14527              (Array.isArray(contact.anr) && contact.anr[0]))) {
 14528           // Case 1.
 14529           this.addContactFieldType2(pbr, contact, field, onsuccess, onerror);
 14530         } else {
 14531           if (onsuccess) {
 14532             onsuccess();
 14535         return;
 14538       // Case 2.
 14539       if (field === USIM_PBR_EMAIL) {
 14540         ICCRecordHelper.updateEmail(pbr, recordId, contact.email, contact.recordId, onsuccess, onerror);
 14541       } else if (field === USIM_PBR_ANR0) {
 14542         let anr = Array.isArray(contact.anr) ? contact.anr[0] : null;
 14543         ICCRecordHelper.updateANR(pbr, recordId, anr, contact.recordId, onsuccess, onerror);
 14544       } else {
 14545         if (DEBUG) {
 14546           this.context.debug("Unsupported field :" + field);
 14548         onerror(CONTACT_ERR_FIELD_NOT_SUPPORTED);
 14551     }.bind(this);
 14553     ICCRecordHelper.readIAP(pbr.iap.fileId, contact.recordId, gotIapCb, onerror);
 14554   },
 14556   /**
 14557    * Add Type 2 USIM contact fields.
 14559    * @param pbr           The phonebook reference file.
 14560    * @param contact       The contact needs to be updated.
 14561    * @param field         Phonebook field to be updated.
 14562    * @param onsuccess     Callback to be called when success.
 14563    * @param onerror       Callback to be called when error.
 14564    */
 14565   addContactFieldType2: function(pbr, contact, field, onsuccess, onerror) {
 14566     let ICCRecordHelper = this.context.ICCRecordHelper;
 14568     let successCb = function successCb(recordId) {
 14569       let updateCb = function updateCb() {
 14570         this.updateContactFieldIndexInIAP(pbr, contact.recordId, field, recordId, onsuccess, onerror);
 14571       }.bind(this);
 14573       if (field === USIM_PBR_EMAIL) {
 14574         ICCRecordHelper.updateEmail(pbr, recordId, contact.email, contact.recordId, updateCb, onerror);
 14575       } else if (field === USIM_PBR_ANR0) {
 14576         ICCRecordHelper.updateANR(pbr, recordId, contact.anr[0], contact.recordId, updateCb, onerror);
 14578     }.bind(this);
 14580     let errorCb = function errorCb(errorMsg) {
 14581       if (DEBUG) {
 14582         this.context.debug(errorMsg + " USIM field " + field);
 14584       onerror(errorMsg);
 14585     }.bind(this);
 14587     ICCRecordHelper.findFreeRecordId(pbr[field].fileId, successCb, errorCb);
 14588   },
 14590   /**
 14591    * Update IAP value.
 14593    * @param pbr           The phonebook reference file.
 14594    * @param recordNumber  The record identifier of EF_IAP.
 14595    * @param field         Phonebook field.
 14596    * @param value         The value of 'field' in IAP.
 14597    * @param onsuccess     Callback to be called when success.
 14598    * @param onerror       Callback to be called when error.
 14600    */
 14601   updateContactFieldIndexInIAP: function(pbr, recordNumber, field, value, onsuccess, onerror) {
 14602     let ICCRecordHelper = this.context.ICCRecordHelper;
 14604     let gotIAPCb = function gotIAPCb(iap) {
 14605       iap[pbr[field].indexInIAP] = value;
 14606       ICCRecordHelper.updateIAP(pbr.iap.fileId, recordNumber, iap, onsuccess, onerror);
 14607     }.bind(this);
 14608     ICCRecordHelper.readIAP(pbr.iap.fileId, recordNumber, gotIAPCb, onerror);
 14609   },
 14610 };
 14612 /**
 14613  * Global stuff.
 14614  */
 14616 function Context(aClientId) {
 14617   this.clientId = aClientId;
 14619   this.Buf = new BufObject(this);
 14620   this.Buf.init();
 14622   this.RIL = new RilObject(this);
 14623   this.RIL.initRILState();
 14625 Context.prototype = {
 14626   clientId: null,
 14627   Buf: null,
 14628   RIL: null,
 14630   debug: function(aMessage) {
 14631     GLOBAL.debug("[" + this.clientId + "] " + aMessage);
 14633 };
 14635 (function() {
 14636   let lazySymbols = [
 14637     "BerTlvHelper", "BitBufferHelper", "CdmaPDUHelper",
 14638     "ComprehensionTlvHelper", "GsmPDUHelper", "ICCContactHelper",
 14639     "ICCFileHelper", "ICCIOHelper", "ICCPDUHelper", "ICCRecordHelper",
 14640     "ICCUtilsHelper", "RuimRecordHelper", "SimRecordHelper",
 14641     "StkCommandParamsFactory", "StkProactiveCmdHelper",
 14642   ];
 14644   for (let i = 0; i < lazySymbols.length; i++) {
 14645     let symbol = lazySymbols[i];
 14646     Object.defineProperty(Context.prototype, symbol, {
 14647       get: function() {
 14648         let real = new GLOBAL[symbol + "Object"](this);
 14649         Object.defineProperty(this, symbol, {
 14650           value: real,
 14651           enumerable: true
 14652         });
 14653         return real;
 14654       },
 14655       configurable: true,
 14656       enumerable: true
 14657     });
 14659 })();
 14661 let ContextPool = {
 14662   _contexts: [],
 14664   handleRilMessage: function(aClientId, aUint8Array) {
 14665     let context = this._contexts[aClientId];
 14666     context.Buf.processIncoming(aUint8Array);
 14667   },
 14669   handleChromeMessage: function(aMessage) {
 14670     let clientId = aMessage.rilMessageClientId;
 14671     if (clientId != null) {
 14672       let context = this._contexts[clientId];
 14673       context.RIL.handleChromeMessage(aMessage);
 14674       return;
 14677     if (DEBUG) debug("Received global chrome message " + JSON.stringify(aMessage));
 14678     let method = this[aMessage.rilMessageType];
 14679     if (typeof method != "function") {
 14680       if (DEBUG) {
 14681         debug("Don't know what to do");
 14683       return;
 14685     method.call(this, aMessage);
 14686   },
 14688   setInitialOptions: function(aOptions) {
 14689     DEBUG = DEBUG_WORKER || aOptions.debug;
 14690     RIL_EMERGENCY_NUMBERS = aOptions.rilEmergencyNumbers;
 14691     RIL_CELLBROADCAST_DISABLED = aOptions.cellBroadcastDisabled;
 14692     RIL_CLIR_MODE = aOptions.clirMode;
 14694     let quirks = aOptions.quirks;
 14695     RILQUIRKS_CALLSTATE_EXTRA_UINT32 = quirks.callstateExtraUint32;
 14696     RILQUIRKS_V5_LEGACY = quirks.v5Legacy;
 14697     RILQUIRKS_REQUEST_USE_DIAL_EMERGENCY_CALL = quirks.requestUseDialEmergencyCall;
 14698     RILQUIRKS_SIM_APP_STATE_EXTRA_FIELDS = quirks.simAppStateExtraFields;
 14699     RILQUIRKS_EXTRA_UINT32_2ND_CALL = quirks.extraUint2ndCall;
 14700     RILQUIRKS_HAVE_QUERY_ICC_LOCK_RETRY_COUNT = quirks.haveQueryIccLockRetryCount;
 14701     RILQUIRKS_SEND_STK_PROFILE_DOWNLOAD = quirks.sendStkProfileDownload;
 14702     RILQUIRKS_DATA_REGISTRATION_ON_DEMAND = quirks.dataRegistrationOnDemand;
 14703   },
 14705   registerClient: function(aOptions) {
 14706     let clientId = aOptions.clientId;
 14707     this._contexts[clientId] = new Context(clientId);
 14708   },
 14709 };
 14711 function onRILMessage(aClientId, aUint8Array) {
 14712   ContextPool.handleRilMessage(aClientId, aUint8Array);
 14715 onmessage = function onmessage(event) {
 14716   ContextPool.handleChromeMessage(event.data);
 14717 };
 14719 onerror = function onerror(event) {
 14720   if (DEBUG) debug("onerror" + event.message + "\n");
 14721 };

mercurial