1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/dom/system/gonk/ril_worker.js Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,14721 @@ 1.4 +/* Copyright 2012 Mozilla Foundation and Mozilla contributors 1.5 + * 1.6 + * Licensed under the Apache License, Version 2.0 (the "License"); 1.7 + * you may not use this file except in compliance with the License. 1.8 + * You may obtain a copy of the License at 1.9 + * 1.10 + * http://www.apache.org/licenses/LICENSE-2.0 1.11 + * 1.12 + * Unless required by applicable law or agreed to in writing, software 1.13 + * distributed under the License is distributed on an "AS IS" BASIS, 1.14 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 1.15 + * See the License for the specific language governing permissions and 1.16 + * limitations under the License. 1.17 + */ 1.18 + 1.19 +/** 1.20 + * This file implements the RIL worker thread. It communicates with 1.21 + * the main thread to provide a high-level API to the phone's RIL 1.22 + * stack, and with the RIL IPC thread to communicate with the RIL 1.23 + * device itself. These communication channels use message events as 1.24 + * known from Web Workers: 1.25 + * 1.26 + * - postMessage()/"message" events for main thread communication 1.27 + * 1.28 + * - postRILMessage()/"RILMessageEvent" events for RIL IPC thread 1.29 + * communication. 1.30 + * 1.31 + * The two main objects in this file represent individual parts of this 1.32 + * communication chain: 1.33 + * 1.34 + * - RILMessageEvent -> Buf -> RIL -> postMessage() -> nsIRadioInterfaceLayer 1.35 + * - nsIRadioInterfaceLayer -> postMessage() -> RIL -> Buf -> postRILMessage() 1.36 + * 1.37 + * Note: The code below is purposely lean on abstractions to be as lean in 1.38 + * terms of object allocations. As a result, it may look more like C than 1.39 + * JavaScript, and that's intended. 1.40 + */ 1.41 + 1.42 +"use strict"; 1.43 + 1.44 +importScripts("ril_consts.js"); 1.45 +importScripts("resource://gre/modules/workers/require.js"); 1.46 + 1.47 +// set to true in ril_consts.js to see debug messages 1.48 +let DEBUG = DEBUG_WORKER; 1.49 +let GLOBAL = this; 1.50 + 1.51 +if (!this.debug) { 1.52 + // Debugging stub that goes nowhere. 1.53 + this.debug = function debug(message) { 1.54 + dump("RIL Worker: " + message + "\n"); 1.55 + }; 1.56 +} 1.57 + 1.58 +let RIL_CELLBROADCAST_DISABLED; 1.59 +let RIL_CLIR_MODE; 1.60 +let RIL_EMERGENCY_NUMBERS; 1.61 +const DEFAULT_EMERGENCY_NUMBERS = ["112", "911"]; 1.62 + 1.63 +// Timeout value for emergency callback mode. 1.64 +const EMERGENCY_CB_MODE_TIMEOUT_MS = 300000; // 5 mins = 300000 ms. 1.65 + 1.66 +const ICC_MAX_LINEAR_FIXED_RECORDS = 0xfe; 1.67 + 1.68 +// MMI match groups 1.69 +const MMI_MATCH_GROUP_FULL_MMI = 1; 1.70 +const MMI_MATCH_GROUP_MMI_PROCEDURE = 2; 1.71 +const MMI_MATCH_GROUP_SERVICE_CODE = 3; 1.72 +const MMI_MATCH_GROUP_SIA = 5; 1.73 +const MMI_MATCH_GROUP_SIB = 7; 1.74 +const MMI_MATCH_GROUP_SIC = 9; 1.75 +const MMI_MATCH_GROUP_PWD_CONFIRM = 11; 1.76 +const MMI_MATCH_GROUP_DIALING_NUMBER = 12; 1.77 + 1.78 +const MMI_MAX_LENGTH_SHORT_CODE = 2; 1.79 + 1.80 +const MMI_END_OF_USSD = "#"; 1.81 + 1.82 +// Should match the value we set in dom/telephony/TelephonyCommon.h 1.83 +const OUTGOING_PLACEHOLDER_CALL_INDEX = 0xffffffff; 1.84 + 1.85 +let RILQUIRKS_CALLSTATE_EXTRA_UINT32; 1.86 +// This may change at runtime since in RIL v6 and later, we get the version 1.87 +// number via the UNSOLICITED_RIL_CONNECTED parcel. 1.88 +let RILQUIRKS_V5_LEGACY; 1.89 +let RILQUIRKS_REQUEST_USE_DIAL_EMERGENCY_CALL; 1.90 +let RILQUIRKS_SIM_APP_STATE_EXTRA_FIELDS; 1.91 +// Needed for call-waiting on Peak device 1.92 +let RILQUIRKS_EXTRA_UINT32_2ND_CALL; 1.93 +// On the emulator we support querying the number of lock retries 1.94 +let RILQUIRKS_HAVE_QUERY_ICC_LOCK_RETRY_COUNT; 1.95 + 1.96 +// Ril quirk to Send STK Profile Download 1.97 +let RILQUIRKS_SEND_STK_PROFILE_DOWNLOAD; 1.98 + 1.99 +// Ril quirk to attach data registration on demand. 1.100 +let RILQUIRKS_DATA_REGISTRATION_ON_DEMAND; 1.101 + 1.102 +function BufObject(aContext) { 1.103 + this.context = aContext; 1.104 +} 1.105 +BufObject.prototype = { 1.106 + context: null, 1.107 + 1.108 + mToken: 0, 1.109 + mTokenRequestMap: null, 1.110 + 1.111 + init: function() { 1.112 + this._init(); 1.113 + 1.114 + // This gets incremented each time we send out a parcel. 1.115 + this.mToken = 1; 1.116 + 1.117 + // Maps tokens we send out with requests to the request type, so that 1.118 + // when we get a response parcel back, we know what request it was for. 1.119 + this.mTokenRequestMap = new Map(); 1.120 + }, 1.121 + 1.122 + /** 1.123 + * Process one parcel. 1.124 + */ 1.125 + processParcel: function() { 1.126 + let response_type = this.readInt32(); 1.127 + 1.128 + let request_type, options; 1.129 + if (response_type == RESPONSE_TYPE_SOLICITED) { 1.130 + let token = this.readInt32(); 1.131 + let error = this.readInt32(); 1.132 + 1.133 + options = this.mTokenRequestMap.get(token); 1.134 + if (!options) { 1.135 + if (DEBUG) { 1.136 + this.context.debug("Suspicious uninvited request found: " + 1.137 + token + ". Ignored!"); 1.138 + } 1.139 + return; 1.140 + } 1.141 + 1.142 + this.mTokenRequestMap.delete(token); 1.143 + request_type = options.rilRequestType; 1.144 + 1.145 + options.rilRequestError = error; 1.146 + if (DEBUG) { 1.147 + this.context.debug("Solicited response for request type " + request_type + 1.148 + ", token " + token + ", error " + error); 1.149 + } 1.150 + } else if (response_type == RESPONSE_TYPE_UNSOLICITED) { 1.151 + request_type = this.readInt32(); 1.152 + if (DEBUG) { 1.153 + this.context.debug("Unsolicited response for request type " + request_type); 1.154 + } 1.155 + } else { 1.156 + if (DEBUG) { 1.157 + this.context.debug("Unknown response type: " + response_type); 1.158 + } 1.159 + return; 1.160 + } 1.161 + 1.162 + this.context.RIL.handleParcel(request_type, this.readAvailable, options); 1.163 + }, 1.164 + 1.165 + /** 1.166 + * Start a new outgoing parcel. 1.167 + * 1.168 + * @param type 1.169 + * Integer specifying the request type. 1.170 + * @param options [optional] 1.171 + * Object containing information about the request, e.g. the 1.172 + * original main thread message object that led to the RIL request. 1.173 + */ 1.174 + newParcel: function(type, options) { 1.175 + if (DEBUG) this.context.debug("New outgoing parcel of type " + type); 1.176 + 1.177 + // We're going to leave room for the parcel size at the beginning. 1.178 + this.outgoingIndex = this.PARCEL_SIZE_SIZE; 1.179 + this.writeInt32(type); 1.180 + this.writeInt32(this.mToken); 1.181 + 1.182 + if (!options) { 1.183 + options = {}; 1.184 + } 1.185 + options.rilRequestType = type; 1.186 + options.rilRequestError = null; 1.187 + this.mTokenRequestMap.set(this.mToken, options); 1.188 + this.mToken++; 1.189 + return this.mToken; 1.190 + }, 1.191 + 1.192 + simpleRequest: function(type, options) { 1.193 + this.newParcel(type, options); 1.194 + this.sendParcel(); 1.195 + }, 1.196 + 1.197 + onSendParcel: function(parcel) { 1.198 + postRILMessage(this.context.clientId, parcel); 1.199 + } 1.200 +}; 1.201 + 1.202 +(function() { 1.203 + let base = require("resource://gre/modules/workers/worker_buf.js").Buf; 1.204 + for (let p in base) { 1.205 + BufObject.prototype[p] = base[p]; 1.206 + } 1.207 +})(); 1.208 + 1.209 +/** 1.210 + * The RIL state machine. 1.211 + * 1.212 + * This object communicates with rild via parcels and with the main thread 1.213 + * via post messages. It maintains state about the radio, ICC, calls, etc. 1.214 + * and acts upon state changes accordingly. 1.215 + */ 1.216 +function RilObject(aContext) { 1.217 + this.context = aContext; 1.218 + 1.219 + this.currentCalls = {}; 1.220 + this.currentConference = {state: null, participants: {}}; 1.221 + this.currentDataCalls = {}; 1.222 + this._pendingSentSmsMap = {}; 1.223 + this.pendingNetworkType = {}; 1.224 + this._receivedSmsCbPagesMap = {}; 1.225 + 1.226 + // Init properties that are only initialized once. 1.227 + this.v5Legacy = RILQUIRKS_V5_LEGACY; 1.228 + this.cellBroadcastDisabled = RIL_CELLBROADCAST_DISABLED; 1.229 + this.clirMode = RIL_CLIR_MODE; 1.230 +} 1.231 +RilObject.prototype = { 1.232 + context: null, 1.233 + 1.234 + v5Legacy: null, 1.235 + 1.236 + /** 1.237 + * Valid calls. 1.238 + */ 1.239 + currentCalls: null, 1.240 + 1.241 + /** 1.242 + * Existing conference call and its participants. 1.243 + */ 1.244 + currentConference: null, 1.245 + 1.246 + /** 1.247 + * Existing data calls. 1.248 + */ 1.249 + currentDataCalls: null, 1.250 + 1.251 + /** 1.252 + * Outgoing messages waiting for SMS-STATUS-REPORT. 1.253 + */ 1.254 + _pendingSentSmsMap: null, 1.255 + 1.256 + /** 1.257 + * Index of the RIL_PREFERRED_NETWORK_TYPE_TO_GECKO. Its value should be 1.258 + * preserved over rild reset. 1.259 + */ 1.260 + preferredNetworkType: null, 1.261 + 1.262 + /** 1.263 + * Marker object. 1.264 + */ 1.265 + pendingNetworkType: null, 1.266 + 1.267 + /** 1.268 + * Global Cell Broadcast switch. 1.269 + */ 1.270 + cellBroadcastDisabled: false, 1.271 + 1.272 + /** 1.273 + * Global CLIR mode settings. 1.274 + */ 1.275 + clirMode: CLIR_DEFAULT, 1.276 + 1.277 + /** 1.278 + * Parsed Cell Broadcast search lists. 1.279 + * cellBroadcastConfigs.MMI should be preserved over rild reset. 1.280 + */ 1.281 + cellBroadcastConfigs: null, 1.282 + mergedCellBroadcastConfig: null, 1.283 + 1.284 + _receivedSmsCbPagesMap: null, 1.285 + 1.286 + initRILState: function() { 1.287 + /** 1.288 + * One of the RADIO_STATE_* constants. 1.289 + */ 1.290 + this.radioState = GECKO_RADIOSTATE_UNAVAILABLE; 1.291 + 1.292 + /** 1.293 + * True if we are on a CDMA phone. 1.294 + */ 1.295 + this._isCdma = false; 1.296 + 1.297 + /** 1.298 + * True if we are in emergency callback mode. 1.299 + */ 1.300 + this._isInEmergencyCbMode = false; 1.301 + 1.302 + /** 1.303 + * Set when radio is ready but radio tech is unknown. That is, we are 1.304 + * waiting for REQUEST_VOICE_RADIO_TECH 1.305 + */ 1.306 + this._waitingRadioTech = false; 1.307 + 1.308 + /** 1.309 + * ICC status. Keeps a reference of the data response to the 1.310 + * getICCStatus request. 1.311 + */ 1.312 + this.iccStatus = null; 1.313 + 1.314 + /** 1.315 + * Card state 1.316 + */ 1.317 + this.cardState = GECKO_CARDSTATE_UNINITIALIZED; 1.318 + 1.319 + /** 1.320 + * Strings 1.321 + */ 1.322 + this.IMEI = null; 1.323 + this.IMEISV = null; 1.324 + this.ESN = null; 1.325 + this.MEID = null; 1.326 + this.SMSC = null; 1.327 + 1.328 + /** 1.329 + * ICC information that is not exposed to Gaia. 1.330 + */ 1.331 + this.iccInfoPrivate = {}; 1.332 + 1.333 + /** 1.334 + * ICC information, such as MSISDN, MCC, MNC, SPN...etc. 1.335 + */ 1.336 + this.iccInfo = {}; 1.337 + 1.338 + /** 1.339 + * CDMA specific information. ex. CDMA Network ID, CDMA System ID... etc. 1.340 + */ 1.341 + this.cdmaHome = null; 1.342 + 1.343 + /** 1.344 + * Application identification for apps in ICC. 1.345 + */ 1.346 + this.aid = null; 1.347 + 1.348 + /** 1.349 + * Application type for apps in ICC. 1.350 + */ 1.351 + this.appType = null; 1.352 + 1.353 + this.networkSelectionMode = null; 1.354 + 1.355 + this.voiceRegistrationState = {}; 1.356 + this.dataRegistrationState = {}; 1.357 + 1.358 + /** 1.359 + * List of strings identifying the network operator. 1.360 + */ 1.361 + this.operator = null; 1.362 + 1.363 + /** 1.364 + * String containing the baseband version. 1.365 + */ 1.366 + this.basebandVersion = null; 1.367 + 1.368 + // Clean up this.currentCalls: rild might have restarted. 1.369 + for each (let currentCall in this.currentCalls) { 1.370 + delete this.currentCalls[currentCall.callIndex]; 1.371 + this._handleDisconnectedCall(currentCall); 1.372 + } 1.373 + 1.374 + // Deactivate this.currentDataCalls: rild might have restarted. 1.375 + for each (let datacall in this.currentDataCalls) { 1.376 + this.deactivateDataCall(datacall); 1.377 + } 1.378 + 1.379 + // Don't clean up this._pendingSentSmsMap 1.380 + // because on rild restart: we may continue with the pending segments. 1.381 + 1.382 + /** 1.383 + * Whether or not the multiple requests in requestNetworkInfo() are currently 1.384 + * being processed 1.385 + */ 1.386 + this._processingNetworkInfo = false; 1.387 + 1.388 + /** 1.389 + * Multiple requestNetworkInfo() in a row before finishing the first 1.390 + * request, hence we need to fire requestNetworkInfo() again after 1.391 + * gathering all necessary stuffs. This is to make sure that ril_worker 1.392 + * gets precise network information. 1.393 + */ 1.394 + this._needRepollNetworkInfo = false; 1.395 + 1.396 + /** 1.397 + * Pending messages to be send in batch from requestNetworkInfo() 1.398 + */ 1.399 + this._pendingNetworkInfo = {rilMessageType: "networkinfochanged"}; 1.400 + 1.401 + /** 1.402 + * USSD session flag. 1.403 + * Only one USSD session may exist at a time, and the session is assumed 1.404 + * to exist until: 1.405 + * a) There's a call to cancelUSSD() 1.406 + * b) The implementation sends a UNSOLICITED_ON_USSD with a type code 1.407 + * of "0" (USSD-Notify/no further action) or "2" (session terminated) 1.408 + */ 1.409 + this._ussdSession = null; 1.410 + 1.411 + /** 1.412 + * Regular expresion to parse MMI codes. 1.413 + */ 1.414 + this._mmiRegExp = null; 1.415 + 1.416 + /** 1.417 + * Cell Broadcast Search Lists. 1.418 + */ 1.419 + let cbmmi = this.cellBroadcastConfigs && this.cellBroadcastConfigs.MMI; 1.420 + this.cellBroadcastConfigs = { 1.421 + MMI: cbmmi || null 1.422 + }; 1.423 + this.mergedCellBroadcastConfig = null; 1.424 + }, 1.425 + 1.426 + /** 1.427 + * Parse an integer from a string, falling back to a default value 1.428 + * if the the provided value is not a string or does not contain a valid 1.429 + * number. 1.430 + * 1.431 + * @param string 1.432 + * String to be parsed. 1.433 + * @param defaultValue [optional] 1.434 + * Default value to be used. 1.435 + * @param radix [optional] 1.436 + * A number that represents the numeral system to be used. Default 10. 1.437 + */ 1.438 + parseInt: function(string, defaultValue, radix) { 1.439 + let number = parseInt(string, radix || 10); 1.440 + if (!isNaN(number)) { 1.441 + return number; 1.442 + } 1.443 + if (defaultValue === undefined) { 1.444 + defaultValue = null; 1.445 + } 1.446 + return defaultValue; 1.447 + }, 1.448 + 1.449 + 1.450 + /** 1.451 + * Outgoing requests to the RIL. These can be triggered from the 1.452 + * main thread via messages that look like this: 1.453 + * 1.454 + * {rilMessageType: "methodName", 1.455 + * extra: "parameters", 1.456 + * go: "here"} 1.457 + * 1.458 + * So if one of the following methods takes arguments, it takes only one, 1.459 + * an object, which then contains all of the parameters as attributes. 1.460 + * The "@param" documentation is to be interpreted accordingly. 1.461 + */ 1.462 + 1.463 + /** 1.464 + * Retrieve the ICC's status. 1.465 + */ 1.466 + getICCStatus: function() { 1.467 + this.context.Buf.simpleRequest(REQUEST_GET_SIM_STATUS); 1.468 + }, 1.469 + 1.470 + /** 1.471 + * Helper function for unlocking ICC locks. 1.472 + */ 1.473 + iccUnlockCardLock: function(options) { 1.474 + switch (options.lockType) { 1.475 + case GECKO_CARDLOCK_PIN: 1.476 + this.enterICCPIN(options); 1.477 + break; 1.478 + case GECKO_CARDLOCK_PIN2: 1.479 + this.enterICCPIN2(options); 1.480 + break; 1.481 + case GECKO_CARDLOCK_PUK: 1.482 + this.enterICCPUK(options); 1.483 + break; 1.484 + case GECKO_CARDLOCK_PUK2: 1.485 + this.enterICCPUK2(options); 1.486 + break; 1.487 + case GECKO_CARDLOCK_NCK: 1.488 + case GECKO_CARDLOCK_NCK1: 1.489 + case GECKO_CARDLOCK_NCK2: 1.490 + case GECKO_CARDLOCK_HNCK: 1.491 + case GECKO_CARDLOCK_CCK: 1.492 + case GECKO_CARDLOCK_SPCK: 1.493 + case GECKO_CARDLOCK_RCCK: // Fall through. 1.494 + case GECKO_CARDLOCK_RSPCK: { 1.495 + let type = GECKO_PERSO_LOCK_TO_CARD_PERSO_LOCK[options.lockType]; 1.496 + this.enterDepersonalization(type, options.pin, options); 1.497 + break; 1.498 + } 1.499 + case GECKO_CARDLOCK_NCK_PUK: 1.500 + case GECKO_CARDLOCK_NCK1_PUK: 1.501 + case GECKO_CARDLOCK_NCK2_PUK: 1.502 + case GECKO_CARDLOCK_HNCK_PUK: 1.503 + case GECKO_CARDLOCK_CCK_PUK: 1.504 + case GECKO_CARDLOCK_SPCK_PUK: 1.505 + case GECKO_CARDLOCK_RCCK_PUK: // Fall through. 1.506 + case GECKO_CARDLOCK_RSPCK_PUK: { 1.507 + let type = GECKO_PERSO_LOCK_TO_CARD_PERSO_LOCK[options.lockType]; 1.508 + this.enterDepersonalization(type, options.puk, options); 1.509 + break; 1.510 + } 1.511 + default: 1.512 + options.errorMsg = "Unsupported Card Lock."; 1.513 + options.success = false; 1.514 + this.sendChromeMessage(options); 1.515 + } 1.516 + }, 1.517 + 1.518 + /** 1.519 + * Enter a PIN to unlock the ICC. 1.520 + * 1.521 + * @param pin 1.522 + * String containing the PIN. 1.523 + * @param [optional] aid 1.524 + * AID value. 1.525 + */ 1.526 + enterICCPIN: function(options) { 1.527 + let Buf = this.context.Buf; 1.528 + Buf.newParcel(REQUEST_ENTER_SIM_PIN, options); 1.529 + Buf.writeInt32(this.v5Legacy ? 1 : 2); 1.530 + Buf.writeString(options.pin); 1.531 + if (!this.v5Legacy) { 1.532 + Buf.writeString(options.aid || this.aid); 1.533 + } 1.534 + Buf.sendParcel(); 1.535 + }, 1.536 + 1.537 + /** 1.538 + * Enter a PIN2 to unlock the ICC. 1.539 + * 1.540 + * @param pin 1.541 + * String containing the PIN2. 1.542 + * @param [optional] aid 1.543 + * AID value. 1.544 + */ 1.545 + enterICCPIN2: function(options) { 1.546 + let Buf = this.context.Buf; 1.547 + Buf.newParcel(REQUEST_ENTER_SIM_PIN2, options); 1.548 + Buf.writeInt32(this.v5Legacy ? 1 : 2); 1.549 + Buf.writeString(options.pin); 1.550 + if (!this.v5Legacy) { 1.551 + Buf.writeString(options.aid || this.aid); 1.552 + } 1.553 + Buf.sendParcel(); 1.554 + }, 1.555 + 1.556 + /** 1.557 + * Requests a network personalization be deactivated. 1.558 + * 1.559 + * @param type 1.560 + * Integer indicating the network personalization be deactivated. 1.561 + * @param password 1.562 + * String containing the password. 1.563 + */ 1.564 + enterDepersonalization: function(type, password, options) { 1.565 + let Buf = this.context.Buf; 1.566 + Buf.newParcel(REQUEST_ENTER_NETWORK_DEPERSONALIZATION_CODE, options); 1.567 + Buf.writeInt32(type); 1.568 + Buf.writeString(password); 1.569 + Buf.sendParcel(); 1.570 + }, 1.571 + 1.572 + /** 1.573 + * Helper function for changing ICC locks. 1.574 + */ 1.575 + iccSetCardLock: function(options) { 1.576 + if (options.newPin !== undefined) { // Change PIN lock. 1.577 + switch (options.lockType) { 1.578 + case GECKO_CARDLOCK_PIN: 1.579 + this.changeICCPIN(options); 1.580 + break; 1.581 + case GECKO_CARDLOCK_PIN2: 1.582 + this.changeICCPIN2(options); 1.583 + break; 1.584 + default: 1.585 + options.errorMsg = "Unsupported Card Lock."; 1.586 + options.success = false; 1.587 + this.sendChromeMessage(options); 1.588 + } 1.589 + } else { // Enable/Disable lock. 1.590 + switch (options.lockType) { 1.591 + case GECKO_CARDLOCK_PIN: 1.592 + options.facility = ICC_CB_FACILITY_SIM; 1.593 + options.password = options.pin; 1.594 + break; 1.595 + case GECKO_CARDLOCK_FDN: 1.596 + options.facility = ICC_CB_FACILITY_FDN; 1.597 + options.password = options.pin2; 1.598 + break; 1.599 + default: 1.600 + options.errorMsg = "Unsupported Card Lock."; 1.601 + options.success = false; 1.602 + this.sendChromeMessage(options); 1.603 + return; 1.604 + } 1.605 + options.enabled = options.enabled; 1.606 + options.serviceClass = ICC_SERVICE_CLASS_VOICE | 1.607 + ICC_SERVICE_CLASS_DATA | 1.608 + ICC_SERVICE_CLASS_FAX; 1.609 + this.setICCFacilityLock(options); 1.610 + } 1.611 + }, 1.612 + 1.613 + /** 1.614 + * Change the current ICC PIN number. 1.615 + * 1.616 + * @param pin 1.617 + * String containing the old PIN value 1.618 + * @param newPin 1.619 + * String containing the new PIN value 1.620 + * @param [optional] aid 1.621 + * AID value. 1.622 + */ 1.623 + changeICCPIN: function(options) { 1.624 + let Buf = this.context.Buf; 1.625 + Buf.newParcel(REQUEST_CHANGE_SIM_PIN, options); 1.626 + Buf.writeInt32(this.v5Legacy ? 2 : 3); 1.627 + Buf.writeString(options.pin); 1.628 + Buf.writeString(options.newPin); 1.629 + if (!this.v5Legacy) { 1.630 + Buf.writeString(options.aid || this.aid); 1.631 + } 1.632 + Buf.sendParcel(); 1.633 + }, 1.634 + 1.635 + /** 1.636 + * Change the current ICC PIN2 number. 1.637 + * 1.638 + * @param pin 1.639 + * String containing the old PIN2 value 1.640 + * @param newPin 1.641 + * String containing the new PIN2 value 1.642 + * @param [optional] aid 1.643 + * AID value. 1.644 + */ 1.645 + changeICCPIN2: function(options) { 1.646 + let Buf = this.context.Buf; 1.647 + Buf.newParcel(REQUEST_CHANGE_SIM_PIN2, options); 1.648 + Buf.writeInt32(this.v5Legacy ? 2 : 3); 1.649 + Buf.writeString(options.pin); 1.650 + Buf.writeString(options.newPin); 1.651 + if (!this.v5Legacy) { 1.652 + Buf.writeString(options.aid || this.aid); 1.653 + } 1.654 + Buf.sendParcel(); 1.655 + }, 1.656 + /** 1.657 + * Supplies ICC PUK and a new PIN to unlock the ICC. 1.658 + * 1.659 + * @param puk 1.660 + * String containing the PUK value. 1.661 + * @param newPin 1.662 + * String containing the new PIN value. 1.663 + * @param [optional] aid 1.664 + * AID value. 1.665 + */ 1.666 + enterICCPUK: function(options) { 1.667 + let Buf = this.context.Buf; 1.668 + Buf.newParcel(REQUEST_ENTER_SIM_PUK, options); 1.669 + Buf.writeInt32(this.v5Legacy ? 2 : 3); 1.670 + Buf.writeString(options.puk); 1.671 + Buf.writeString(options.newPin); 1.672 + if (!this.v5Legacy) { 1.673 + Buf.writeString(options.aid || this.aid); 1.674 + } 1.675 + Buf.sendParcel(); 1.676 + }, 1.677 + 1.678 + /** 1.679 + * Supplies ICC PUK2 and a new PIN2 to unlock the ICC. 1.680 + * 1.681 + * @param puk 1.682 + * String containing the PUK2 value. 1.683 + * @param newPin 1.684 + * String containing the new PIN2 value. 1.685 + * @param [optional] aid 1.686 + * AID value. 1.687 + */ 1.688 + enterICCPUK2: function(options) { 1.689 + let Buf = this.context.Buf; 1.690 + Buf.newParcel(REQUEST_ENTER_SIM_PUK2, options); 1.691 + Buf.writeInt32(this.v5Legacy ? 2 : 3); 1.692 + Buf.writeString(options.puk); 1.693 + Buf.writeString(options.newPin); 1.694 + if (!this.v5Legacy) { 1.695 + Buf.writeString(options.aid || this.aid); 1.696 + } 1.697 + Buf.sendParcel(); 1.698 + }, 1.699 + 1.700 + /** 1.701 + * Helper function for fetching the state of ICC locks. 1.702 + */ 1.703 + iccGetCardLockState: function(options) { 1.704 + switch (options.lockType) { 1.705 + case GECKO_CARDLOCK_PIN: 1.706 + options.facility = ICC_CB_FACILITY_SIM; 1.707 + break; 1.708 + case GECKO_CARDLOCK_FDN: 1.709 + options.facility = ICC_CB_FACILITY_FDN; 1.710 + break; 1.711 + default: 1.712 + options.errorMsg = "Unsupported Card Lock."; 1.713 + options.success = false; 1.714 + this.sendChromeMessage(options); 1.715 + return; 1.716 + } 1.717 + 1.718 + options.password = ""; // For query no need to provide pin. 1.719 + options.serviceClass = ICC_SERVICE_CLASS_VOICE | 1.720 + ICC_SERVICE_CLASS_DATA | 1.721 + ICC_SERVICE_CLASS_FAX; 1.722 + this.queryICCFacilityLock(options); 1.723 + }, 1.724 + 1.725 + /** 1.726 + * Helper function for fetching the number of unlock retries of ICC locks. 1.727 + * 1.728 + * We only query the retry count when we're on the emulator. The phones do 1.729 + * not support the request id and their rild doesn't return an error. 1.730 + */ 1.731 + iccGetCardLockRetryCount: function(options) { 1.732 + var selCode = { 1.733 + pin: ICC_SEL_CODE_SIM_PIN, 1.734 + puk: ICC_SEL_CODE_SIM_PUK, 1.735 + pin2: ICC_SEL_CODE_SIM_PIN2, 1.736 + puk2: ICC_SEL_CODE_SIM_PUK2, 1.737 + nck: ICC_SEL_CODE_PH_NET_PIN, 1.738 + cck: ICC_SEL_CODE_PH_CORP_PIN, 1.739 + spck: ICC_SEL_CODE_PH_SP_PIN 1.740 + }; 1.741 + 1.742 + if (typeof(selCode[options.lockType]) === 'undefined') { 1.743 + /* unknown lock type */ 1.744 + options.errorMsg = GECKO_ERROR_GENERIC_FAILURE; 1.745 + options.success = false; 1.746 + this.sendChromeMessage(options); 1.747 + return; 1.748 + } 1.749 + 1.750 + if (RILQUIRKS_HAVE_QUERY_ICC_LOCK_RETRY_COUNT) { 1.751 + /* Only the emulator supports this request, ... */ 1.752 + options.selCode = selCode[options.lockType]; 1.753 + this.queryICCLockRetryCount(options); 1.754 + } else { 1.755 + /* ... while the phones do not. */ 1.756 + options.errorMsg = GECKO_ERROR_REQUEST_NOT_SUPPORTED; 1.757 + options.success = false; 1.758 + this.sendChromeMessage(options); 1.759 + } 1.760 + }, 1.761 + 1.762 + /** 1.763 + * Query ICC lock retry count. 1.764 + * 1.765 + * @param selCode 1.766 + * One of ICC_SEL_CODE_*. 1.767 + * @param serviceClass 1.768 + * One of ICC_SERVICE_CLASS_*. 1.769 + */ 1.770 + queryICCLockRetryCount: function(options) { 1.771 + let Buf = this.context.Buf; 1.772 + Buf.newParcel(REQUEST_GET_UNLOCK_RETRY_COUNT, options); 1.773 + Buf.writeInt32(1); 1.774 + Buf.writeString(options.selCode); 1.775 + Buf.sendParcel(); 1.776 + }, 1.777 + 1.778 + /** 1.779 + * Query ICC facility lock. 1.780 + * 1.781 + * @param facility 1.782 + * One of ICC_CB_FACILITY_*. 1.783 + * @param password 1.784 + * Password for the facility, or "" if not required. 1.785 + * @param serviceClass 1.786 + * One of ICC_SERVICE_CLASS_*. 1.787 + * @param [optional] aid 1.788 + * AID value. 1.789 + */ 1.790 + queryICCFacilityLock: function(options) { 1.791 + let Buf = this.context.Buf; 1.792 + Buf.newParcel(REQUEST_QUERY_FACILITY_LOCK, options); 1.793 + Buf.writeInt32(this.v5Legacy ? 3 : 4); 1.794 + Buf.writeString(options.facility); 1.795 + Buf.writeString(options.password); 1.796 + Buf.writeString(options.serviceClass.toString()); 1.797 + if (!this.v5Legacy) { 1.798 + Buf.writeString(options.aid || this.aid); 1.799 + } 1.800 + Buf.sendParcel(); 1.801 + }, 1.802 + 1.803 + /** 1.804 + * Set ICC facility lock. 1.805 + * 1.806 + * @param facility 1.807 + * One of ICC_CB_FACILITY_*. 1.808 + * @param enabled 1.809 + * true to enable, false to disable. 1.810 + * @param password 1.811 + * Password for the facility, or "" if not required. 1.812 + * @param serviceClass 1.813 + * One of ICC_SERVICE_CLASS_*. 1.814 + * @param [optional] aid 1.815 + * AID value. 1.816 + */ 1.817 + setICCFacilityLock: function(options) { 1.818 + let Buf = this.context.Buf; 1.819 + Buf.newParcel(REQUEST_SET_FACILITY_LOCK, options); 1.820 + Buf.writeInt32(this.v5Legacy ? 4 : 5); 1.821 + Buf.writeString(options.facility); 1.822 + Buf.writeString(options.enabled ? "1" : "0"); 1.823 + Buf.writeString(options.password); 1.824 + Buf.writeString(options.serviceClass.toString()); 1.825 + if (!this.v5Legacy) { 1.826 + Buf.writeString(options.aid || this.aid); 1.827 + } 1.828 + Buf.sendParcel(); 1.829 + }, 1.830 + 1.831 + /** 1.832 + * Request an ICC I/O operation. 1.833 + * 1.834 + * See TS 27.007 "restricted SIM" operation, "AT Command +CRSM". 1.835 + * The sequence is in the same order as how libril reads this parcel, 1.836 + * see the struct RIL_SIM_IO_v5 or RIL_SIM_IO_v6 defined in ril.h 1.837 + * 1.838 + * @param command 1.839 + * The I/O command, one of the ICC_COMMAND_* constants. 1.840 + * @param fileId 1.841 + * The file to operate on, one of the ICC_EF_* constants. 1.842 + * @param pathId 1.843 + * String type, check the 'pathid' parameter from TS 27.007 +CRSM. 1.844 + * @param p1, p2, p3 1.845 + * Arbitrary integer parameters for the command. 1.846 + * @param [optional] dataWriter 1.847 + * The function for writing string parameter for the ICC_COMMAND_UPDATE_RECORD. 1.848 + * @param [optional] pin2 1.849 + * String containing the PIN2. 1.850 + * @param [optional] aid 1.851 + * AID value. 1.852 + */ 1.853 + iccIO: function(options) { 1.854 + let Buf = this.context.Buf; 1.855 + Buf.newParcel(REQUEST_SIM_IO, options); 1.856 + Buf.writeInt32(options.command); 1.857 + Buf.writeInt32(options.fileId); 1.858 + Buf.writeString(options.pathId); 1.859 + Buf.writeInt32(options.p1); 1.860 + Buf.writeInt32(options.p2); 1.861 + Buf.writeInt32(options.p3); 1.862 + 1.863 + // Write data. 1.864 + if (options.command == ICC_COMMAND_UPDATE_RECORD && 1.865 + options.dataWriter) { 1.866 + options.dataWriter(options.p3); 1.867 + } else { 1.868 + Buf.writeString(null); 1.869 + } 1.870 + 1.871 + // Write pin2. 1.872 + if (options.command == ICC_COMMAND_UPDATE_RECORD && 1.873 + options.pin2) { 1.874 + Buf.writeString(options.pin2); 1.875 + } else { 1.876 + Buf.writeString(null); 1.877 + } 1.878 + 1.879 + if (!this.v5Legacy) { 1.880 + Buf.writeString(options.aid || this.aid); 1.881 + } 1.882 + Buf.sendParcel(); 1.883 + }, 1.884 + 1.885 + /** 1.886 + * Get IMSI. 1.887 + * 1.888 + * @param [optional] aid 1.889 + * AID value. 1.890 + */ 1.891 + getIMSI: function(aid) { 1.892 + let Buf = this.context.Buf; 1.893 + if (this.v5Legacy) { 1.894 + Buf.simpleRequest(REQUEST_GET_IMSI); 1.895 + return; 1.896 + } 1.897 + Buf.newParcel(REQUEST_GET_IMSI); 1.898 + Buf.writeInt32(1); 1.899 + Buf.writeString(aid || this.aid); 1.900 + Buf.sendParcel(); 1.901 + }, 1.902 + 1.903 + /** 1.904 + * Read UICC Phonebook contacts. 1.905 + * 1.906 + * @param contactType 1.907 + * "adn" or "fdn". 1.908 + * @param requestId 1.909 + * Request id from RadioInterfaceLayer. 1.910 + */ 1.911 + readICCContacts: function(options) { 1.912 + if (!this.appType) { 1.913 + options.errorMsg = CONTACT_ERR_REQUEST_NOT_SUPPORTED; 1.914 + this.sendChromeMessage(options); 1.915 + return; 1.916 + } 1.917 + 1.918 + this.context.ICCContactHelper.readICCContacts( 1.919 + this.appType, 1.920 + options.contactType, 1.921 + function onsuccess(contacts) { 1.922 + for (let i = 0; i < contacts.length; i++) { 1.923 + let contact = contacts[i]; 1.924 + let pbrIndex = contact.pbrIndex || 0; 1.925 + let recordIndex = pbrIndex * ICC_MAX_LINEAR_FIXED_RECORDS + contact.recordId; 1.926 + contact.contactId = this.iccInfo.iccid + recordIndex; 1.927 + } 1.928 + // Reuse 'options' to get 'requestId' and 'contactType'. 1.929 + options.contacts = contacts; 1.930 + this.sendChromeMessage(options); 1.931 + }.bind(this), 1.932 + function onerror(errorMsg) { 1.933 + options.errorMsg = errorMsg; 1.934 + this.sendChromeMessage(options); 1.935 + }.bind(this)); 1.936 + }, 1.937 + 1.938 + /** 1.939 + * Update UICC Phonebook. 1.940 + * 1.941 + * @param contactType "adn" or "fdn". 1.942 + * @param contact The contact will be updated. 1.943 + * @param pin2 PIN2 is required for updating FDN. 1.944 + * @param requestId Request id from RadioInterfaceLayer. 1.945 + */ 1.946 + updateICCContact: function(options) { 1.947 + let onsuccess = function onsuccess() { 1.948 + let recordIndex = 1.949 + contact.pbrIndex * ICC_MAX_LINEAR_FIXED_RECORDS + contact.recordId; 1.950 + contact.contactId = this.iccInfo.iccid + recordIndex; 1.951 + // Reuse 'options' to get 'requestId' and 'contactType'. 1.952 + this.sendChromeMessage(options); 1.953 + }.bind(this); 1.954 + 1.955 + let onerror = function onerror(errorMsg) { 1.956 + options.errorMsg = errorMsg; 1.957 + this.sendChromeMessage(options); 1.958 + }.bind(this); 1.959 + 1.960 + if (!this.appType || !options.contact) { 1.961 + onerror(CONTACT_ERR_REQUEST_NOT_SUPPORTED ); 1.962 + return; 1.963 + } 1.964 + 1.965 + let contact = options.contact; 1.966 + let iccid = this.iccInfo.iccid; 1.967 + let isValidRecordId = false; 1.968 + if (typeof contact.contactId === "string" && 1.969 + contact.contactId.startsWith(iccid)) { 1.970 + let recordIndex = contact.contactId.substring(iccid.length); 1.971 + contact.pbrIndex = Math.floor(recordIndex / ICC_MAX_LINEAR_FIXED_RECORDS); 1.972 + contact.recordId = recordIndex % ICC_MAX_LINEAR_FIXED_RECORDS; 1.973 + isValidRecordId = contact.recordId > 0 && contact.recordId < 0xff; 1.974 + } 1.975 + 1.976 + if (DEBUG) { 1.977 + this.context.debug("Update ICC Contact " + JSON.stringify(contact)); 1.978 + } 1.979 + 1.980 + let ICCContactHelper = this.context.ICCContactHelper; 1.981 + // If contact has 'recordId' property, updates corresponding record. 1.982 + // If not, inserts the contact into a free record. 1.983 + if (isValidRecordId) { 1.984 + ICCContactHelper.updateICCContact( 1.985 + this.appType, options.contactType, contact, options.pin2, onsuccess, onerror); 1.986 + } else { 1.987 + ICCContactHelper.addICCContact( 1.988 + this.appType, options.contactType, contact, options.pin2, onsuccess, onerror); 1.989 + } 1.990 + }, 1.991 + 1.992 + /** 1.993 + * Request the phone's radio to be enabled or disabled. 1.994 + * 1.995 + * @param enabled 1.996 + * Boolean indicating the desired state. 1.997 + */ 1.998 + setRadioEnabled: function(options) { 1.999 + let Buf = this.context.Buf; 1.1000 + Buf.newParcel(REQUEST_RADIO_POWER, options); 1.1001 + Buf.writeInt32(1); 1.1002 + Buf.writeInt32(options.enabled ? 1 : 0); 1.1003 + Buf.sendParcel(); 1.1004 + }, 1.1005 + 1.1006 + /** 1.1007 + * Query call waiting status via MMI. 1.1008 + */ 1.1009 + _handleQueryMMICallWaiting: function(options) { 1.1010 + let Buf = this.context.Buf; 1.1011 + 1.1012 + function callback(options) { 1.1013 + options.length = Buf.readInt32(); 1.1014 + options.enabled = (Buf.readInt32() === 1); 1.1015 + let services = Buf.readInt32(); 1.1016 + if (options.enabled) { 1.1017 + options.statusMessage = MMI_SM_KS_SERVICE_ENABLED_FOR; 1.1018 + let serviceClass = []; 1.1019 + for (let serviceClassMask = 1; 1.1020 + serviceClassMask <= ICC_SERVICE_CLASS_MAX; 1.1021 + serviceClassMask <<= 1) { 1.1022 + if ((serviceClassMask & services) !== 0) { 1.1023 + serviceClass.push(MMI_KS_SERVICE_CLASS_MAPPING[serviceClassMask]); 1.1024 + } 1.1025 + } 1.1026 + options.additionalInformation = serviceClass; 1.1027 + } else { 1.1028 + options.statusMessage = MMI_SM_KS_SERVICE_DISABLED; 1.1029 + } 1.1030 + 1.1031 + // Prevent DataCloneError when sending chrome messages. 1.1032 + delete options.callback; 1.1033 + this.sendChromeMessage(options); 1.1034 + } 1.1035 + 1.1036 + options.callback = callback; 1.1037 + this.queryCallWaiting(options); 1.1038 + }, 1.1039 + 1.1040 + /** 1.1041 + * Set call waiting status via MMI. 1.1042 + */ 1.1043 + _handleSetMMICallWaiting: function(options) { 1.1044 + function callback(options) { 1.1045 + if (options.enabled) { 1.1046 + options.statusMessage = MMI_SM_KS_SERVICE_ENABLED; 1.1047 + } else { 1.1048 + options.statusMessage = MMI_SM_KS_SERVICE_DISABLED; 1.1049 + } 1.1050 + 1.1051 + // Prevent DataCloneError when sending chrome messages. 1.1052 + delete options.callback; 1.1053 + this.sendChromeMessage(options); 1.1054 + } 1.1055 + 1.1056 + options.callback = callback; 1.1057 + this.setCallWaiting(options); 1.1058 + }, 1.1059 + 1.1060 + /** 1.1061 + * Query call waiting status. 1.1062 + * 1.1063 + */ 1.1064 + queryCallWaiting: function(options) { 1.1065 + let Buf = this.context.Buf; 1.1066 + Buf.newParcel(REQUEST_QUERY_CALL_WAITING, options); 1.1067 + Buf.writeInt32(1); 1.1068 + // As per 3GPP TS 24.083, section 1.6 UE doesn't need to send service 1.1069 + // class parameter in call waiting interrogation to network 1.1070 + Buf.writeInt32(ICC_SERVICE_CLASS_NONE); 1.1071 + Buf.sendParcel(); 1.1072 + }, 1.1073 + 1.1074 + /** 1.1075 + * Set call waiting status. 1.1076 + * 1.1077 + * @param on 1.1078 + * Boolean indicating the desired waiting status. 1.1079 + */ 1.1080 + setCallWaiting: function(options) { 1.1081 + let Buf = this.context.Buf; 1.1082 + Buf.newParcel(REQUEST_SET_CALL_WAITING, options); 1.1083 + Buf.writeInt32(2); 1.1084 + Buf.writeInt32(options.enabled ? 1 : 0); 1.1085 + Buf.writeInt32(options.serviceClass !== undefined ? 1.1086 + options.serviceClass : ICC_SERVICE_CLASS_VOICE); 1.1087 + Buf.sendParcel(); 1.1088 + }, 1.1089 + 1.1090 + /** 1.1091 + * Queries current CLIP status. 1.1092 + * 1.1093 + * (MMI request for code "*#30#") 1.1094 + * 1.1095 + */ 1.1096 + queryCLIP: function(options) { 1.1097 + this.context.Buf.simpleRequest(REQUEST_QUERY_CLIP, options); 1.1098 + }, 1.1099 + 1.1100 + /** 1.1101 + * Queries current CLIR status. 1.1102 + * 1.1103 + */ 1.1104 + getCLIR: function(options) { 1.1105 + this.context.Buf.simpleRequest(REQUEST_GET_CLIR, options); 1.1106 + }, 1.1107 + 1.1108 + /** 1.1109 + * Enables or disables the presentation of the calling line identity (CLI) to 1.1110 + * the called party when originating a call. 1.1111 + * 1.1112 + * @param options.clirMode 1.1113 + * Is one of the CLIR_* constants in 1.1114 + * nsIDOMMozMobileConnection interface. 1.1115 + */ 1.1116 + setCLIR: function(options) { 1.1117 + if (options) { 1.1118 + this.clirMode = options.clirMode; 1.1119 + } 1.1120 + let Buf = this.context.Buf; 1.1121 + Buf.newParcel(REQUEST_SET_CLIR, options); 1.1122 + Buf.writeInt32(1); 1.1123 + Buf.writeInt32(this.clirMode); 1.1124 + Buf.sendParcel(); 1.1125 + }, 1.1126 + 1.1127 + /** 1.1128 + * Set screen state. 1.1129 + * 1.1130 + * @param on 1.1131 + * Boolean indicating whether the screen should be on or off. 1.1132 + */ 1.1133 + setScreenState: function(options) { 1.1134 + let Buf = this.context.Buf; 1.1135 + Buf.newParcel(REQUEST_SCREEN_STATE); 1.1136 + Buf.writeInt32(1); 1.1137 + Buf.writeInt32(options.on ? 1 : 0); 1.1138 + Buf.sendParcel(); 1.1139 + }, 1.1140 + 1.1141 + getVoiceRegistrationState: function() { 1.1142 + this.context.Buf.simpleRequest(REQUEST_VOICE_REGISTRATION_STATE); 1.1143 + }, 1.1144 + 1.1145 + getVoiceRadioTechnology: function() { 1.1146 + this.context.Buf.simpleRequest(REQUEST_VOICE_RADIO_TECH); 1.1147 + }, 1.1148 + 1.1149 + getDataRegistrationState: function() { 1.1150 + this.context.Buf.simpleRequest(REQUEST_DATA_REGISTRATION_STATE); 1.1151 + }, 1.1152 + 1.1153 + getOperator: function() { 1.1154 + this.context.Buf.simpleRequest(REQUEST_OPERATOR); 1.1155 + }, 1.1156 + 1.1157 + /** 1.1158 + * Set the preferred network type. 1.1159 + * 1.1160 + * @param options An object contains a valid index of 1.1161 + * RIL_PREFERRED_NETWORK_TYPE_TO_GECKO as its `networkType` 1.1162 + * attribute, or undefined to set current preferred network 1.1163 + * type. 1.1164 + */ 1.1165 + setPreferredNetworkType: function(options) { 1.1166 + if (options) { 1.1167 + this.preferredNetworkType = options.networkType; 1.1168 + } 1.1169 + if (this.preferredNetworkType == null) { 1.1170 + return; 1.1171 + } 1.1172 + 1.1173 + let Buf = this.context.Buf; 1.1174 + Buf.newParcel(REQUEST_SET_PREFERRED_NETWORK_TYPE, options); 1.1175 + Buf.writeInt32(1); 1.1176 + Buf.writeInt32(this.preferredNetworkType); 1.1177 + Buf.sendParcel(); 1.1178 + }, 1.1179 + 1.1180 + /** 1.1181 + * Get the preferred network type. 1.1182 + */ 1.1183 + getPreferredNetworkType: function(options) { 1.1184 + this.context.Buf.simpleRequest(REQUEST_GET_PREFERRED_NETWORK_TYPE, options); 1.1185 + }, 1.1186 + 1.1187 + /** 1.1188 + * Request various states about the network. 1.1189 + */ 1.1190 + requestNetworkInfo: function() { 1.1191 + if (this._processingNetworkInfo) { 1.1192 + if (DEBUG) { 1.1193 + this.context.debug("Network info requested, but we're already " + 1.1194 + "requesting network info."); 1.1195 + } 1.1196 + this._needRepollNetworkInfo = true; 1.1197 + return; 1.1198 + } 1.1199 + 1.1200 + if (DEBUG) this.context.debug("Requesting network info"); 1.1201 + 1.1202 + this._processingNetworkInfo = true; 1.1203 + this.getVoiceRegistrationState(); 1.1204 + this.getDataRegistrationState(); //TODO only GSM 1.1205 + this.getOperator(); 1.1206 + this.getNetworkSelectionMode(); 1.1207 + this.getSignalStrength(); 1.1208 + }, 1.1209 + 1.1210 + /** 1.1211 + * Get the available networks 1.1212 + */ 1.1213 + getAvailableNetworks: function(options) { 1.1214 + if (DEBUG) this.context.debug("Getting available networks"); 1.1215 + let Buf = this.context.Buf; 1.1216 + Buf.newParcel(REQUEST_QUERY_AVAILABLE_NETWORKS, options); 1.1217 + Buf.sendParcel(); 1.1218 + }, 1.1219 + 1.1220 + /** 1.1221 + * Request the radio's network selection mode 1.1222 + */ 1.1223 + getNetworkSelectionMode: function() { 1.1224 + if (DEBUG) this.context.debug("Getting network selection mode"); 1.1225 + this.context.Buf.simpleRequest(REQUEST_QUERY_NETWORK_SELECTION_MODE); 1.1226 + }, 1.1227 + 1.1228 + /** 1.1229 + * Tell the radio to automatically choose a voice/data network 1.1230 + */ 1.1231 + selectNetworkAuto: function(options) { 1.1232 + if (DEBUG) this.context.debug("Setting automatic network selection"); 1.1233 + this.context.Buf.simpleRequest(REQUEST_SET_NETWORK_SELECTION_AUTOMATIC, options); 1.1234 + }, 1.1235 + 1.1236 + /** 1.1237 + * Set the roaming preference mode 1.1238 + */ 1.1239 + setRoamingPreference: function(options) { 1.1240 + let roamingMode = CDMA_ROAMING_PREFERENCE_TO_GECKO.indexOf(options.mode); 1.1241 + 1.1242 + if (roamingMode === -1) { 1.1243 + options.errorMsg = GECKO_ERROR_INVALID_PARAMETER; 1.1244 + this.sendChromeMessage(options); 1.1245 + return; 1.1246 + } 1.1247 + 1.1248 + let Buf = this.context.Buf; 1.1249 + Buf.newParcel(REQUEST_CDMA_SET_ROAMING_PREFERENCE, options); 1.1250 + Buf.writeInt32(1); 1.1251 + Buf.writeInt32(roamingMode); 1.1252 + Buf.sendParcel(); 1.1253 + }, 1.1254 + 1.1255 + /** 1.1256 + * Get the roaming preference mode 1.1257 + */ 1.1258 + queryRoamingPreference: function(options) { 1.1259 + this.context.Buf.simpleRequest(REQUEST_CDMA_QUERY_ROAMING_PREFERENCE, options); 1.1260 + }, 1.1261 + 1.1262 + /** 1.1263 + * Set the voice privacy mode 1.1264 + */ 1.1265 + setVoicePrivacyMode: function(options) { 1.1266 + let Buf = this.context.Buf; 1.1267 + Buf.newParcel(REQUEST_CDMA_SET_PREFERRED_VOICE_PRIVACY_MODE, options); 1.1268 + Buf.writeInt32(1); 1.1269 + Buf.writeInt32(options.enabled ? 1 : 0); 1.1270 + Buf.sendParcel(); 1.1271 + }, 1.1272 + 1.1273 + /** 1.1274 + * Get the voice privacy mode 1.1275 + */ 1.1276 + queryVoicePrivacyMode: function(options) { 1.1277 + this.context.Buf.simpleRequest(REQUEST_CDMA_QUERY_PREFERRED_VOICE_PRIVACY_MODE, options); 1.1278 + }, 1.1279 + 1.1280 + /** 1.1281 + * Open Logical UICC channel (aid) for Secure Element access 1.1282 + */ 1.1283 + iccOpenChannel: function(options) { 1.1284 + if (DEBUG) { 1.1285 + this.context.debug("iccOpenChannel: " + JSON.stringify(options)); 1.1286 + } 1.1287 + 1.1288 + let Buf = this.context.Buf; 1.1289 + Buf.newParcel(REQUEST_SIM_OPEN_CHANNEL, options); 1.1290 + Buf.writeString(options.aid); 1.1291 + Buf.sendParcel(); 1.1292 + }, 1.1293 + 1.1294 +/** 1.1295 + * Exchange APDU data on an open Logical UICC channel 1.1296 + */ 1.1297 + iccExchangeAPDU: function(options) { 1.1298 + if (DEBUG) this.context.debug("iccExchangeAPDU: " + JSON.stringify(options)); 1.1299 + 1.1300 + let cla = options.apdu.cla; 1.1301 + let command = options.apdu.command; 1.1302 + let channel = options.channel; 1.1303 + let path = options.apdu.path || ""; 1.1304 + let data = options.apdu.data || ""; 1.1305 + let data2 = options.apdu.data2 || ""; 1.1306 + 1.1307 + let p1 = options.apdu.p1; 1.1308 + let p2 = options.apdu.p2; 1.1309 + let p3 = options.apdu.p3; // Extra 1.1310 + 1.1311 + let Buf = this.context.Buf; 1.1312 + Buf.newParcel(REQUEST_SIM_ACCESS_CHANNEL, options); 1.1313 + Buf.writeInt32(cla); 1.1314 + Buf.writeInt32(command); 1.1315 + Buf.writeInt32(channel); 1.1316 + Buf.writeString(path); // path 1.1317 + Buf.writeInt32(p1); 1.1318 + Buf.writeInt32(p2); 1.1319 + Buf.writeInt32(p3); 1.1320 + Buf.writeString(data); // generic data field. 1.1321 + Buf.writeString(data2); 1.1322 + 1.1323 + Buf.sendParcel(); 1.1324 + }, 1.1325 + 1.1326 + /** 1.1327 + * Close Logical UICC channel 1.1328 + */ 1.1329 + iccCloseChannel: function(options) { 1.1330 + if (DEBUG) this.context.debug("iccCloseChannel: " + JSON.stringify(options)); 1.1331 + 1.1332 + let Buf = this.context.Buf; 1.1333 + Buf.newParcel(REQUEST_SIM_CLOSE_CHANNEL, options); 1.1334 + Buf.writeInt32(1); 1.1335 + Buf.writeInt32(options.channel); 1.1336 + Buf.sendParcel(); 1.1337 + }, 1.1338 + 1.1339 + /** 1.1340 + * Tell the radio to choose a specific voice/data network 1.1341 + */ 1.1342 + selectNetwork: function(options) { 1.1343 + if (DEBUG) { 1.1344 + this.context.debug("Setting manual network selection: " + 1.1345 + options.mcc + ", " + options.mnc); 1.1346 + } 1.1347 + 1.1348 + let numeric = (options.mcc && options.mnc) ? options.mcc + options.mnc : null; 1.1349 + let Buf = this.context.Buf; 1.1350 + Buf.newParcel(REQUEST_SET_NETWORK_SELECTION_MANUAL, options); 1.1351 + Buf.writeString(numeric); 1.1352 + Buf.sendParcel(); 1.1353 + }, 1.1354 + 1.1355 + /** 1.1356 + * Get current calls. 1.1357 + */ 1.1358 + getCurrentCalls: function() { 1.1359 + this.context.Buf.simpleRequest(REQUEST_GET_CURRENT_CALLS); 1.1360 + }, 1.1361 + 1.1362 + /** 1.1363 + * Get the signal strength. 1.1364 + */ 1.1365 + getSignalStrength: function() { 1.1366 + this.context.Buf.simpleRequest(REQUEST_SIGNAL_STRENGTH); 1.1367 + }, 1.1368 + 1.1369 + getIMEI: function(options) { 1.1370 + this.context.Buf.simpleRequest(REQUEST_GET_IMEI, options); 1.1371 + }, 1.1372 + 1.1373 + getIMEISV: function() { 1.1374 + this.context.Buf.simpleRequest(REQUEST_GET_IMEISV); 1.1375 + }, 1.1376 + 1.1377 + getDeviceIdentity: function() { 1.1378 + this.context.Buf.simpleRequest(REQUEST_DEVICE_IDENTITY); 1.1379 + }, 1.1380 + 1.1381 + getBasebandVersion: function() { 1.1382 + this.context.Buf.simpleRequest(REQUEST_BASEBAND_VERSION); 1.1383 + }, 1.1384 + 1.1385 + sendExitEmergencyCbModeRequest: function(options) { 1.1386 + this.context.Buf.simpleRequest(REQUEST_EXIT_EMERGENCY_CALLBACK_MODE, options); 1.1387 + }, 1.1388 + 1.1389 + getCdmaSubscription: function() { 1.1390 + this.context.Buf.simpleRequest(REQUEST_CDMA_SUBSCRIPTION); 1.1391 + }, 1.1392 + 1.1393 + exitEmergencyCbMode: function(options) { 1.1394 + // The function could be called by an API from RadioInterfaceLayer or by 1.1395 + // ril_worker itself. From ril_worker, we won't pass the parameter 1.1396 + // 'options'. In this case, it is marked as internal. 1.1397 + if (!options) { 1.1398 + options = {internal: true}; 1.1399 + } 1.1400 + this._cancelEmergencyCbModeTimeout(); 1.1401 + this.sendExitEmergencyCbModeRequest(options); 1.1402 + }, 1.1403 + 1.1404 + /** 1.1405 + * Cache the request for making an emergency call when radio is off. The 1.1406 + * request shall include two types of callback functions. 'callback' is 1.1407 + * called when radio is ready, and 'onerror' is called when turning radio 1.1408 + * on fails. 1.1409 + */ 1.1410 + cachedDialRequest : null, 1.1411 + 1.1412 + /** 1.1413 + * Dial the phone. 1.1414 + * 1.1415 + * @param number 1.1416 + * String containing the number to dial. 1.1417 + * @param clirMode 1.1418 + * Integer for showing/hidding the caller Id to the called party. 1.1419 + * @param uusInfo 1.1420 + * Integer doing something XXX TODO 1.1421 + */ 1.1422 + dial: function(options) { 1.1423 + let onerror = (function onerror(options, errorMsg) { 1.1424 + options.success = false; 1.1425 + options.errorMsg = errorMsg; 1.1426 + this.sendChromeMessage(options); 1.1427 + }).bind(this, options); 1.1428 + 1.1429 + if (this._isEmergencyNumber(options.number)) { 1.1430 + this.dialEmergencyNumber(options, onerror); 1.1431 + } else { 1.1432 + if (!this._isCdma) { 1.1433 + // TODO: Both dial() and sendMMI() functions should be unified at some 1.1434 + // point in the future. In the mean time we handle temporary CLIR MMI 1.1435 + // commands through the dial() function. Please see bug 889737. 1.1436 + let mmi = this._parseMMI(options.number); 1.1437 + if (mmi && this._isTemporaryModeCLIR(mmi)) { 1.1438 + options.number = mmi.dialNumber; 1.1439 + // In temporary mode, MMI_PROCEDURE_ACTIVATION means allowing CLI 1.1440 + // presentation, i.e. CLIR_SUPPRESSION. See TS 22.030, Annex B. 1.1441 + options.clirMode = mmi.procedure == MMI_PROCEDURE_ACTIVATION ? 1.1442 + CLIR_SUPPRESSION : CLIR_INVOCATION; 1.1443 + } 1.1444 + } 1.1445 + this.dialNonEmergencyNumber(options, onerror); 1.1446 + } 1.1447 + }, 1.1448 + 1.1449 + dialNonEmergencyNumber: function(options, onerror) { 1.1450 + if (this.radioState == GECKO_RADIOSTATE_OFF) { 1.1451 + // Notify error in establishing the call without radio. 1.1452 + onerror(GECKO_ERROR_RADIO_NOT_AVAILABLE); 1.1453 + return; 1.1454 + } 1.1455 + 1.1456 + if (this.voiceRegistrationState.emergencyCallsOnly || 1.1457 + options.isDialEmergency) { 1.1458 + onerror(RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[CALL_FAIL_UNOBTAINABLE_NUMBER]); 1.1459 + return; 1.1460 + } 1.1461 + 1.1462 + // Exit emergency callback mode when user dial a non-emergency call. 1.1463 + if (this._isInEmergencyCbMode) { 1.1464 + this.exitEmergencyCbMode(); 1.1465 + } 1.1466 + 1.1467 + if (this._isCdma && Object.keys(this.currentCalls).length == 1) { 1.1468 + // Make a Cdma 3way call. 1.1469 + options.featureStr = options.number; 1.1470 + this.sendCdmaFlashCommand(options); 1.1471 + } else { 1.1472 + options.request = REQUEST_DIAL; 1.1473 + this.sendDialRequest(options); 1.1474 + } 1.1475 + }, 1.1476 + 1.1477 + dialEmergencyNumber: function(options, onerror) { 1.1478 + options.request = RILQUIRKS_REQUEST_USE_DIAL_EMERGENCY_CALL ? 1.1479 + REQUEST_DIAL_EMERGENCY_CALL : REQUEST_DIAL; 1.1480 + if (this.radioState == GECKO_RADIOSTATE_OFF) { 1.1481 + if (DEBUG) { 1.1482 + this.context.debug("Automatically enable radio for an emergency call."); 1.1483 + } 1.1484 + 1.1485 + if (!this.cachedDialRequest) { 1.1486 + this.cachedDialRequest = {}; 1.1487 + } 1.1488 + this.cachedDialRequest.onerror = onerror; 1.1489 + this.cachedDialRequest.callback = this.sendDialRequest.bind(this, options); 1.1490 + this.setRadioEnabled({enabled: true}); 1.1491 + return; 1.1492 + } 1.1493 + 1.1494 + if (this._isCdma && Object.keys(this.currentCalls).length == 1) { 1.1495 + // Make a Cdma 3way call. 1.1496 + options.featureStr = options.number; 1.1497 + this.sendCdmaFlashCommand(options); 1.1498 + } else { 1.1499 + this.sendDialRequest(options); 1.1500 + } 1.1501 + }, 1.1502 + 1.1503 + sendDialRequest: function(options) { 1.1504 + // Always succeed. 1.1505 + options.success = true; 1.1506 + this.sendChromeMessage(options); 1.1507 + this._createPendingOutgoingCall(options); 1.1508 + 1.1509 + let Buf = this.context.Buf; 1.1510 + Buf.newParcel(options.request, options); 1.1511 + Buf.writeString(options.number); 1.1512 + Buf.writeInt32(options.clirMode || 0); 1.1513 + Buf.writeInt32(options.uusInfo || 0); 1.1514 + // TODO Why do we need this extra 0? It was put it in to make this 1.1515 + // match the format of the binary message. 1.1516 + Buf.writeInt32(0); 1.1517 + Buf.sendParcel(); 1.1518 + }, 1.1519 + 1.1520 + sendCdmaFlashCommand: function(options) { 1.1521 + let Buf = this.context.Buf; 1.1522 + options.isCdma = true; 1.1523 + options.request = REQUEST_CDMA_FLASH; 1.1524 + Buf.newParcel(options.request, options); 1.1525 + Buf.writeString(options.featureStr); 1.1526 + Buf.sendParcel(); 1.1527 + }, 1.1528 + 1.1529 + /** 1.1530 + * Hang up all calls 1.1531 + */ 1.1532 + hangUpAll: function() { 1.1533 + for (let callIndex in this.currentCalls) { 1.1534 + this.hangUp({callIndex: callIndex}); 1.1535 + } 1.1536 + }, 1.1537 + 1.1538 + /** 1.1539 + * Hang up the phone. 1.1540 + * 1.1541 + * @param callIndex 1.1542 + * Call index (1-based) as reported by REQUEST_GET_CURRENT_CALLS. 1.1543 + */ 1.1544 + hangUp: function(options) { 1.1545 + let call = this.currentCalls[options.callIndex]; 1.1546 + if (!call) { 1.1547 + return; 1.1548 + } 1.1549 + 1.1550 + let callIndex = call.callIndex; 1.1551 + if (callIndex === OUTGOING_PLACEHOLDER_CALL_INDEX) { 1.1552 + if (DEBUG) this.context.debug("Hang up pending outgoing call."); 1.1553 + this._removeVoiceCall(call, GECKO_CALL_ERROR_NORMAL_CALL_CLEARING); 1.1554 + return; 1.1555 + } 1.1556 + 1.1557 + call.hangUpLocal = true; 1.1558 + 1.1559 + if (call.state === CALL_STATE_HOLDING) { 1.1560 + this.sendHangUpBackgroundRequest(callIndex); 1.1561 + } else { 1.1562 + this.sendHangUpRequest(callIndex); 1.1563 + } 1.1564 + }, 1.1565 + 1.1566 + sendHangUpRequest: function(callIndex) { 1.1567 + let Buf = this.context.Buf; 1.1568 + Buf.newParcel(REQUEST_HANGUP); 1.1569 + Buf.writeInt32(1); 1.1570 + Buf.writeInt32(callIndex); 1.1571 + Buf.sendParcel(); 1.1572 + }, 1.1573 + 1.1574 + sendHangUpBackgroundRequest: function(callIndex) { 1.1575 + let Buf = this.context.Buf; 1.1576 + Buf.simpleRequest(REQUEST_HANGUP_WAITING_OR_BACKGROUND); 1.1577 + }, 1.1578 + 1.1579 + /** 1.1580 + * Mute or unmute the radio. 1.1581 + * 1.1582 + * @param mute 1.1583 + * Boolean to indicate whether to mute or unmute the radio. 1.1584 + */ 1.1585 + setMute: function(options) { 1.1586 + let Buf = this.context.Buf; 1.1587 + Buf.newParcel(REQUEST_SET_MUTE); 1.1588 + Buf.writeInt32(1); 1.1589 + Buf.writeInt32(options.muted ? 1 : 0); 1.1590 + Buf.sendParcel(); 1.1591 + }, 1.1592 + 1.1593 + /** 1.1594 + * Answer an incoming/waiting call. 1.1595 + * 1.1596 + * @param callIndex 1.1597 + * Call index of the call to answer. 1.1598 + */ 1.1599 + answerCall: function(options) { 1.1600 + // Check for races. Since we dispatched the incoming/waiting call 1.1601 + // notification the incoming/waiting call may have changed. The main 1.1602 + // thread thinks that it is answering the call with the given index, 1.1603 + // so only answer if that is still incoming/waiting. 1.1604 + let call = this.currentCalls[options.callIndex]; 1.1605 + if (!call) { 1.1606 + return; 1.1607 + } 1.1608 + 1.1609 + let Buf = this.context.Buf; 1.1610 + switch (call.state) { 1.1611 + case CALL_STATE_INCOMING: 1.1612 + Buf.simpleRequest(REQUEST_ANSWER); 1.1613 + break; 1.1614 + case CALL_STATE_WAITING: 1.1615 + // Answer the waiting (second) call, and hold the first call. 1.1616 + Buf.simpleRequest(REQUEST_SWITCH_WAITING_OR_HOLDING_AND_ACTIVE); 1.1617 + break; 1.1618 + } 1.1619 + }, 1.1620 + 1.1621 + /** 1.1622 + * Reject an incoming/waiting call. 1.1623 + * 1.1624 + * @param callIndex 1.1625 + * Call index of the call to reject. 1.1626 + */ 1.1627 + rejectCall: function(options) { 1.1628 + // Check for races. Since we dispatched the incoming/waiting call 1.1629 + // notification the incoming/waiting call may have changed. The main 1.1630 + // thread thinks that it is rejecting the call with the given index, 1.1631 + // so only reject if that is still incoming/waiting. 1.1632 + let call = this.currentCalls[options.callIndex]; 1.1633 + if (!call) { 1.1634 + return; 1.1635 + } 1.1636 + 1.1637 + call.hangUpLocal = true; 1.1638 + 1.1639 + let Buf = this.context.Buf; 1.1640 + if (this._isCdma) { 1.1641 + // AT+CHLD=0 means "release held or UDUB." 1.1642 + Buf.simpleRequest(REQUEST_HANGUP_WAITING_OR_BACKGROUND); 1.1643 + return; 1.1644 + } 1.1645 + 1.1646 + switch (call.state) { 1.1647 + case CALL_STATE_INCOMING: 1.1648 + Buf.simpleRequest(REQUEST_UDUB); 1.1649 + break; 1.1650 + case CALL_STATE_WAITING: 1.1651 + // Reject the waiting (second) call, and remain the first call. 1.1652 + Buf.simpleRequest(REQUEST_HANGUP_WAITING_OR_BACKGROUND); 1.1653 + break; 1.1654 + } 1.1655 + }, 1.1656 + 1.1657 + holdCall: function(options) { 1.1658 + let call = this.currentCalls[options.callIndex]; 1.1659 + if (!call) { 1.1660 + options.errorMsg = GECKO_ERROR_GENERIC_FAILURE; 1.1661 + options.success = false; 1.1662 + this.sendChromeMessage(options); 1.1663 + return; 1.1664 + } 1.1665 + 1.1666 + let Buf = this.context.Buf; 1.1667 + if (this._isCdma) { 1.1668 + options.featureStr = ""; 1.1669 + this.sendCdmaFlashCommand(options); 1.1670 + } else if (call.state == CALL_STATE_ACTIVE) { 1.1671 + Buf.simpleRequest(REQUEST_SWITCH_WAITING_OR_HOLDING_AND_ACTIVE, options); 1.1672 + } 1.1673 + }, 1.1674 + 1.1675 + resumeCall: function(options) { 1.1676 + let call = this.currentCalls[options.callIndex]; 1.1677 + if (!call) { 1.1678 + options.errorMsg = GECKO_ERROR_GENERIC_FAILURE; 1.1679 + options.success = false; 1.1680 + this.sendChromeMessage(options); 1.1681 + return; 1.1682 + } 1.1683 + 1.1684 + let Buf = this.context.Buf; 1.1685 + if (this._isCdma) { 1.1686 + options.featureStr = ""; 1.1687 + this.sendCdmaFlashCommand(options); 1.1688 + } else if (call.state == CALL_STATE_HOLDING) { 1.1689 + Buf.simpleRequest(REQUEST_SWITCH_WAITING_OR_HOLDING_AND_ACTIVE, options); 1.1690 + } 1.1691 + }, 1.1692 + 1.1693 + // Flag indicating whether user has requested making a conference call. 1.1694 + _hasConferenceRequest: false, 1.1695 + 1.1696 + conferenceCall: function(options) { 1.1697 + let Buf = this.context.Buf; 1.1698 + if (this._isCdma) { 1.1699 + options.featureStr = ""; 1.1700 + this.sendCdmaFlashCommand(options); 1.1701 + } else { 1.1702 + this._hasConferenceRequest = true; 1.1703 + Buf.simpleRequest(REQUEST_CONFERENCE, options); 1.1704 + } 1.1705 + }, 1.1706 + 1.1707 + separateCall: function(options) { 1.1708 + let call = this.currentCalls[options.callIndex]; 1.1709 + if (!call) { 1.1710 + options.errorName = "removeError"; 1.1711 + options.errorMsg = GECKO_ERROR_GENERIC_FAILURE; 1.1712 + options.success = false; 1.1713 + this.sendChromeMessage(options); 1.1714 + return; 1.1715 + } 1.1716 + 1.1717 + let Buf = this.context.Buf; 1.1718 + if (this._isCdma) { 1.1719 + options.featureStr = ""; 1.1720 + this.sendCdmaFlashCommand(options); 1.1721 + } else { 1.1722 + Buf.newParcel(REQUEST_SEPARATE_CONNECTION, options); 1.1723 + Buf.writeInt32(1); 1.1724 + Buf.writeInt32(options.callIndex); 1.1725 + Buf.sendParcel(); 1.1726 + } 1.1727 + }, 1.1728 + 1.1729 + holdConference: function() { 1.1730 + if (this._isCdma) { 1.1731 + return; 1.1732 + } 1.1733 + 1.1734 + this.context.Buf.simpleRequest(REQUEST_SWITCH_WAITING_OR_HOLDING_AND_ACTIVE); 1.1735 + }, 1.1736 + 1.1737 + resumeConference: function() { 1.1738 + if (this._isCdma) { 1.1739 + return; 1.1740 + } 1.1741 + 1.1742 + this.context.Buf.simpleRequest(REQUEST_SWITCH_WAITING_OR_HOLDING_AND_ACTIVE); 1.1743 + }, 1.1744 + 1.1745 + /** 1.1746 + * Send an SMS. 1.1747 + * 1.1748 + * The `options` parameter object should contain the following attributes: 1.1749 + * 1.1750 + * @param number 1.1751 + * String containing the recipient number. 1.1752 + * @param body 1.1753 + * String containing the message text. 1.1754 + * @param envelopeId 1.1755 + * Numeric value identifying the sms request. 1.1756 + */ 1.1757 + sendSMS: function(options) { 1.1758 + options.langIndex = options.langIndex || PDU_NL_IDENTIFIER_DEFAULT; 1.1759 + options.langShiftIndex = options.langShiftIndex || PDU_NL_IDENTIFIER_DEFAULT; 1.1760 + 1.1761 + if (!options.retryCount) { 1.1762 + options.retryCount = 0; 1.1763 + } 1.1764 + 1.1765 + if (!options.segmentSeq) { 1.1766 + // Fist segment to send 1.1767 + options.segmentSeq = 1; 1.1768 + options.body = options.segments[0].body; 1.1769 + options.encodedBodyLength = options.segments[0].encodedBodyLength; 1.1770 + } 1.1771 + 1.1772 + let Buf = this.context.Buf; 1.1773 + if (this._isCdma) { 1.1774 + Buf.newParcel(REQUEST_CDMA_SEND_SMS, options); 1.1775 + this.context.CdmaPDUHelper.writeMessage(options); 1.1776 + } else { 1.1777 + Buf.newParcel(REQUEST_SEND_SMS, options); 1.1778 + Buf.writeInt32(2); 1.1779 + Buf.writeString(options.SMSC); 1.1780 + this.context.GsmPDUHelper.writeMessage(options); 1.1781 + } 1.1782 + Buf.sendParcel(); 1.1783 + }, 1.1784 + 1.1785 + /** 1.1786 + * Acknowledge the receipt and handling of an SMS. 1.1787 + * 1.1788 + * @param success 1.1789 + * Boolean indicating whether the message was successfuly handled. 1.1790 + * @param cause 1.1791 + * SMS_* constant indicating the reason for unsuccessful handling. 1.1792 + */ 1.1793 + acknowledgeGsmSms: function(success, cause) { 1.1794 + let Buf = this.context.Buf; 1.1795 + Buf.newParcel(REQUEST_SMS_ACKNOWLEDGE); 1.1796 + Buf.writeInt32(2); 1.1797 + Buf.writeInt32(success ? 1 : 0); 1.1798 + Buf.writeInt32(cause); 1.1799 + Buf.sendParcel(); 1.1800 + }, 1.1801 + 1.1802 + /** 1.1803 + * Acknowledge the receipt and handling of an SMS. 1.1804 + * 1.1805 + * @param success 1.1806 + * Boolean indicating whether the message was successfuly handled. 1.1807 + */ 1.1808 + ackSMS: function(options) { 1.1809 + if (options.result == PDU_FCS_RESERVED) { 1.1810 + return; 1.1811 + } 1.1812 + if (this._isCdma) { 1.1813 + this.acknowledgeCdmaSms(options.result == PDU_FCS_OK, options.result); 1.1814 + } else { 1.1815 + this.acknowledgeGsmSms(options.result == PDU_FCS_OK, options.result); 1.1816 + } 1.1817 + }, 1.1818 + 1.1819 + /** 1.1820 + * Acknowledge the receipt and handling of a CDMA SMS. 1.1821 + * 1.1822 + * @param success 1.1823 + * Boolean indicating whether the message was successfuly handled. 1.1824 + * @param cause 1.1825 + * SMS_* constant indicating the reason for unsuccessful handling. 1.1826 + */ 1.1827 + acknowledgeCdmaSms: function(success, cause) { 1.1828 + let Buf = this.context.Buf; 1.1829 + Buf.newParcel(REQUEST_CDMA_SMS_ACKNOWLEDGE); 1.1830 + Buf.writeInt32(success ? 0 : 1); 1.1831 + Buf.writeInt32(cause); 1.1832 + Buf.sendParcel(); 1.1833 + }, 1.1834 + 1.1835 + /** 1.1836 + * Update received MWI into EF_MWIS. 1.1837 + */ 1.1838 + updateMwis: function(options) { 1.1839 + if (this.context.ICCUtilsHelper.isICCServiceAvailable("MWIS")) { 1.1840 + this.context.SimRecordHelper.updateMWIS(options.mwi); 1.1841 + } 1.1842 + }, 1.1843 + 1.1844 + setCellBroadcastDisabled: function(options) { 1.1845 + this.cellBroadcastDisabled = options.disabled; 1.1846 + 1.1847 + // If |this.mergedCellBroadcastConfig| is null, either we haven't finished 1.1848 + // reading required SIM files, or no any channel is ever configured. In 1.1849 + // the former case, we'll call |this.updateCellBroadcastConfig()| later 1.1850 + // with correct configs; in the latter case, we don't bother resetting CB 1.1851 + // to disabled again. 1.1852 + if (this.mergedCellBroadcastConfig) { 1.1853 + this.updateCellBroadcastConfig(); 1.1854 + } 1.1855 + }, 1.1856 + 1.1857 + setCellBroadcastSearchList: function(options) { 1.1858 + let getSearchListStr = function(aSearchList) { 1.1859 + if (typeof aSearchList === "string" || aSearchList instanceof String) { 1.1860 + return aSearchList; 1.1861 + } 1.1862 + 1.1863 + // TODO: Set search list for CDMA/GSM individually. Bug 990926 1.1864 + let prop = this._isCdma ? "cdma" : "gsm"; 1.1865 + 1.1866 + return aSearchList && aSearchList[prop]; 1.1867 + }.bind(this); 1.1868 + 1.1869 + try { 1.1870 + let str = getSearchListStr(options.searchList); 1.1871 + this.cellBroadcastConfigs.MMI = this._convertCellBroadcastSearchList(str); 1.1872 + options.success = true; 1.1873 + } catch (e) { 1.1874 + if (DEBUG) { 1.1875 + this.context.debug("Invalid Cell Broadcast search list: " + e); 1.1876 + } 1.1877 + options.success = false; 1.1878 + } 1.1879 + 1.1880 + this.sendChromeMessage(options); 1.1881 + if (!options.success) { 1.1882 + return; 1.1883 + } 1.1884 + 1.1885 + this._mergeAllCellBroadcastConfigs(); 1.1886 + }, 1.1887 + 1.1888 + updateCellBroadcastConfig: function() { 1.1889 + let activate = !this.cellBroadcastDisabled && 1.1890 + (this.mergedCellBroadcastConfig != null) && 1.1891 + (this.mergedCellBroadcastConfig.length > 0); 1.1892 + if (activate) { 1.1893 + this.setSmsBroadcastConfig(this.mergedCellBroadcastConfig); 1.1894 + } else { 1.1895 + // It's unnecessary to set config first if we're deactivating. 1.1896 + this.setSmsBroadcastActivation(false); 1.1897 + } 1.1898 + }, 1.1899 + 1.1900 + setGsmSmsBroadcastConfig: function(config) { 1.1901 + let Buf = this.context.Buf; 1.1902 + Buf.newParcel(REQUEST_GSM_SET_BROADCAST_SMS_CONFIG); 1.1903 + 1.1904 + let numConfigs = config ? config.length / 2 : 0; 1.1905 + Buf.writeInt32(numConfigs); 1.1906 + for (let i = 0; i < config.length;) { 1.1907 + Buf.writeInt32(config[i++]); 1.1908 + Buf.writeInt32(config[i++]); 1.1909 + Buf.writeInt32(0x00); 1.1910 + Buf.writeInt32(0xFF); 1.1911 + Buf.writeInt32(1); 1.1912 + } 1.1913 + 1.1914 + Buf.sendParcel(); 1.1915 + }, 1.1916 + 1.1917 + /** 1.1918 + * Send CDMA SMS broadcast config. 1.1919 + * 1.1920 + * @see 3GPP2 C.R1001 Sec. 9.2 and 9.3 1.1921 + */ 1.1922 + setCdmaSmsBroadcastConfig: function(config) { 1.1923 + let Buf = this.context.Buf; 1.1924 + // |config| is an array of half-closed range: [[from, to), [from, to), ...]. 1.1925 + // It will be further decomposed, ex: [1, 4) => 1, 2, 3. 1.1926 + Buf.newParcel(REQUEST_CDMA_SET_BROADCAST_SMS_CONFIG); 1.1927 + 1.1928 + let numConfigs = 0; 1.1929 + for (let i = 0; i < config.length; i += 2) { 1.1930 + numConfigs += (config[i+1] - config[i]); 1.1931 + } 1.1932 + 1.1933 + Buf.writeInt32(numConfigs); 1.1934 + for (let i = 0; i < config.length;) { 1.1935 + let begin = config[i++]; 1.1936 + let end = config[i++]; 1.1937 + 1.1938 + for (let j = begin; j < end; ++j) { 1.1939 + Buf.writeInt32(j); 1.1940 + Buf.writeInt32(0); // Language Indicator: Unknown or unspecified. 1.1941 + Buf.writeInt32(1); 1.1942 + } 1.1943 + } 1.1944 + 1.1945 + Buf.sendParcel(); 1.1946 + }, 1.1947 + 1.1948 + setSmsBroadcastConfig: function(config) { 1.1949 + if (this._isCdma) { 1.1950 + this.setCdmaSmsBroadcastConfig(config); 1.1951 + } else { 1.1952 + this.setGsmSmsBroadcastConfig(config); 1.1953 + } 1.1954 + }, 1.1955 + 1.1956 + setSmsBroadcastActivation: function(activate) { 1.1957 + let parcelType = this._isCdma ? REQUEST_CDMA_SMS_BROADCAST_ACTIVATION : 1.1958 + REQUEST_GSM_SMS_BROADCAST_ACTIVATION; 1.1959 + let Buf = this.context.Buf; 1.1960 + Buf.newParcel(parcelType); 1.1961 + Buf.writeInt32(1); 1.1962 + // See hardware/ril/include/telephony/ril.h, 0 - Activate, 1 - Turn off. 1.1963 + Buf.writeInt32(activate ? 0 : 1); 1.1964 + Buf.sendParcel(); 1.1965 + }, 1.1966 + 1.1967 + /** 1.1968 + * Start a DTMF Tone. 1.1969 + * 1.1970 + * @param dtmfChar 1.1971 + * DTMF signal to send, 0-9, *, + 1.1972 + */ 1.1973 + startTone: function(options) { 1.1974 + let Buf = this.context.Buf; 1.1975 + Buf.newParcel(REQUEST_DTMF_START); 1.1976 + Buf.writeString(options.dtmfChar); 1.1977 + Buf.sendParcel(); 1.1978 + }, 1.1979 + 1.1980 + stopTone: function() { 1.1981 + this.context.Buf.simpleRequest(REQUEST_DTMF_STOP); 1.1982 + }, 1.1983 + 1.1984 + /** 1.1985 + * Send a DTMF tone. 1.1986 + * 1.1987 + * @param dtmfChar 1.1988 + * DTMF signal to send, 0-9, *, + 1.1989 + */ 1.1990 + sendTone: function(options) { 1.1991 + let Buf = this.context.Buf; 1.1992 + Buf.newParcel(REQUEST_DTMF); 1.1993 + Buf.writeString(options.dtmfChar); 1.1994 + Buf.sendParcel(); 1.1995 + }, 1.1996 + 1.1997 + /** 1.1998 + * Get the Short Message Service Center address. 1.1999 + */ 1.2000 + getSmscAddress: function(options) { 1.2001 + if (!this.SMSC) { 1.2002 + this.context.Buf.simpleRequest(REQUEST_GET_SMSC_ADDRESS, options); 1.2003 + return; 1.2004 + } 1.2005 + 1.2006 + if (!options || options.rilMessageType !== "getSmscAddress") { 1.2007 + return; 1.2008 + } 1.2009 + 1.2010 + options.smscAddress = this.SMSC; 1.2011 + options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError]; 1.2012 + this.sendChromeMessage(options); 1.2013 + }, 1.2014 + 1.2015 + /** 1.2016 + * Set the Short Message Service Center address. 1.2017 + * 1.2018 + * @param smscAddress 1.2019 + * Short Message Service Center address in PDU format. 1.2020 + */ 1.2021 + setSmscAddress: function(options) { 1.2022 + let Buf = this.context.Buf; 1.2023 + Buf.newParcel(REQUEST_SET_SMSC_ADDRESS, options); 1.2024 + Buf.writeString(options.smscAddress); 1.2025 + Buf.sendParcel(); 1.2026 + }, 1.2027 + 1.2028 + /** 1.2029 + * Setup a data call. 1.2030 + * 1.2031 + * @param radioTech 1.2032 + * Integer to indicate radio technology. 1.2033 + * DATACALL_RADIOTECHNOLOGY_CDMA => CDMA. 1.2034 + * DATACALL_RADIOTECHNOLOGY_GSM => GSM. 1.2035 + * @param apn 1.2036 + * String containing the name of the APN to connect to. 1.2037 + * @param user 1.2038 + * String containing the username for the APN. 1.2039 + * @param passwd 1.2040 + * String containing the password for the APN. 1.2041 + * @param chappap 1.2042 + * Integer containing CHAP/PAP auth type. 1.2043 + * DATACALL_AUTH_NONE => PAP and CHAP is never performed. 1.2044 + * DATACALL_AUTH_PAP => PAP may be performed. 1.2045 + * DATACALL_AUTH_CHAP => CHAP may be performed. 1.2046 + * DATACALL_AUTH_PAP_OR_CHAP => PAP / CHAP may be performed. 1.2047 + * @param pdptype 1.2048 + * String containing PDP type to request. ("IP", "IPV6", ...) 1.2049 + */ 1.2050 + setupDataCall: function(options) { 1.2051 + // From ./hardware/ril/include/telephony/ril.h: 1.2052 + // ((const char **)data)[0] Radio technology to use: 0-CDMA, 1-GSM/UMTS, 2... 1.2053 + // for values above 2 this is RIL_RadioTechnology + 2. 1.2054 + // 1.2055 + // From frameworks/base/telephony/java/com/android/internal/telephony/DataConnection.java: 1.2056 + // if the mRilVersion < 6, radio technology must be GSM/UMTS or CDMA. 1.2057 + // Otherwise, it must be + 2 1.2058 + // 1.2059 + // See also bug 901232 and 867873 1.2060 + let radioTech; 1.2061 + if (this.v5Legacy) { 1.2062 + radioTech = this._isCdma ? DATACALL_RADIOTECHNOLOGY_CDMA 1.2063 + : DATACALL_RADIOTECHNOLOGY_GSM; 1.2064 + } else { 1.2065 + radioTech = options.radioTech + 2; 1.2066 + } 1.2067 + let Buf = this.context.Buf; 1.2068 + let token = Buf.newParcel(REQUEST_SETUP_DATA_CALL, options); 1.2069 + Buf.writeInt32(7); 1.2070 + Buf.writeString(radioTech.toString()); 1.2071 + Buf.writeString(DATACALL_PROFILE_DEFAULT.toString()); 1.2072 + Buf.writeString(options.apn); 1.2073 + Buf.writeString(options.user); 1.2074 + Buf.writeString(options.passwd); 1.2075 + Buf.writeString(options.chappap.toString()); 1.2076 + Buf.writeString(options.pdptype); 1.2077 + Buf.sendParcel(); 1.2078 + return token; 1.2079 + }, 1.2080 + 1.2081 + /** 1.2082 + * Deactivate a data call. 1.2083 + * 1.2084 + * @param cid 1.2085 + * String containing CID. 1.2086 + * @param reason 1.2087 + * One of DATACALL_DEACTIVATE_* constants. 1.2088 + */ 1.2089 + deactivateDataCall: function(options) { 1.2090 + let datacall = this.currentDataCalls[options.cid]; 1.2091 + if (!datacall) { 1.2092 + return; 1.2093 + } 1.2094 + 1.2095 + let Buf = this.context.Buf; 1.2096 + Buf.newParcel(REQUEST_DEACTIVATE_DATA_CALL, options); 1.2097 + Buf.writeInt32(2); 1.2098 + Buf.writeString(options.cid); 1.2099 + Buf.writeString(options.reason || DATACALL_DEACTIVATE_NO_REASON); 1.2100 + Buf.sendParcel(); 1.2101 + 1.2102 + datacall.state = GECKO_NETWORK_STATE_DISCONNECTING; 1.2103 + this.sendChromeMessage(datacall); 1.2104 + }, 1.2105 + 1.2106 + /** 1.2107 + * Get a list of data calls. 1.2108 + */ 1.2109 + getDataCallList: function() { 1.2110 + this.context.Buf.simpleRequest(REQUEST_DATA_CALL_LIST); 1.2111 + }, 1.2112 + 1.2113 + _attachDataRegistration: false, 1.2114 + /** 1.2115 + * Manually attach/detach data registration. 1.2116 + * 1.2117 + * @param attach 1.2118 + * Boolean value indicating attach or detach. 1.2119 + */ 1.2120 + setDataRegistration: function(options) { 1.2121 + let request = options.attach ? RIL_REQUEST_GPRS_ATTACH : 1.2122 + RIL_REQUEST_GPRS_DETACH; 1.2123 + this._attachDataRegistration = options.attach; 1.2124 + this.context.Buf.simpleRequest(request); 1.2125 + }, 1.2126 + 1.2127 + /** 1.2128 + * Get failure casue code for the most recently failed PDP context. 1.2129 + */ 1.2130 + getFailCauseCode: function(callback) { 1.2131 + this.context.Buf.simpleRequest(REQUEST_LAST_CALL_FAIL_CAUSE, 1.2132 + {callback: callback}); 1.2133 + }, 1.2134 + 1.2135 + /** 1.2136 + * Helper to parse MMI/USSD string. TS.22.030 Figure 3.5.3.2. 1.2137 + */ 1.2138 + _parseMMI: function(mmiString) { 1.2139 + if (!mmiString || !mmiString.length) { 1.2140 + return null; 1.2141 + } 1.2142 + 1.2143 + let matches = this._matchMMIRegexp(mmiString); 1.2144 + if (matches) { 1.2145 + // After successfully executing the regular expresion over the MMI string, 1.2146 + // the following match groups should contain: 1.2147 + // 1 = full MMI string that might be used as a USSD request. 1.2148 + // 2 = MMI procedure. 1.2149 + // 3 = Service code. 1.2150 + // 5 = SIA. 1.2151 + // 7 = SIB. 1.2152 + // 9 = SIC. 1.2153 + // 11 = Password registration. 1.2154 + // 12 = Dialing number. 1.2155 + return { 1.2156 + fullMMI: matches[MMI_MATCH_GROUP_FULL_MMI], 1.2157 + procedure: matches[MMI_MATCH_GROUP_MMI_PROCEDURE], 1.2158 + serviceCode: matches[MMI_MATCH_GROUP_SERVICE_CODE], 1.2159 + sia: matches[MMI_MATCH_GROUP_SIA], 1.2160 + sib: matches[MMI_MATCH_GROUP_SIB], 1.2161 + sic: matches[MMI_MATCH_GROUP_SIC], 1.2162 + pwd: matches[MMI_MATCH_GROUP_PWD_CONFIRM], 1.2163 + dialNumber: matches[MMI_MATCH_GROUP_DIALING_NUMBER] 1.2164 + }; 1.2165 + } 1.2166 + 1.2167 + if (this._isPoundString(mmiString) || 1.2168 + this._isMMIShortString(mmiString)) { 1.2169 + return { 1.2170 + fullMMI: mmiString 1.2171 + }; 1.2172 + } 1.2173 + 1.2174 + return null; 1.2175 + }, 1.2176 + 1.2177 + /** 1.2178 + * Helper to parse MMI string via regular expression. TS.22.030 Figure 1.2179 + * 3.5.3.2. 1.2180 + */ 1.2181 + _matchMMIRegexp: function(mmiString) { 1.2182 + // Regexp to parse and process the MMI code. 1.2183 + if (this._mmiRegExp == null) { 1.2184 + // The first group of the regexp takes the whole MMI string. 1.2185 + // The second group takes the MMI procedure that can be: 1.2186 + // - Activation (*SC*SI#). 1.2187 + // - Deactivation (#SC*SI#). 1.2188 + // - Interrogation (*#SC*SI#). 1.2189 + // - Registration (**SC*SI#). 1.2190 + // - Erasure (##SC*SI#). 1.2191 + // where SC = Service Code (2 or 3 digits) and SI = Supplementary Info 1.2192 + // (variable length). 1.2193 + let pattern = "((\\*[*#]?|##?)"; 1.2194 + 1.2195 + // Third group of the regexp looks for the MMI Service code, which is a 1.2196 + // 2 or 3 digits that uniquely specifies the Supplementary Service 1.2197 + // associated with the MMI code. 1.2198 + pattern += "(\\d{2,3})"; 1.2199 + 1.2200 + // Groups from 4 to 9 looks for the MMI Supplementary Information SIA, 1.2201 + // SIB and SIC. SIA may comprise e.g. a PIN code or Directory Number, 1.2202 + // SIB may be used to specify the tele or bearer service and SIC to 1.2203 + // specify the value of the "No Reply Condition Timer". Where a particular 1.2204 + // service request does not require any SI, "*SI" is not entered. The use 1.2205 + // of SIA, SIB and SIC is optional and shall be entered in any of the 1.2206 + // following formats: 1.2207 + // - *SIA*SIB*SIC# 1.2208 + // - *SIA*SIB# 1.2209 + // - *SIA**SIC# 1.2210 + // - *SIA# 1.2211 + // - **SIB*SIC# 1.2212 + // - ***SISC# 1.2213 + pattern += "(\\*([^*#]*)(\\*([^*#]*)(\\*([^*#]*)"; 1.2214 + 1.2215 + // The eleventh group takes the password for the case of a password 1.2216 + // registration procedure. 1.2217 + pattern += "(\\*([^*#]*))?)?)?)?#)"; 1.2218 + 1.2219 + // The last group takes the dial string after the #. 1.2220 + pattern += "([^#]*)"; 1.2221 + 1.2222 + this._mmiRegExp = new RegExp(pattern); 1.2223 + } 1.2224 + 1.2225 + // Regex only applys for those well-defined MMI strings (refer to TS.22.030 1.2226 + // Annex B), otherwise, null should be the expected return value. 1.2227 + return this._mmiRegExp.exec(mmiString); 1.2228 + }, 1.2229 + 1.2230 + /** 1.2231 + * Helper to parse # string. TS.22.030 Figure 3.5.3.2. 1.2232 + */ 1.2233 + _isPoundString: function(mmiString) { 1.2234 + return (mmiString.charAt(mmiString.length - 1) === MMI_END_OF_USSD); 1.2235 + }, 1.2236 + 1.2237 + /** 1.2238 + * Helper to parse short string. TS.22.030 Figure 3.5.3.2. 1.2239 + */ 1.2240 + _isMMIShortString: function(mmiString) { 1.2241 + if (mmiString.length > 2) { 1.2242 + return false; 1.2243 + } 1.2244 + 1.2245 + if (this._isEmergencyNumber(mmiString)) { 1.2246 + return false; 1.2247 + } 1.2248 + 1.2249 + // In a call case. 1.2250 + if (Object.getOwnPropertyNames(this.currentCalls).length > 0) { 1.2251 + return true; 1.2252 + } 1.2253 + 1.2254 + if ((mmiString.length != 2) || (mmiString.charAt(0) !== '1')) { 1.2255 + return true; 1.2256 + } 1.2257 + 1.2258 + return false; 1.2259 + }, 1.2260 + 1.2261 + sendMMI: function(options) { 1.2262 + if (DEBUG) { 1.2263 + this.context.debug("SendMMI " + JSON.stringify(options)); 1.2264 + } 1.2265 + let mmiString = options.mmi; 1.2266 + let mmi = this._parseMMI(mmiString); 1.2267 + 1.2268 + let _sendMMIError = (function(errorMsg, mmiServiceCode) { 1.2269 + options.success = false; 1.2270 + options.errorMsg = errorMsg; 1.2271 + if (mmiServiceCode) { 1.2272 + options.mmiServiceCode = mmiServiceCode; 1.2273 + } 1.2274 + this.sendChromeMessage(options); 1.2275 + }).bind(this); 1.2276 + 1.2277 + function _isValidPINPUKRequest(mmiServiceCode) { 1.2278 + // The only allowed MMI procedure for ICC PIN, PIN2, PUK and PUK2 handling 1.2279 + // is "Registration" (**). 1.2280 + if (!mmi.procedure || mmi.procedure != MMI_PROCEDURE_REGISTRATION ) { 1.2281 + _sendMMIError(MMI_ERROR_KS_INVALID_ACTION, mmiServiceCode); 1.2282 + return false; 1.2283 + } 1.2284 + 1.2285 + if (!mmi.sia || !mmi.sia.length || !mmi.sib || !mmi.sib.length || 1.2286 + !mmi.sic || !mmi.sic.length) { 1.2287 + _sendMMIError(MMI_ERROR_KS_ERROR, mmiServiceCode); 1.2288 + return false; 1.2289 + } 1.2290 + 1.2291 + if (mmi.sib != mmi.sic) { 1.2292 + _sendMMIError(MMI_ERROR_KS_MISMATCH_PIN, mmiServiceCode); 1.2293 + return false; 1.2294 + } 1.2295 + 1.2296 + if (mmi.sia.length < 4 || mmi.sia.length > 8 || 1.2297 + mmi.sib.length < 4 || mmi.sib.length > 8 || 1.2298 + mmi.sic.length < 4 || mmi.sic.length > 8) { 1.2299 + _sendMMIError(MMI_ERROR_KS_INVALID_PIN, mmiServiceCode); 1.2300 + return false; 1.2301 + } 1.2302 + 1.2303 + return true; 1.2304 + } 1.2305 + 1.2306 + let _isRadioAvailable = (function(mmiServiceCode) { 1.2307 + if (this.radioState !== GECKO_RADIOSTATE_READY) { 1.2308 + _sendMMIError(GECKO_ERROR_RADIO_NOT_AVAILABLE, mmiServiceCode); 1.2309 + return false; 1.2310 + } 1.2311 + return true; 1.2312 + }).bind(this); 1.2313 + 1.2314 + // If we couldn't parse the MMI code, we'll send it as an USSD request. 1.2315 + if (mmi === null) { 1.2316 + if (this._ussdSession) { 1.2317 + if (!_isRadioAvailable(MMI_KS_SC_USSD)) { 1.2318 + return; 1.2319 + } 1.2320 + options.ussd = mmiString; 1.2321 + this.sendUSSD(options); 1.2322 + return; 1.2323 + } 1.2324 + 1.2325 + _sendMMIError(MMI_ERROR_KS_ERROR); 1.2326 + return; 1.2327 + } 1.2328 + 1.2329 + if (DEBUG) { 1.2330 + this.context.debug("MMI " + JSON.stringify(mmi)); 1.2331 + } 1.2332 + 1.2333 + // We check if the MMI service code is supported and in that case we 1.2334 + // trigger the appropriate RIL request if possible. 1.2335 + let sc = mmi.serviceCode; 1.2336 + switch (sc) { 1.2337 + // Call forwarding 1.2338 + case MMI_SC_CFU: 1.2339 + case MMI_SC_CF_BUSY: 1.2340 + case MMI_SC_CF_NO_REPLY: 1.2341 + case MMI_SC_CF_NOT_REACHABLE: 1.2342 + case MMI_SC_CF_ALL: 1.2343 + case MMI_SC_CF_ALL_CONDITIONAL: 1.2344 + if (!_isRadioAvailable(MMI_KS_SC_CALL_FORWARDING)) { 1.2345 + return; 1.2346 + } 1.2347 + // Call forwarding requires at least an action, given by the MMI 1.2348 + // procedure, and a reason, given by the MMI service code, but there 1.2349 + // is no way that we get this far without a valid procedure or service 1.2350 + // code. 1.2351 + options.mmiServiceCode = MMI_KS_SC_CALL_FORWARDING; 1.2352 + options.action = MMI_PROC_TO_CF_ACTION[mmi.procedure]; 1.2353 + options.reason = MMI_SC_TO_CF_REASON[sc]; 1.2354 + options.number = mmi.sia; 1.2355 + options.serviceClass = this._siToServiceClass(mmi.sib); 1.2356 + if (options.action == CALL_FORWARD_ACTION_QUERY_STATUS) { 1.2357 + this.queryCallForwardStatus(options); 1.2358 + return; 1.2359 + } 1.2360 + 1.2361 + options.isSetCallForward = true; 1.2362 + options.timeSeconds = mmi.sic; 1.2363 + this.setCallForward(options); 1.2364 + return; 1.2365 + 1.2366 + // Change the current ICC PIN number. 1.2367 + case MMI_SC_PIN: 1.2368 + // As defined in TS.122.030 6.6.2 to change the ICC PIN we should expect 1.2369 + // an MMI code of the form **04*OLD_PIN*NEW_PIN*NEW_PIN#, where old PIN 1.2370 + // should be entered as the SIA parameter and the new PIN as SIB and 1.2371 + // SIC. 1.2372 + if (!_isRadioAvailable(MMI_KS_SC_PIN) || 1.2373 + !_isValidPINPUKRequest(MMI_KS_SC_PIN)) { 1.2374 + return; 1.2375 + } 1.2376 + 1.2377 + options.mmiServiceCode = MMI_KS_SC_PIN; 1.2378 + options.pin = mmi.sia; 1.2379 + options.newPin = mmi.sib; 1.2380 + this.changeICCPIN(options); 1.2381 + return; 1.2382 + 1.2383 + // Change the current ICC PIN2 number. 1.2384 + case MMI_SC_PIN2: 1.2385 + // As defined in TS.122.030 6.6.2 to change the ICC PIN2 we should 1.2386 + // enter and MMI code of the form **042*OLD_PIN2*NEW_PIN2*NEW_PIN2#, 1.2387 + // where the old PIN2 should be entered as the SIA parameter and the 1.2388 + // new PIN2 as SIB and SIC. 1.2389 + if (!_isRadioAvailable(MMI_KS_SC_PIN2) || 1.2390 + !_isValidPINPUKRequest(MMI_KS_SC_PIN2)) { 1.2391 + return; 1.2392 + } 1.2393 + 1.2394 + options.mmiServiceCode = MMI_KS_SC_PIN2; 1.2395 + options.pin = mmi.sia; 1.2396 + options.newPin = mmi.sib; 1.2397 + this.changeICCPIN2(options); 1.2398 + return; 1.2399 + 1.2400 + // Unblock ICC PIN. 1.2401 + case MMI_SC_PUK: 1.2402 + // As defined in TS.122.030 6.6.3 to unblock the ICC PIN we should 1.2403 + // enter an MMI code of the form **05*PUK*NEW_PIN*NEW_PIN#, where PUK 1.2404 + // should be entered as the SIA parameter and the new PIN as SIB and 1.2405 + // SIC. 1.2406 + if (!_isRadioAvailable(MMI_KS_SC_PUK) || 1.2407 + !_isValidPINPUKRequest(MMI_KS_SC_PUK)) { 1.2408 + return; 1.2409 + } 1.2410 + 1.2411 + options.mmiServiceCode = MMI_KS_SC_PUK; 1.2412 + options.puk = mmi.sia; 1.2413 + options.newPin = mmi.sib; 1.2414 + this.enterICCPUK(options); 1.2415 + return; 1.2416 + 1.2417 + // Unblock ICC PIN2. 1.2418 + case MMI_SC_PUK2: 1.2419 + // As defined in TS.122.030 6.6.3 to unblock the ICC PIN2 we should 1.2420 + // enter an MMI code of the form **052*PUK2*NEW_PIN2*NEW_PIN2#, where 1.2421 + // PUK2 should be entered as the SIA parameter and the new PIN2 as SIB 1.2422 + // and SIC. 1.2423 + if (!_isRadioAvailable(MMI_KS_SC_PUK2) || 1.2424 + !_isValidPINPUKRequest(MMI_KS_SC_PUK2)) { 1.2425 + return; 1.2426 + } 1.2427 + 1.2428 + options.mmiServiceCode = MMI_KS_SC_PUK2; 1.2429 + options.puk = mmi.sia; 1.2430 + options.newPin = mmi.sib; 1.2431 + this.enterICCPUK2(options); 1.2432 + return; 1.2433 + 1.2434 + // IMEI 1.2435 + case MMI_SC_IMEI: 1.2436 + // A device's IMEI can't change, so we only need to request it once. 1.2437 + if (this.IMEI == null) { 1.2438 + this.getIMEI(options); 1.2439 + return; 1.2440 + } 1.2441 + // If we already had the device's IMEI, we just send it to chrome. 1.2442 + options.mmiServiceCode = MMI_KS_SC_IMEI; 1.2443 + options.success = true; 1.2444 + options.statusMessage = this.IMEI; 1.2445 + this.sendChromeMessage(options); 1.2446 + return; 1.2447 + 1.2448 + // CLIP 1.2449 + case MMI_SC_CLIP: 1.2450 + options.mmiServiceCode = MMI_KS_SC_CLIP; 1.2451 + options.procedure = mmi.procedure; 1.2452 + if (options.procedure === MMI_PROCEDURE_INTERROGATION) { 1.2453 + this.queryCLIP(options); 1.2454 + } else { 1.2455 + _sendMMIError(MMI_ERROR_KS_NOT_SUPPORTED, MMI_KS_SC_CLIP); 1.2456 + } 1.2457 + return; 1.2458 + 1.2459 + // CLIR (non-temporary ones) 1.2460 + // TODO: Both dial() and sendMMI() functions should be unified at some 1.2461 + // point in the future. In the mean time we handle temporary CLIR MMI 1.2462 + // commands through the dial() function. Please see bug 889737. 1.2463 + case MMI_SC_CLIR: 1.2464 + options.mmiServiceCode = MMI_KS_SC_CLIR; 1.2465 + options.procedure = mmi.procedure; 1.2466 + switch (options.procedure) { 1.2467 + case MMI_PROCEDURE_INTERROGATION: 1.2468 + this.getCLIR(options); 1.2469 + return; 1.2470 + case MMI_PROCEDURE_ACTIVATION: 1.2471 + options.clirMode = CLIR_INVOCATION; 1.2472 + break; 1.2473 + case MMI_PROCEDURE_DEACTIVATION: 1.2474 + options.clirMode = CLIR_SUPPRESSION; 1.2475 + break; 1.2476 + default: 1.2477 + _sendMMIError(MMI_ERROR_KS_NOT_SUPPORTED, MMI_KS_SC_CLIR); 1.2478 + return; 1.2479 + } 1.2480 + options.isSetCLIR = true; 1.2481 + this.setCLIR(options); 1.2482 + return; 1.2483 + 1.2484 + // Call barring 1.2485 + case MMI_SC_BAOC: 1.2486 + case MMI_SC_BAOIC: 1.2487 + case MMI_SC_BAOICxH: 1.2488 + case MMI_SC_BAIC: 1.2489 + case MMI_SC_BAICr: 1.2490 + case MMI_SC_BA_ALL: 1.2491 + case MMI_SC_BA_MO: 1.2492 + case MMI_SC_BA_MT: 1.2493 + options.mmiServiceCode = MMI_KS_SC_CALL_BARRING; 1.2494 + options.password = mmi.sia || ""; 1.2495 + options.serviceClass = this._siToServiceClass(mmi.sib); 1.2496 + options.facility = MMI_SC_TO_CB_FACILITY[sc]; 1.2497 + options.procedure = mmi.procedure; 1.2498 + if (mmi.procedure === MMI_PROCEDURE_INTERROGATION) { 1.2499 + this.queryICCFacilityLock(options); 1.2500 + return; 1.2501 + } 1.2502 + if (mmi.procedure === MMI_PROCEDURE_ACTIVATION) { 1.2503 + options.enabled = 1; 1.2504 + } else if (mmi.procedure === MMI_PROCEDURE_DEACTIVATION) { 1.2505 + options.enabled = 0; 1.2506 + } else { 1.2507 + _sendMMIError(MMI_ERROR_KS_NOT_SUPPORTED, MMI_KS_SC_CALL_BARRING); 1.2508 + return; 1.2509 + } 1.2510 + this.setICCFacilityLock(options); 1.2511 + return; 1.2512 + 1.2513 + // Call waiting 1.2514 + case MMI_SC_CALL_WAITING: 1.2515 + if (!_isRadioAvailable(MMI_KS_SC_CALL_WAITING)) { 1.2516 + return; 1.2517 + } 1.2518 + 1.2519 + options.mmiServiceCode = MMI_KS_SC_CALL_WAITING; 1.2520 + 1.2521 + if (mmi.procedure === MMI_PROCEDURE_INTERROGATION) { 1.2522 + this._handleQueryMMICallWaiting(options); 1.2523 + return; 1.2524 + } 1.2525 + 1.2526 + if (mmi.procedure === MMI_PROCEDURE_ACTIVATION) { 1.2527 + options.enabled = true; 1.2528 + } else if (mmi.procedure === MMI_PROCEDURE_DEACTIVATION) { 1.2529 + options.enabled = false; 1.2530 + } else { 1.2531 + _sendMMIError(MMI_ERROR_KS_NOT_SUPPORTED, MMI_KS_SC_CALL_WAITING); 1.2532 + return; 1.2533 + } 1.2534 + 1.2535 + options.serviceClass = this._siToServiceClass(mmi.sia); 1.2536 + this._handleSetMMICallWaiting(options); 1.2537 + return; 1.2538 + } 1.2539 + 1.2540 + // If the MMI code is not a known code and is a recognized USSD request, 1.2541 + // it shall still be sent as a USSD request. 1.2542 + if (mmi.fullMMI) { 1.2543 + if (!_isRadioAvailable(MMI_KS_SC_USSD)) { 1.2544 + return; 1.2545 + } 1.2546 + 1.2547 + options.ussd = mmi.fullMMI; 1.2548 + options.mmiServiceCode = MMI_KS_SC_USSD; 1.2549 + this.sendUSSD(options); 1.2550 + return; 1.2551 + } 1.2552 + 1.2553 + // At this point, the MMI string is considered as not valid MMI code and 1.2554 + // not valid USSD code. 1.2555 + _sendMMIError(MMI_ERROR_KS_ERROR); 1.2556 + }, 1.2557 + 1.2558 + /** 1.2559 + * Send USSD. 1.2560 + * 1.2561 + * @param ussd 1.2562 + * String containing the USSD code. 1.2563 + * 1.2564 + */ 1.2565 + sendUSSD: function(options) { 1.2566 + let Buf = this.context.Buf; 1.2567 + Buf.newParcel(REQUEST_SEND_USSD, options); 1.2568 + Buf.writeString(options.ussd); 1.2569 + Buf.sendParcel(); 1.2570 + }, 1.2571 + 1.2572 + /** 1.2573 + * Cancel pending USSD. 1.2574 + */ 1.2575 + cancelUSSD: function(options) { 1.2576 + options.mmiServiceCode = MMI_KS_SC_USSD; 1.2577 + this.context.Buf.simpleRequest(REQUEST_CANCEL_USSD, options); 1.2578 + }, 1.2579 + 1.2580 + /** 1.2581 + * Queries current call forward rules. 1.2582 + * 1.2583 + * @param reason 1.2584 + * One of nsIDOMMozMobileCFInfo.CALL_FORWARD_REASON_* constants. 1.2585 + * @param serviceClass 1.2586 + * One of ICC_SERVICE_CLASS_* constants. 1.2587 + * @param number 1.2588 + * Phone number of forwarding address. 1.2589 + */ 1.2590 + queryCallForwardStatus: function(options) { 1.2591 + let Buf = this.context.Buf; 1.2592 + let number = options.number || ""; 1.2593 + Buf.newParcel(REQUEST_QUERY_CALL_FORWARD_STATUS, options); 1.2594 + Buf.writeInt32(CALL_FORWARD_ACTION_QUERY_STATUS); 1.2595 + Buf.writeInt32(options.reason); 1.2596 + Buf.writeInt32(options.serviceClass || ICC_SERVICE_CLASS_NONE); 1.2597 + Buf.writeInt32(this._toaFromString(number)); 1.2598 + Buf.writeString(number); 1.2599 + Buf.writeInt32(0); 1.2600 + Buf.sendParcel(); 1.2601 + }, 1.2602 + 1.2603 + /** 1.2604 + * Configures call forward rule. 1.2605 + * 1.2606 + * @param action 1.2607 + * One of nsIDOMMozMobileCFInfo.CALL_FORWARD_ACTION_* constants. 1.2608 + * @param reason 1.2609 + * One of nsIDOMMozMobileCFInfo.CALL_FORWARD_REASON_* constants. 1.2610 + * @param serviceClass 1.2611 + * One of ICC_SERVICE_CLASS_* constants. 1.2612 + * @param number 1.2613 + * Phone number of forwarding address. 1.2614 + * @param timeSeconds 1.2615 + * Time in seconds to wait beforec all is forwarded. 1.2616 + */ 1.2617 + setCallForward: function(options) { 1.2618 + let Buf = this.context.Buf; 1.2619 + Buf.newParcel(REQUEST_SET_CALL_FORWARD, options); 1.2620 + Buf.writeInt32(options.action); 1.2621 + Buf.writeInt32(options.reason); 1.2622 + Buf.writeInt32(options.serviceClass); 1.2623 + Buf.writeInt32(this._toaFromString(options.number)); 1.2624 + Buf.writeString(options.number); 1.2625 + Buf.writeInt32(options.timeSeconds); 1.2626 + Buf.sendParcel(); 1.2627 + }, 1.2628 + 1.2629 + /** 1.2630 + * Queries current call barring rules. 1.2631 + * 1.2632 + * @param program 1.2633 + * One of nsIDOMMozMobileConnection.CALL_BARRING_PROGRAM_* constants. 1.2634 + * @param serviceClass 1.2635 + * One of ICC_SERVICE_CLASS_* constants. 1.2636 + */ 1.2637 + queryCallBarringStatus: function(options) { 1.2638 + options.facility = CALL_BARRING_PROGRAM_TO_FACILITY[options.program]; 1.2639 + options.password = ""; // For query no need to provide it. 1.2640 + this.queryICCFacilityLock(options); 1.2641 + }, 1.2642 + 1.2643 + /** 1.2644 + * Configures call barring rule. 1.2645 + * 1.2646 + * @param program 1.2647 + * One of nsIDOMMozMobileConnection.CALL_BARRING_PROGRAM_* constants. 1.2648 + * @param enabled 1.2649 + * Enable or disable the call barring. 1.2650 + * @param password 1.2651 + * Barring password. 1.2652 + * @param serviceClass 1.2653 + * One of ICC_SERVICE_CLASS_* constants. 1.2654 + */ 1.2655 + setCallBarring: function(options) { 1.2656 + options.facility = CALL_BARRING_PROGRAM_TO_FACILITY[options.program]; 1.2657 + this.setICCFacilityLock(options); 1.2658 + }, 1.2659 + 1.2660 + /** 1.2661 + * Change call barring facility password. 1.2662 + * 1.2663 + * @param pin 1.2664 + * Old password. 1.2665 + * @param newPin 1.2666 + * New password. 1.2667 + */ 1.2668 + changeCallBarringPassword: function(options) { 1.2669 + let Buf = this.context.Buf; 1.2670 + Buf.newParcel(REQUEST_CHANGE_BARRING_PASSWORD, options); 1.2671 + Buf.writeInt32(3); 1.2672 + // Set facility to ICC_CB_FACILITY_BA_ALL by following TS.22.030 clause 1.2673 + // 6.5.4 and Table B.1. 1.2674 + Buf.writeString(ICC_CB_FACILITY_BA_ALL); 1.2675 + Buf.writeString(options.pin); 1.2676 + Buf.writeString(options.newPin); 1.2677 + Buf.sendParcel(); 1.2678 + }, 1.2679 + 1.2680 + /** 1.2681 + * Handle STK CALL_SET_UP request. 1.2682 + * 1.2683 + * @param hasConfirmed 1.2684 + * Does use have confirmed the call requested from ICC? 1.2685 + */ 1.2686 + stkHandleCallSetup: function(options) { 1.2687 + let Buf = this.context.Buf; 1.2688 + Buf.newParcel(REQUEST_STK_HANDLE_CALL_SETUP_REQUESTED_FROM_SIM); 1.2689 + Buf.writeInt32(1); 1.2690 + Buf.writeInt32(options.hasConfirmed ? 1 : 0); 1.2691 + Buf.sendParcel(); 1.2692 + }, 1.2693 + 1.2694 + /** 1.2695 + * Send STK Profile Download. 1.2696 + * 1.2697 + * @param profile Profile supported by ME. 1.2698 + */ 1.2699 + sendStkTerminalProfile: function(profile) { 1.2700 + let Buf = this.context.Buf; 1.2701 + let GsmPDUHelper = this.context.GsmPDUHelper; 1.2702 + 1.2703 + Buf.newParcel(REQUEST_STK_SET_PROFILE); 1.2704 + Buf.writeInt32(profile.length * 2); 1.2705 + for (let i = 0; i < profile.length; i++) { 1.2706 + GsmPDUHelper.writeHexOctet(profile[i]); 1.2707 + } 1.2708 + Buf.writeInt32(0); 1.2709 + Buf.sendParcel(); 1.2710 + }, 1.2711 + 1.2712 + /** 1.2713 + * Send STK terminal response. 1.2714 + * 1.2715 + * @param command 1.2716 + * @param deviceIdentities 1.2717 + * @param resultCode 1.2718 + * @param [optional] itemIdentifier 1.2719 + * @param [optional] input 1.2720 + * @param [optional] isYesNo 1.2721 + * @param [optional] hasConfirmed 1.2722 + * @param [optional] localInfo 1.2723 + * @param [optional] timer 1.2724 + */ 1.2725 + sendStkTerminalResponse: function(response) { 1.2726 + if (response.hasConfirmed !== undefined) { 1.2727 + this.stkHandleCallSetup(response); 1.2728 + return; 1.2729 + } 1.2730 + 1.2731 + let Buf = this.context.Buf; 1.2732 + let ComprehensionTlvHelper = this.context.ComprehensionTlvHelper; 1.2733 + let GsmPDUHelper = this.context.GsmPDUHelper; 1.2734 + 1.2735 + let command = response.command; 1.2736 + Buf.newParcel(REQUEST_STK_SEND_TERMINAL_RESPONSE); 1.2737 + 1.2738 + // 1st mark for Parcel size 1.2739 + Buf.startCalOutgoingSize(function(size) { 1.2740 + // Parcel size is in string length, which costs 2 uint8 per char. 1.2741 + Buf.writeInt32(size / 2); 1.2742 + }); 1.2743 + 1.2744 + // Command Details 1.2745 + GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_COMMAND_DETAILS | 1.2746 + COMPREHENSIONTLV_FLAG_CR); 1.2747 + GsmPDUHelper.writeHexOctet(3); 1.2748 + if (response.command) { 1.2749 + GsmPDUHelper.writeHexOctet(command.commandNumber); 1.2750 + GsmPDUHelper.writeHexOctet(command.typeOfCommand); 1.2751 + GsmPDUHelper.writeHexOctet(command.commandQualifier); 1.2752 + } else { 1.2753 + GsmPDUHelper.writeHexOctet(0x00); 1.2754 + GsmPDUHelper.writeHexOctet(0x00); 1.2755 + GsmPDUHelper.writeHexOctet(0x00); 1.2756 + } 1.2757 + 1.2758 + // Device Identifier 1.2759 + // According to TS102.223/TS31.111 section 6.8 Structure of 1.2760 + // TERMINAL RESPONSE, "For all SIMPLE-TLV objects with Min=N, 1.2761 + // the ME should set the CR(comprehension required) flag to 1.2762 + // comprehension not required.(CR=0)" 1.2763 + // Since DEVICE_IDENTITIES and DURATION TLVs have Min=N, 1.2764 + // the CR flag is not set. 1.2765 + GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_DEVICE_ID); 1.2766 + GsmPDUHelper.writeHexOctet(2); 1.2767 + GsmPDUHelper.writeHexOctet(STK_DEVICE_ID_ME); 1.2768 + GsmPDUHelper.writeHexOctet(STK_DEVICE_ID_SIM); 1.2769 + 1.2770 + // Result 1.2771 + GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_RESULT | 1.2772 + COMPREHENSIONTLV_FLAG_CR); 1.2773 + GsmPDUHelper.writeHexOctet(1); 1.2774 + GsmPDUHelper.writeHexOctet(response.resultCode); 1.2775 + 1.2776 + // Item Identifier 1.2777 + if (response.itemIdentifier != null) { 1.2778 + GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_ITEM_ID | 1.2779 + COMPREHENSIONTLV_FLAG_CR); 1.2780 + GsmPDUHelper.writeHexOctet(1); 1.2781 + GsmPDUHelper.writeHexOctet(response.itemIdentifier); 1.2782 + } 1.2783 + 1.2784 + // No need to process Text data if user requests help information. 1.2785 + if (response.resultCode != STK_RESULT_HELP_INFO_REQUIRED) { 1.2786 + let text; 1.2787 + if (response.isYesNo !== undefined) { 1.2788 + // GET_INKEY 1.2789 + // When the ME issues a successful TERMINAL RESPONSE for a GET INKEY 1.2790 + // ("Yes/No") command with command qualifier set to "Yes/No", it shall 1.2791 + // supply the value '01' when the answer is "positive" and the value 1.2792 + // '00' when the answer is "negative" in the Text string data object. 1.2793 + text = response.isYesNo ? 0x01 : 0x00; 1.2794 + } else { 1.2795 + text = response.input; 1.2796 + } 1.2797 + 1.2798 + if (text) { 1.2799 + GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_TEXT_STRING | 1.2800 + COMPREHENSIONTLV_FLAG_CR); 1.2801 + 1.2802 + // 2nd mark for text length 1.2803 + Buf.startCalOutgoingSize(function(size) { 1.2804 + // Text length is in number of hexOctets, which costs 4 uint8 per hexOctet. 1.2805 + GsmPDUHelper.writeHexOctet(size / 4); 1.2806 + }); 1.2807 + 1.2808 + let coding = command.options.isUCS2 ? 1.2809 + STK_TEXT_CODING_UCS2 : 1.2810 + (command.options.isPacked ? 1.2811 + STK_TEXT_CODING_GSM_7BIT_PACKED : 1.2812 + STK_TEXT_CODING_GSM_8BIT); 1.2813 + GsmPDUHelper.writeHexOctet(coding); 1.2814 + 1.2815 + // Write Text String. 1.2816 + switch (coding) { 1.2817 + case STK_TEXT_CODING_UCS2: 1.2818 + GsmPDUHelper.writeUCS2String(text); 1.2819 + break; 1.2820 + case STK_TEXT_CODING_GSM_7BIT_PACKED: 1.2821 + GsmPDUHelper.writeStringAsSeptets(text, 0, 0, 0); 1.2822 + break; 1.2823 + case STK_TEXT_CODING_GSM_8BIT: 1.2824 + for (let i = 0; i < text.length; i++) { 1.2825 + GsmPDUHelper.writeHexOctet(text.charCodeAt(i)); 1.2826 + } 1.2827 + break; 1.2828 + } 1.2829 + 1.2830 + // Calculate and write text length to 2nd mark 1.2831 + Buf.stopCalOutgoingSize(); 1.2832 + } 1.2833 + } 1.2834 + 1.2835 + // Local Information 1.2836 + if (response.localInfo) { 1.2837 + let localInfo = response.localInfo; 1.2838 + 1.2839 + // Location Infomation 1.2840 + if (localInfo.locationInfo) { 1.2841 + ComprehensionTlvHelper.writeLocationInfoTlv(localInfo.locationInfo); 1.2842 + } 1.2843 + 1.2844 + // IMEI 1.2845 + if (localInfo.imei != null) { 1.2846 + let imei = localInfo.imei; 1.2847 + if (imei.length == 15) { 1.2848 + imei = imei + "0"; 1.2849 + } 1.2850 + 1.2851 + GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_IMEI); 1.2852 + GsmPDUHelper.writeHexOctet(8); 1.2853 + for (let i = 0; i < imei.length / 2; i++) { 1.2854 + GsmPDUHelper.writeHexOctet(parseInt(imei.substr(i * 2, 2), 16)); 1.2855 + } 1.2856 + } 1.2857 + 1.2858 + // Date and Time Zone 1.2859 + if (localInfo.date != null) { 1.2860 + ComprehensionTlvHelper.writeDateTimeZoneTlv(localInfo.date); 1.2861 + } 1.2862 + 1.2863 + // Language 1.2864 + if (localInfo.language) { 1.2865 + ComprehensionTlvHelper.writeLanguageTlv(localInfo.language); 1.2866 + } 1.2867 + } 1.2868 + 1.2869 + // Timer 1.2870 + if (response.timer) { 1.2871 + let timer = response.timer; 1.2872 + 1.2873 + if (timer.timerId) { 1.2874 + GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_TIMER_IDENTIFIER); 1.2875 + GsmPDUHelper.writeHexOctet(1); 1.2876 + GsmPDUHelper.writeHexOctet(timer.timerId); 1.2877 + } 1.2878 + 1.2879 + if (timer.timerValue) { 1.2880 + ComprehensionTlvHelper.writeTimerValueTlv(timer.timerValue, false); 1.2881 + } 1.2882 + } 1.2883 + 1.2884 + // Calculate and write Parcel size to 1st mark 1.2885 + Buf.stopCalOutgoingSize(); 1.2886 + 1.2887 + Buf.writeInt32(0); 1.2888 + Buf.sendParcel(); 1.2889 + }, 1.2890 + 1.2891 + /** 1.2892 + * Send STK Envelope(Menu Selection) command. 1.2893 + * 1.2894 + * @param itemIdentifier 1.2895 + * @param helpRequested 1.2896 + */ 1.2897 + sendStkMenuSelection: function(command) { 1.2898 + command.tag = BER_MENU_SELECTION_TAG; 1.2899 + command.deviceId = { 1.2900 + sourceId :STK_DEVICE_ID_KEYPAD, 1.2901 + destinationId: STK_DEVICE_ID_SIM 1.2902 + }; 1.2903 + this.sendICCEnvelopeCommand(command); 1.2904 + }, 1.2905 + 1.2906 + /** 1.2907 + * Send STK Envelope(Timer Expiration) command. 1.2908 + * 1.2909 + * @param timer 1.2910 + */ 1.2911 + sendStkTimerExpiration: function(command) { 1.2912 + command.tag = BER_TIMER_EXPIRATION_TAG; 1.2913 + command.deviceId = { 1.2914 + sourceId: STK_DEVICE_ID_ME, 1.2915 + destinationId: STK_DEVICE_ID_SIM 1.2916 + }; 1.2917 + command.timerId = command.timer.timerId; 1.2918 + command.timerValue = command.timer.timerValue; 1.2919 + this.sendICCEnvelopeCommand(command); 1.2920 + }, 1.2921 + 1.2922 + /** 1.2923 + * Send STK Envelope(Event Download) command. 1.2924 + * @param event 1.2925 + */ 1.2926 + sendStkEventDownload: function(command) { 1.2927 + command.tag = BER_EVENT_DOWNLOAD_TAG; 1.2928 + command.eventList = command.event.eventType; 1.2929 + switch (command.eventList) { 1.2930 + case STK_EVENT_TYPE_LOCATION_STATUS: 1.2931 + command.deviceId = { 1.2932 + sourceId :STK_DEVICE_ID_ME, 1.2933 + destinationId: STK_DEVICE_ID_SIM 1.2934 + }; 1.2935 + command.locationStatus = command.event.locationStatus; 1.2936 + // Location info should only be provided when locationStatus is normal. 1.2937 + if (command.locationStatus == STK_SERVICE_STATE_NORMAL) { 1.2938 + command.locationInfo = command.event.locationInfo; 1.2939 + } 1.2940 + break; 1.2941 + case STK_EVENT_TYPE_MT_CALL: 1.2942 + command.deviceId = { 1.2943 + sourceId: STK_DEVICE_ID_NETWORK, 1.2944 + destinationId: STK_DEVICE_ID_SIM 1.2945 + }; 1.2946 + command.transactionId = 0; 1.2947 + command.address = command.event.number; 1.2948 + break; 1.2949 + case STK_EVENT_TYPE_CALL_DISCONNECTED: 1.2950 + command.cause = command.event.error; 1.2951 + // Fall through. 1.2952 + case STK_EVENT_TYPE_CALL_CONNECTED: 1.2953 + command.deviceId = { 1.2954 + sourceId: (command.event.isIssuedByRemote ? 1.2955 + STK_DEVICE_ID_NETWORK : STK_DEVICE_ID_ME), 1.2956 + destinationId: STK_DEVICE_ID_SIM 1.2957 + }; 1.2958 + command.transactionId = 0; 1.2959 + break; 1.2960 + case STK_EVENT_TYPE_USER_ACTIVITY: 1.2961 + command.deviceId = { 1.2962 + sourceId: STK_DEVICE_ID_ME, 1.2963 + destinationId: STK_DEVICE_ID_SIM 1.2964 + }; 1.2965 + break; 1.2966 + case STK_EVENT_TYPE_IDLE_SCREEN_AVAILABLE: 1.2967 + command.deviceId = { 1.2968 + sourceId: STK_DEVICE_ID_DISPLAY, 1.2969 + destinationId: STK_DEVICE_ID_SIM 1.2970 + }; 1.2971 + break; 1.2972 + case STK_EVENT_TYPE_LANGUAGE_SELECTION: 1.2973 + command.deviceId = { 1.2974 + sourceId: STK_DEVICE_ID_ME, 1.2975 + destinationId: STK_DEVICE_ID_SIM 1.2976 + }; 1.2977 + command.language = command.event.language; 1.2978 + break; 1.2979 + case STK_EVENT_TYPE_BROWSER_TERMINATION: 1.2980 + command.deviceId = { 1.2981 + sourceId: STK_DEVICE_ID_ME, 1.2982 + destinationId: STK_DEVICE_ID_SIM 1.2983 + }; 1.2984 + command.terminationCause = command.event.terminationCause; 1.2985 + break; 1.2986 + } 1.2987 + this.sendICCEnvelopeCommand(command); 1.2988 + }, 1.2989 + 1.2990 + /** 1.2991 + * Send REQUEST_STK_SEND_ENVELOPE_COMMAND to ICC. 1.2992 + * 1.2993 + * @param tag 1.2994 + * @patam deviceId 1.2995 + * @param [optioanl] itemIdentifier 1.2996 + * @param [optional] helpRequested 1.2997 + * @param [optional] eventList 1.2998 + * @param [optional] locationStatus 1.2999 + * @param [optional] locationInfo 1.3000 + * @param [optional] address 1.3001 + * @param [optional] transactionId 1.3002 + * @param [optional] cause 1.3003 + * @param [optional] timerId 1.3004 + * @param [optional] timerValue 1.3005 + * @param [optional] terminationCause 1.3006 + */ 1.3007 + sendICCEnvelopeCommand: function(options) { 1.3008 + if (DEBUG) { 1.3009 + this.context.debug("Stk Envelope " + JSON.stringify(options)); 1.3010 + } 1.3011 + 1.3012 + let Buf = this.context.Buf; 1.3013 + let ComprehensionTlvHelper = this.context.ComprehensionTlvHelper; 1.3014 + let GsmPDUHelper = this.context.GsmPDUHelper; 1.3015 + 1.3016 + Buf.newParcel(REQUEST_STK_SEND_ENVELOPE_COMMAND); 1.3017 + 1.3018 + // 1st mark for Parcel size 1.3019 + Buf.startCalOutgoingSize(function(size) { 1.3020 + // Parcel size is in string length, which costs 2 uint8 per char. 1.3021 + Buf.writeInt32(size / 2); 1.3022 + }); 1.3023 + 1.3024 + // Write a BER-TLV 1.3025 + GsmPDUHelper.writeHexOctet(options.tag); 1.3026 + // 2nd mark for BER length 1.3027 + Buf.startCalOutgoingSize(function(size) { 1.3028 + // BER length is in number of hexOctets, which costs 4 uint8 per hexOctet. 1.3029 + GsmPDUHelper.writeHexOctet(size / 4); 1.3030 + }); 1.3031 + 1.3032 + // Device Identifies 1.3033 + GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_DEVICE_ID | 1.3034 + COMPREHENSIONTLV_FLAG_CR); 1.3035 + GsmPDUHelper.writeHexOctet(2); 1.3036 + GsmPDUHelper.writeHexOctet(options.deviceId.sourceId); 1.3037 + GsmPDUHelper.writeHexOctet(options.deviceId.destinationId); 1.3038 + 1.3039 + // Item Identifier 1.3040 + if (options.itemIdentifier != null) { 1.3041 + GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_ITEM_ID | 1.3042 + COMPREHENSIONTLV_FLAG_CR); 1.3043 + GsmPDUHelper.writeHexOctet(1); 1.3044 + GsmPDUHelper.writeHexOctet(options.itemIdentifier); 1.3045 + } 1.3046 + 1.3047 + // Help Request 1.3048 + if (options.helpRequested) { 1.3049 + GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_HELP_REQUEST | 1.3050 + COMPREHENSIONTLV_FLAG_CR); 1.3051 + GsmPDUHelper.writeHexOctet(0); 1.3052 + // Help Request doesn't have value 1.3053 + } 1.3054 + 1.3055 + // Event List 1.3056 + if (options.eventList != null) { 1.3057 + GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_EVENT_LIST | 1.3058 + COMPREHENSIONTLV_FLAG_CR); 1.3059 + GsmPDUHelper.writeHexOctet(1); 1.3060 + GsmPDUHelper.writeHexOctet(options.eventList); 1.3061 + } 1.3062 + 1.3063 + // Location Status 1.3064 + if (options.locationStatus != null) { 1.3065 + let len = options.locationStatus.length; 1.3066 + GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_LOCATION_STATUS | 1.3067 + COMPREHENSIONTLV_FLAG_CR); 1.3068 + GsmPDUHelper.writeHexOctet(1); 1.3069 + GsmPDUHelper.writeHexOctet(options.locationStatus); 1.3070 + } 1.3071 + 1.3072 + // Location Info 1.3073 + if (options.locationInfo) { 1.3074 + ComprehensionTlvHelper.writeLocationInfoTlv(options.locationInfo); 1.3075 + } 1.3076 + 1.3077 + // Transaction Id 1.3078 + if (options.transactionId != null) { 1.3079 + GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_TRANSACTION_ID | 1.3080 + COMPREHENSIONTLV_FLAG_CR); 1.3081 + GsmPDUHelper.writeHexOctet(1); 1.3082 + GsmPDUHelper.writeHexOctet(options.transactionId); 1.3083 + } 1.3084 + 1.3085 + // Address 1.3086 + if (options.address) { 1.3087 + GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_ADDRESS | 1.3088 + COMPREHENSIONTLV_FLAG_CR); 1.3089 + ComprehensionTlvHelper.writeLength( 1.3090 + Math.ceil(options.address.length/2) + 1 // address BCD + TON 1.3091 + ); 1.3092 + this.context.ICCPDUHelper.writeDiallingNumber(options.address); 1.3093 + } 1.3094 + 1.3095 + // Cause of disconnection. 1.3096 + if (options.cause != null) { 1.3097 + ComprehensionTlvHelper.writeCauseTlv(options.cause); 1.3098 + } 1.3099 + 1.3100 + // Timer Identifier 1.3101 + if (options.timerId != null) { 1.3102 + GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_TIMER_IDENTIFIER | 1.3103 + COMPREHENSIONTLV_FLAG_CR); 1.3104 + GsmPDUHelper.writeHexOctet(1); 1.3105 + GsmPDUHelper.writeHexOctet(options.timerId); 1.3106 + } 1.3107 + 1.3108 + // Timer Value 1.3109 + if (options.timerValue != null) { 1.3110 + ComprehensionTlvHelper.writeTimerValueTlv(options.timerValue, true); 1.3111 + } 1.3112 + 1.3113 + // Language 1.3114 + if (options.language) { 1.3115 + ComprehensionTlvHelper.writeLanguageTlv(options.language); 1.3116 + } 1.3117 + 1.3118 + // Browser Termination 1.3119 + if (options.terminationCause != null) { 1.3120 + GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_BROWSER_TERMINATION_CAUSE | 1.3121 + COMPREHENSIONTLV_FLAG_CR); 1.3122 + GsmPDUHelper.writeHexOctet(1); 1.3123 + GsmPDUHelper.writeHexOctet(options.terminationCause); 1.3124 + } 1.3125 + 1.3126 + // Calculate and write BER length to 2nd mark 1.3127 + Buf.stopCalOutgoingSize(); 1.3128 + 1.3129 + // Calculate and write Parcel size to 1st mark 1.3130 + Buf.stopCalOutgoingSize(); 1.3131 + 1.3132 + Buf.writeInt32(0); 1.3133 + Buf.sendParcel(); 1.3134 + }, 1.3135 + 1.3136 + /** 1.3137 + * Check a given number against the list of emergency numbers provided by the RIL. 1.3138 + * 1.3139 + * @param number 1.3140 + * The number to look up. 1.3141 + */ 1.3142 + _isEmergencyNumber: function(number) { 1.3143 + // Check ril provided numbers first. 1.3144 + let numbers = RIL_EMERGENCY_NUMBERS; 1.3145 + 1.3146 + if (numbers) { 1.3147 + numbers = numbers.split(","); 1.3148 + } else { 1.3149 + // No ecclist system property, so use our own list. 1.3150 + numbers = DEFAULT_EMERGENCY_NUMBERS; 1.3151 + } 1.3152 + 1.3153 + return numbers.indexOf(number) != -1; 1.3154 + }, 1.3155 + 1.3156 + /** 1.3157 + * Checks whether to temporarily suppress caller id for the call. 1.3158 + * 1.3159 + * @param mmi 1.3160 + * MMI full object. 1.3161 + */ 1.3162 + _isTemporaryModeCLIR: function(mmi) { 1.3163 + return (mmi && 1.3164 + mmi.serviceCode == MMI_SC_CLIR && 1.3165 + mmi.dialNumber && 1.3166 + (mmi.procedure == MMI_PROCEDURE_ACTIVATION || 1.3167 + mmi.procedure == MMI_PROCEDURE_DEACTIVATION)); 1.3168 + }, 1.3169 + 1.3170 + /** 1.3171 + * Report STK Service is running. 1.3172 + */ 1.3173 + reportStkServiceIsRunning: function() { 1.3174 + this.context.Buf.simpleRequest(REQUEST_REPORT_STK_SERVICE_IS_RUNNING); 1.3175 + }, 1.3176 + 1.3177 + /** 1.3178 + * Process ICC status. 1.3179 + */ 1.3180 + _processICCStatus: function(iccStatus) { 1.3181 + // If |_waitingRadioTech| is true, we should not get app information because 1.3182 + // the |_isCdma| flag is not ready yet. Otherwise we may use wrong index to 1.3183 + // get app information, especially for the case that icc card has both cdma 1.3184 + // and gsm subscription. 1.3185 + if (this._waitingRadioTech) { 1.3186 + return; 1.3187 + } 1.3188 + 1.3189 + this.iccStatus = iccStatus; 1.3190 + let newCardState; 1.3191 + let index = this._isCdma ? iccStatus.cdmaSubscriptionAppIndex : 1.3192 + iccStatus.gsmUmtsSubscriptionAppIndex; 1.3193 + let app = iccStatus.apps[index]; 1.3194 + 1.3195 + // When |iccStatus.cardState| is not CARD_STATE_PRESENT or have incorrect 1.3196 + // app information, we can not get iccId. So treat ICC as undetected. 1.3197 + if (iccStatus.cardState !== CARD_STATE_PRESENT || !app) { 1.3198 + if (this.cardState !== GECKO_CARDSTATE_UNDETECTED) { 1.3199 + this.operator = null; 1.3200 + // We should send |cardstatechange| before |iccinfochange|, otherwise we 1.3201 + // may lost cardstatechange event when icc card becomes undetected. 1.3202 + this.cardState = GECKO_CARDSTATE_UNDETECTED; 1.3203 + this.sendChromeMessage({rilMessageType: "cardstatechange", 1.3204 + cardState: this.cardState}); 1.3205 + 1.3206 + this.iccInfo = {iccType: null}; 1.3207 + this.context.ICCUtilsHelper.handleICCInfoChange(); 1.3208 + } 1.3209 + return; 1.3210 + } 1.3211 + 1.3212 + let ICCRecordHelper = this.context.ICCRecordHelper; 1.3213 + // fetchICCRecords will need to read aid, so read aid here. 1.3214 + this.aid = app.aid; 1.3215 + this.appType = app.app_type; 1.3216 + this.iccInfo.iccType = GECKO_CARD_TYPE[this.appType]; 1.3217 + // Try to get iccId only when cardState left GECKO_CARDSTATE_UNDETECTED. 1.3218 + if (iccStatus.cardState === CARD_STATE_PRESENT && 1.3219 + (this.cardState === GECKO_CARDSTATE_UNINITIALIZED || 1.3220 + this.cardState === GECKO_CARDSTATE_UNDETECTED)) { 1.3221 + ICCRecordHelper.readICCID(); 1.3222 + } 1.3223 + 1.3224 + switch (app.app_state) { 1.3225 + case CARD_APPSTATE_ILLEGAL: 1.3226 + newCardState = GECKO_CARDSTATE_ILLEGAL; 1.3227 + break; 1.3228 + case CARD_APPSTATE_PIN: 1.3229 + newCardState = GECKO_CARDSTATE_PIN_REQUIRED; 1.3230 + break; 1.3231 + case CARD_APPSTATE_PUK: 1.3232 + newCardState = GECKO_CARDSTATE_PUK_REQUIRED; 1.3233 + break; 1.3234 + case CARD_APPSTATE_SUBSCRIPTION_PERSO: 1.3235 + newCardState = PERSONSUBSTATE[app.perso_substate]; 1.3236 + break; 1.3237 + case CARD_APPSTATE_READY: 1.3238 + newCardState = GECKO_CARDSTATE_READY; 1.3239 + break; 1.3240 + case CARD_APPSTATE_UNKNOWN: 1.3241 + case CARD_APPSTATE_DETECTED: 1.3242 + // Fall through. 1.3243 + default: 1.3244 + newCardState = GECKO_CARDSTATE_UNKNOWN; 1.3245 + } 1.3246 + 1.3247 + let pin1State = app.pin1_replaced ? iccStatus.universalPINState : 1.3248 + app.pin1; 1.3249 + if (pin1State === CARD_PINSTATE_ENABLED_PERM_BLOCKED) { 1.3250 + newCardState = GECKO_CARDSTATE_PERMANENT_BLOCKED; 1.3251 + } 1.3252 + 1.3253 + if (this.cardState == newCardState) { 1.3254 + return; 1.3255 + } 1.3256 + 1.3257 + // This was moved down from CARD_APPSTATE_READY 1.3258 + this.requestNetworkInfo(); 1.3259 + if (newCardState == GECKO_CARDSTATE_READY) { 1.3260 + // For type SIM, we need to check EF_phase first. 1.3261 + // Other types of ICC we can send Terminal_Profile immediately. 1.3262 + if (this.appType == CARD_APPTYPE_SIM) { 1.3263 + this.context.SimRecordHelper.readSimPhase(); 1.3264 + } else if (RILQUIRKS_SEND_STK_PROFILE_DOWNLOAD) { 1.3265 + this.sendStkTerminalProfile(STK_SUPPORTED_TERMINAL_PROFILE); 1.3266 + } 1.3267 + 1.3268 + ICCRecordHelper.fetchICCRecords(); 1.3269 + } 1.3270 + 1.3271 + this.cardState = newCardState; 1.3272 + this.sendChromeMessage({rilMessageType: "cardstatechange", 1.3273 + cardState: this.cardState}); 1.3274 + }, 1.3275 + 1.3276 + /** 1.3277 + * Helper for processing responses of functions such as enterICC* and changeICC*. 1.3278 + */ 1.3279 + _processEnterAndChangeICCResponses: function(length, options) { 1.3280 + options.success = (options.rilRequestError === 0); 1.3281 + if (!options.success) { 1.3282 + options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError]; 1.3283 + } 1.3284 + options.retryCount = length ? this.context.Buf.readInt32List()[0] : -1; 1.3285 + if (options.rilMessageType != "sendMMI") { 1.3286 + this.sendChromeMessage(options); 1.3287 + return; 1.3288 + } 1.3289 + 1.3290 + let mmiServiceCode = options.mmiServiceCode; 1.3291 + 1.3292 + if (options.success) { 1.3293 + switch (mmiServiceCode) { 1.3294 + case MMI_KS_SC_PIN: 1.3295 + options.statusMessage = MMI_SM_KS_PIN_CHANGED; 1.3296 + break; 1.3297 + case MMI_KS_SC_PIN2: 1.3298 + options.statusMessage = MMI_SM_KS_PIN2_CHANGED; 1.3299 + break; 1.3300 + case MMI_KS_SC_PUK: 1.3301 + options.statusMessage = MMI_SM_KS_PIN_UNBLOCKED; 1.3302 + break; 1.3303 + case MMI_KS_SC_PUK2: 1.3304 + options.statusMessage = MMI_SM_KS_PIN2_UNBLOCKED; 1.3305 + break; 1.3306 + } 1.3307 + } else { 1.3308 + if (options.retryCount <= 0) { 1.3309 + if (mmiServiceCode === MMI_KS_SC_PUK) { 1.3310 + options.errorMsg = MMI_ERROR_KS_SIM_BLOCKED; 1.3311 + } else if (mmiServiceCode === MMI_KS_SC_PIN) { 1.3312 + options.errorMsg = MMI_ERROR_KS_NEEDS_PUK; 1.3313 + } 1.3314 + } else { 1.3315 + if (mmiServiceCode === MMI_KS_SC_PIN || 1.3316 + mmiServiceCode === MMI_KS_SC_PIN2) { 1.3317 + options.errorMsg = MMI_ERROR_KS_BAD_PIN; 1.3318 + } else if (mmiServiceCode === MMI_KS_SC_PUK || 1.3319 + mmiServiceCode === MMI_KS_SC_PUK2) { 1.3320 + options.errorMsg = MMI_ERROR_KS_BAD_PUK; 1.3321 + } 1.3322 + if (options.retryCount !== undefined) { 1.3323 + options.additionalInformation = options.retryCount; 1.3324 + } 1.3325 + } 1.3326 + } 1.3327 + 1.3328 + this.sendChromeMessage(options); 1.3329 + }, 1.3330 + 1.3331 + // We combine all of the NETWORK_INFO_MESSAGE_TYPES into one "networkinfochange" 1.3332 + // message to the RadioInterfaceLayer, so we can avoid sending multiple 1.3333 + // VoiceInfoChanged events for both operator / voice_data_registration 1.3334 + // 1.3335 + // State management here is a little tricky. We need to know both: 1.3336 + // 1. Whether or not a response was received for each of the 1.3337 + // NETWORK_INFO_MESSAGE_TYPES 1.3338 + // 2. The outbound message that corresponds with that response -- but this 1.3339 + // only happens when internal state changes (i.e. it isn't guaranteed) 1.3340 + // 1.3341 + // To collect this state, each message response function first calls 1.3342 + // _receivedNetworkInfo, to mark the response as received. When the 1.3343 + // final response is received, a call to _sendPendingNetworkInfo is placed 1.3344 + // on the next tick of the worker thread. 1.3345 + // 1.3346 + // Since the original call to _receivedNetworkInfo happens at the top 1.3347 + // of the response handler, this gives the final handler a chance to 1.3348 + // queue up it's "changed" message by calling _sendNetworkInfoMessage if/when 1.3349 + // the internal state has actually changed. 1.3350 + _sendNetworkInfoMessage: function(type, message) { 1.3351 + if (!this._processingNetworkInfo) { 1.3352 + // We only combine these messages in the case of the combined request 1.3353 + // in requestNetworkInfo() 1.3354 + this.sendChromeMessage(message); 1.3355 + return; 1.3356 + } 1.3357 + 1.3358 + if (DEBUG) { 1.3359 + this.context.debug("Queuing " + type + " network info message: " + 1.3360 + JSON.stringify(message)); 1.3361 + } 1.3362 + this._pendingNetworkInfo[type] = message; 1.3363 + }, 1.3364 + 1.3365 + _receivedNetworkInfo: function(type) { 1.3366 + if (DEBUG) this.context.debug("Received " + type + " network info."); 1.3367 + if (!this._processingNetworkInfo) { 1.3368 + return; 1.3369 + } 1.3370 + 1.3371 + let pending = this._pendingNetworkInfo; 1.3372 + 1.3373 + // We still need to track states for events that aren't fired. 1.3374 + if (!(type in pending)) { 1.3375 + pending[type] = this.pendingNetworkType; 1.3376 + } 1.3377 + 1.3378 + // Pending network info is ready to be sent when no more messages 1.3379 + // are waiting for responses, but the combined payload hasn't been sent. 1.3380 + for (let i = 0; i < NETWORK_INFO_MESSAGE_TYPES.length; i++) { 1.3381 + let msgType = NETWORK_INFO_MESSAGE_TYPES[i]; 1.3382 + if (!(msgType in pending)) { 1.3383 + if (DEBUG) { 1.3384 + this.context.debug("Still missing some more network info, not " + 1.3385 + "notifying main thread."); 1.3386 + } 1.3387 + return; 1.3388 + } 1.3389 + } 1.3390 + 1.3391 + // Do a pass to clean up the processed messages that didn't create 1.3392 + // a response message, so we don't have unused keys in the outbound 1.3393 + // networkinfochanged message. 1.3394 + for (let key in pending) { 1.3395 + if (pending[key] == this.pendingNetworkType) { 1.3396 + delete pending[key]; 1.3397 + } 1.3398 + } 1.3399 + 1.3400 + if (DEBUG) { 1.3401 + this.context.debug("All pending network info has been received: " + 1.3402 + JSON.stringify(pending)); 1.3403 + } 1.3404 + 1.3405 + // Send the message on the next tick of the worker's loop, so we give the 1.3406 + // last message a chance to call _sendNetworkInfoMessage first. 1.3407 + setTimeout(this._sendPendingNetworkInfo.bind(this), 0); 1.3408 + }, 1.3409 + 1.3410 + _sendPendingNetworkInfo: function() { 1.3411 + this.sendChromeMessage(this._pendingNetworkInfo); 1.3412 + 1.3413 + this._processingNetworkInfo = false; 1.3414 + for (let i = 0; i < NETWORK_INFO_MESSAGE_TYPES.length; i++) { 1.3415 + delete this._pendingNetworkInfo[NETWORK_INFO_MESSAGE_TYPES[i]]; 1.3416 + } 1.3417 + 1.3418 + if (this._needRepollNetworkInfo) { 1.3419 + this._needRepollNetworkInfo = false; 1.3420 + this.requestNetworkInfo(); 1.3421 + } 1.3422 + }, 1.3423 + 1.3424 + /** 1.3425 + * Normalize the signal strength in dBm to the signal level from 0 to 100. 1.3426 + * 1.3427 + * @param signal 1.3428 + * The signal strength in dBm to normalize. 1.3429 + * @param min 1.3430 + * The signal strength in dBm maps to level 0. 1.3431 + * @param max 1.3432 + * The signal strength in dBm maps to level 100. 1.3433 + * 1.3434 + * @return level 1.3435 + * The signal level from 0 to 100. 1.3436 + */ 1.3437 + _processSignalLevel: function(signal, min, max) { 1.3438 + if (signal <= min) { 1.3439 + return 0; 1.3440 + } 1.3441 + 1.3442 + if (signal >= max) { 1.3443 + return 100; 1.3444 + } 1.3445 + 1.3446 + return Math.floor((signal - min) * 100 / (max - min)); 1.3447 + }, 1.3448 + 1.3449 + /** 1.3450 + * Process LTE signal strength to the signal info object. 1.3451 + * 1.3452 + * @param signal 1.3453 + * The signal object reported from RIL/modem. 1.3454 + * 1.3455 + * @return The object of signal strength info. 1.3456 + * Or null if invalid signal input. 1.3457 + */ 1.3458 + _processLteSignal: function(signal) { 1.3459 + // Valid values are 0-63 as defined in TS 27.007 clause 8.69. 1.3460 + if (signal.lteSignalStrength === undefined || 1.3461 + signal.lteSignalStrength < 0 || 1.3462 + signal.lteSignalStrength > 63) { 1.3463 + return null; 1.3464 + } 1.3465 + 1.3466 + let info = { 1.3467 + voice: { 1.3468 + signalStrength: null, 1.3469 + relSignalStrength: null 1.3470 + }, 1.3471 + data: { 1.3472 + signalStrength: null, 1.3473 + relSignalStrength: null 1.3474 + } 1.3475 + }; 1.3476 + 1.3477 + // TODO: Bug 982013: reconsider signalStrength/relSignalStrength APIs for 1.3478 + // GSM/CDMA/LTE, and take rsrp/rssnr into account for LTE case then. 1.3479 + let signalStrength = -111 + signal.lteSignalStrength; 1.3480 + info.voice.signalStrength = info.data.signalStrength = signalStrength; 1.3481 + // 0 and 12 are referred to AOSP's implementation. These values are not 1.3482 + // constants and can be customized based on different requirements. 1.3483 + let signalLevel = this._processSignalLevel(signal.lteSignalStrength, 0, 12); 1.3484 + info.voice.relSignalStrength = info.data.relSignalStrength = signalLevel; 1.3485 + 1.3486 + return info; 1.3487 + }, 1.3488 + 1.3489 + _processSignalStrength: function(signal) { 1.3490 + let info = { 1.3491 + voice: { 1.3492 + signalStrength: null, 1.3493 + relSignalStrength: null 1.3494 + }, 1.3495 + data: { 1.3496 + signalStrength: null, 1.3497 + relSignalStrength: null 1.3498 + } 1.3499 + }; 1.3500 + 1.3501 + // During startup, |radioTech| is not yet defined, so we need to 1.3502 + // check it separately. 1.3503 + if (("radioTech" in this.voiceRegistrationState) && 1.3504 + !this._isGsmTechGroup(this.voiceRegistrationState.radioTech)) { 1.3505 + // CDMA RSSI. 1.3506 + // Valid values are positive integers. This value is the actual RSSI value 1.3507 + // multiplied by -1. Example: If the actual RSSI is -75, then this 1.3508 + // response value will be 75. 1.3509 + if (signal.cdmaDBM && signal.cdmaDBM > 0) { 1.3510 + let signalStrength = -1 * signal.cdmaDBM; 1.3511 + info.voice.signalStrength = signalStrength; 1.3512 + 1.3513 + // -105 and -70 are referred to AOSP's implementation. These values are 1.3514 + // not constants and can be customized based on different requirement. 1.3515 + let signalLevel = this._processSignalLevel(signalStrength, -105, -70); 1.3516 + info.voice.relSignalStrength = signalLevel; 1.3517 + } 1.3518 + 1.3519 + // EVDO RSSI. 1.3520 + // Valid values are positive integers. This value is the actual RSSI value 1.3521 + // multiplied by -1. Example: If the actual RSSI is -75, then this 1.3522 + // response value will be 75. 1.3523 + if (signal.evdoDBM && signal.evdoDBM > 0) { 1.3524 + let signalStrength = -1 * signal.evdoDBM; 1.3525 + info.data.signalStrength = signalStrength; 1.3526 + 1.3527 + // -105 and -70 are referred to AOSP's implementation. These values are 1.3528 + // not constants and can be customized based on different requirement. 1.3529 + let signalLevel = this._processSignalLevel(signalStrength, -105, -70); 1.3530 + info.data.relSignalStrength = signalLevel; 1.3531 + } 1.3532 + } else { 1.3533 + // Check LTE level first, and check GSM/UMTS level next if LTE one is not 1.3534 + // valid. 1.3535 + let lteInfo = this._processLteSignal(signal); 1.3536 + if (lteInfo) { 1.3537 + info = lteInfo; 1.3538 + } else { 1.3539 + // GSM signal strength. 1.3540 + // Valid values are 0-31 as defined in TS 27.007 8.5. 1.3541 + // 0 : -113 dBm or less 1.3542 + // 1 : -111 dBm 1.3543 + // 2...30: -109...-53 dBm 1.3544 + // 31 : -51 dBm 1.3545 + if (signal.gsmSignalStrength && 1.3546 + signal.gsmSignalStrength >= 0 && 1.3547 + signal.gsmSignalStrength <= 31) { 1.3548 + let signalStrength = -113 + 2 * signal.gsmSignalStrength; 1.3549 + info.voice.signalStrength = info.data.signalStrength = signalStrength; 1.3550 + 1.3551 + // -115 and -85 are referred to AOSP's implementation. These values are 1.3552 + // not constants and can be customized based on different requirement. 1.3553 + let signalLevel = this._processSignalLevel(signalStrength, -110, -85); 1.3554 + info.voice.relSignalStrength = info.data.relSignalStrength = signalLevel; 1.3555 + } 1.3556 + } 1.3557 + } 1.3558 + 1.3559 + info.rilMessageType = "signalstrengthchange"; 1.3560 + this._sendNetworkInfoMessage(NETWORK_INFO_SIGNAL, info); 1.3561 + 1.3562 + if (this.cachedDialRequest && info.voice.signalStrength) { 1.3563 + // Radio is ready for making the cached emergency call. 1.3564 + this.cachedDialRequest.callback(); 1.3565 + this.cachedDialRequest = null; 1.3566 + } 1.3567 + }, 1.3568 + 1.3569 + /** 1.3570 + * Process the network registration flags. 1.3571 + * 1.3572 + * @return true if the state changed, false otherwise. 1.3573 + */ 1.3574 + _processCREG: function(curState, newState) { 1.3575 + let changed = false; 1.3576 + 1.3577 + let regState = this.parseInt(newState[0], NETWORK_CREG_STATE_UNKNOWN); 1.3578 + if (curState.regState === undefined || curState.regState !== regState) { 1.3579 + changed = true; 1.3580 + curState.regState = regState; 1.3581 + 1.3582 + curState.state = NETWORK_CREG_TO_GECKO_MOBILE_CONNECTION_STATE[regState]; 1.3583 + curState.connected = regState == NETWORK_CREG_STATE_REGISTERED_HOME || 1.3584 + regState == NETWORK_CREG_STATE_REGISTERED_ROAMING; 1.3585 + curState.roaming = regState == NETWORK_CREG_STATE_REGISTERED_ROAMING; 1.3586 + curState.emergencyCallsOnly = !curState.connected; 1.3587 + } 1.3588 + 1.3589 + if (!curState.cell) { 1.3590 + curState.cell = {}; 1.3591 + } 1.3592 + 1.3593 + // From TS 23.003, 0000 and 0xfffe are indicated that no valid LAI exists 1.3594 + // in MS. So we still need to report the '0000' as well. 1.3595 + let lac = this.parseInt(newState[1], -1, 16); 1.3596 + if (curState.cell.gsmLocationAreaCode === undefined || 1.3597 + curState.cell.gsmLocationAreaCode !== lac) { 1.3598 + curState.cell.gsmLocationAreaCode = lac; 1.3599 + changed = true; 1.3600 + } 1.3601 + 1.3602 + let cid = this.parseInt(newState[2], -1, 16); 1.3603 + if (curState.cell.gsmCellId === undefined || 1.3604 + curState.cell.gsmCellId !== cid) { 1.3605 + curState.cell.gsmCellId = cid; 1.3606 + changed = true; 1.3607 + } 1.3608 + 1.3609 + let radioTech = (newState[3] === undefined ? 1.3610 + NETWORK_CREG_TECH_UNKNOWN : 1.3611 + this.parseInt(newState[3], NETWORK_CREG_TECH_UNKNOWN)); 1.3612 + if (curState.radioTech === undefined || curState.radioTech !== radioTech) { 1.3613 + changed = true; 1.3614 + curState.radioTech = radioTech; 1.3615 + curState.type = GECKO_RADIO_TECH[radioTech] || null; 1.3616 + } 1.3617 + return changed; 1.3618 + }, 1.3619 + 1.3620 + _processVoiceRegistrationState: function(state) { 1.3621 + let rs = this.voiceRegistrationState; 1.3622 + let stateChanged = this._processCREG(rs, state); 1.3623 + if (stateChanged && rs.connected) { 1.3624 + this.getSmscAddress(); 1.3625 + } 1.3626 + 1.3627 + let cell = rs.cell; 1.3628 + if (this._isCdma) { 1.3629 + // Some variables below are not used. Comment them instead of removing to 1.3630 + // keep the information about state[x]. 1.3631 + let cdmaBaseStationId = this.parseInt(state[4], -1); 1.3632 + let cdmaBaseStationLatitude = this.parseInt(state[5], -2147483648); 1.3633 + let cdmaBaseStationLongitude = this.parseInt(state[6], -2147483648); 1.3634 + // let cssIndicator = this.parseInt(state[7]); 1.3635 + let cdmaSystemId = this.parseInt(state[8], -1); 1.3636 + let cdmaNetworkId = this.parseInt(state[9], -1); 1.3637 + // let roamingIndicator = this.parseInt(state[10]); 1.3638 + // let systemIsInPRL = this.parseInt(state[11]); 1.3639 + // let defaultRoamingIndicator = this.parseInt(state[12]); 1.3640 + // let reasonForDenial = this.parseInt(state[13]); 1.3641 + 1.3642 + if (cell.cdmaBaseStationId !== cdmaBaseStationId || 1.3643 + cell.cdmaBaseStationLatitude !== cdmaBaseStationLatitude || 1.3644 + cell.cdmaBaseStationLongitude !== cdmaBaseStationLongitude || 1.3645 + cell.cdmaSystemId !== cdmaSystemId || 1.3646 + cell.cdmaNetworkId !== cdmaNetworkId) { 1.3647 + stateChanged = true; 1.3648 + cell.cdmaBaseStationId = cdmaBaseStationId; 1.3649 + cell.cdmaBaseStationLatitude = cdmaBaseStationLatitude; 1.3650 + cell.cdmaBaseStationLongitude = cdmaBaseStationLongitude; 1.3651 + cell.cdmaSystemId = cdmaSystemId; 1.3652 + cell.cdmaNetworkId = cdmaNetworkId; 1.3653 + } 1.3654 + } 1.3655 + 1.3656 + if (stateChanged) { 1.3657 + rs.rilMessageType = "voiceregistrationstatechange"; 1.3658 + this._sendNetworkInfoMessage(NETWORK_INFO_VOICE_REGISTRATION_STATE, rs); 1.3659 + } 1.3660 + }, 1.3661 + 1.3662 + _processDataRegistrationState: function(state) { 1.3663 + let rs = this.dataRegistrationState; 1.3664 + let stateChanged = this._processCREG(rs, state); 1.3665 + if (stateChanged) { 1.3666 + rs.rilMessageType = "dataregistrationstatechange"; 1.3667 + this._sendNetworkInfoMessage(NETWORK_INFO_DATA_REGISTRATION_STATE, rs); 1.3668 + } 1.3669 + }, 1.3670 + 1.3671 + _processOperator: function(operatorData) { 1.3672 + if (operatorData.length < 3) { 1.3673 + if (DEBUG) { 1.3674 + this.context.debug("Expected at least 3 strings for operator."); 1.3675 + } 1.3676 + } 1.3677 + 1.3678 + if (!this.operator) { 1.3679 + this.operator = { 1.3680 + rilMessageType: "operatorchange", 1.3681 + longName: null, 1.3682 + shortName: null 1.3683 + }; 1.3684 + } 1.3685 + 1.3686 + let [longName, shortName, networkTuple] = operatorData; 1.3687 + let thisTuple = (this.operator.mcc || "") + (this.operator.mnc || ""); 1.3688 + 1.3689 + if (this.operator.longName !== longName || 1.3690 + this.operator.shortName !== shortName || 1.3691 + thisTuple !== networkTuple) { 1.3692 + 1.3693 + this.operator.mcc = null; 1.3694 + this.operator.mnc = null; 1.3695 + 1.3696 + if (networkTuple) { 1.3697 + try { 1.3698 + this._processNetworkTuple(networkTuple, this.operator); 1.3699 + } catch (e) { 1.3700 + if (DEBUG) this.context.debug("Error processing operator tuple: " + e); 1.3701 + } 1.3702 + } else { 1.3703 + // According to ril.h, the operator fields will be NULL when the operator 1.3704 + // is not currently registered. We can avoid trying to parse the numeric 1.3705 + // tuple in that case. 1.3706 + if (DEBUG) { 1.3707 + this.context.debug("Operator is currently unregistered"); 1.3708 + } 1.3709 + } 1.3710 + 1.3711 + let ICCUtilsHelper = this.context.ICCUtilsHelper; 1.3712 + let networkName; 1.3713 + // We won't get network name using PNN and OPL if voice registration isn't ready 1.3714 + if (this.voiceRegistrationState.cell && 1.3715 + this.voiceRegistrationState.cell.gsmLocationAreaCode != -1) { 1.3716 + networkName = ICCUtilsHelper.getNetworkNameFromICC( 1.3717 + this.operator.mcc, 1.3718 + this.operator.mnc, 1.3719 + this.voiceRegistrationState.cell.gsmLocationAreaCode); 1.3720 + } 1.3721 + 1.3722 + if (networkName) { 1.3723 + if (DEBUG) { 1.3724 + this.context.debug("Operator names will be overriden: " + 1.3725 + "longName = " + networkName.fullName + ", " + 1.3726 + "shortName = " + networkName.shortName); 1.3727 + } 1.3728 + 1.3729 + this.operator.longName = networkName.fullName; 1.3730 + this.operator.shortName = networkName.shortName; 1.3731 + } else { 1.3732 + this.operator.longName = longName; 1.3733 + this.operator.shortName = shortName; 1.3734 + } 1.3735 + 1.3736 + if (ICCUtilsHelper.updateDisplayCondition()) { 1.3737 + ICCUtilsHelper.handleICCInfoChange(); 1.3738 + } 1.3739 + this._sendNetworkInfoMessage(NETWORK_INFO_OPERATOR, this.operator); 1.3740 + } 1.3741 + }, 1.3742 + 1.3743 + /** 1.3744 + * Helpers for processing call state and handle the active call. 1.3745 + */ 1.3746 + _processCalls: function(newCalls) { 1.3747 + let conferenceChanged = false; 1.3748 + let clearConferenceRequest = false; 1.3749 + let pendingOutgoingCall = null; 1.3750 + 1.3751 + // Go through the calls we currently have on file and see if any of them 1.3752 + // changed state. Remove them from the newCalls map as we deal with them 1.3753 + // so that only new calls remain in the map after we're done. 1.3754 + for each (let currentCall in this.currentCalls) { 1.3755 + if (currentCall.callIndex == OUTGOING_PLACEHOLDER_CALL_INDEX) { 1.3756 + pendingOutgoingCall = currentCall; 1.3757 + continue; 1.3758 + } 1.3759 + 1.3760 + let newCall; 1.3761 + if (newCalls) { 1.3762 + newCall = newCalls[currentCall.callIndex]; 1.3763 + delete newCalls[currentCall.callIndex]; 1.3764 + } 1.3765 + 1.3766 + // Call is no longer reported by the radio. Remove from our map and send 1.3767 + // disconnected state change. 1.3768 + if (!newCall) { 1.3769 + if (this.currentConference.participants[currentCall.callIndex]) { 1.3770 + conferenceChanged = true; 1.3771 + } 1.3772 + this._removeVoiceCall(currentCall, 1.3773 + currentCall.hangUpLocal ? 1.3774 + GECKO_CALL_ERROR_NORMAL_CALL_CLEARING : null); 1.3775 + continue; 1.3776 + } 1.3777 + 1.3778 + // Call is still valid. 1.3779 + if (newCall.state == currentCall.state && 1.3780 + newCall.isMpty == currentCall.isMpty) { 1.3781 + continue; 1.3782 + } 1.3783 + 1.3784 + // State has changed. 1.3785 + if (newCall.state == CALL_STATE_INCOMING && 1.3786 + currentCall.state == CALL_STATE_WAITING) { 1.3787 + // Update the call internally but we don't notify chrome since these two 1.3788 + // states are viewed as the same one there. 1.3789 + currentCall.state = newCall.state; 1.3790 + continue; 1.3791 + } 1.3792 + 1.3793 + if (!currentCall.started && newCall.state == CALL_STATE_ACTIVE) { 1.3794 + currentCall.started = new Date().getTime(); 1.3795 + } 1.3796 + 1.3797 + if (currentCall.isMpty == newCall.isMpty && 1.3798 + newCall.state != currentCall.state) { 1.3799 + currentCall.state = newCall.state; 1.3800 + if (currentCall.isConference) { 1.3801 + conferenceChanged = true; 1.3802 + } 1.3803 + this._handleChangedCallState(currentCall); 1.3804 + continue; 1.3805 + } 1.3806 + 1.3807 + // '.isMpty' becomes false when the conference call is put on hold. 1.3808 + // We need to introduce additional 'isConference' to correctly record the 1.3809 + // real conference status 1.3810 + 1.3811 + // Update a possible conference participant when .isMpty changes. 1.3812 + if (!currentCall.isMpty && newCall.isMpty) { 1.3813 + if (this._hasConferenceRequest) { 1.3814 + conferenceChanged = true; 1.3815 + clearConferenceRequest = true; 1.3816 + currentCall.state = newCall.state; 1.3817 + currentCall.isMpty = newCall.isMpty; 1.3818 + currentCall.isConference = true; 1.3819 + this.currentConference.participants[currentCall.callIndex] = currentCall; 1.3820 + this._handleChangedCallState(currentCall); 1.3821 + } else if (currentCall.isConference) { 1.3822 + // The case happens when resuming a held conference call. 1.3823 + conferenceChanged = true; 1.3824 + currentCall.state = newCall.state; 1.3825 + currentCall.isMpty = newCall.isMpty; 1.3826 + this.currentConference.participants[currentCall.callIndex] = currentCall; 1.3827 + this._handleChangedCallState(currentCall); 1.3828 + } else { 1.3829 + // Weird. This sometimes happens when we switch two calls, but it is 1.3830 + // not a conference call. 1.3831 + currentCall.state = newCall.state; 1.3832 + this._handleChangedCallState(currentCall); 1.3833 + } 1.3834 + } else if (currentCall.isMpty && !newCall.isMpty) { 1.3835 + if (!this.currentConference.participants[newCall.callIndex]) { 1.3836 + continue; 1.3837 + } 1.3838 + 1.3839 + // '.isMpty' of a conference participant is set to false by rild when 1.3840 + // the conference call is put on hold. We don't actually know if the call 1.3841 + // still attends the conference until updating all calls finishes. We 1.3842 + // cache it for further determination. 1.3843 + if (newCall.state != CALL_STATE_HOLDING) { 1.3844 + delete this.currentConference.participants[newCall.callIndex]; 1.3845 + currentCall.state = newCall.state; 1.3846 + currentCall.isMpty = newCall.isMpty; 1.3847 + currentCall.isConference = false; 1.3848 + conferenceChanged = true; 1.3849 + this._handleChangedCallState(currentCall); 1.3850 + continue; 1.3851 + } 1.3852 + 1.3853 + if (!this.currentConference.cache) { 1.3854 + this.currentConference.cache = {}; 1.3855 + } 1.3856 + this.currentConference.cache[currentCall.callIndex] = newCall; 1.3857 + currentCall.state = newCall.state; 1.3858 + currentCall.isMpty = newCall.isMpty; 1.3859 + conferenceChanged = true; 1.3860 + } 1.3861 + } 1.3862 + 1.3863 + if (pendingOutgoingCall) { 1.3864 + if (!newCalls || Object.keys(newCalls).length === 0) { 1.3865 + // We don't get a successful call for pendingOutgoingCall. 1.3866 + this._removePendingOutgoingCall(GECKO_CALL_ERROR_UNSPECIFIED); 1.3867 + } else { 1.3868 + // Only remove it from currentCalls map. Will use the new call to 1.3869 + // replace the placeholder. 1.3870 + delete this.currentCalls[OUTGOING_PLACEHOLDER_CALL_INDEX]; 1.3871 + } 1.3872 + } 1.3873 + 1.3874 + // Go through any remaining calls that are new to us. 1.3875 + for each (let newCall in newCalls) { 1.3876 + if (newCall.isVoice) { 1.3877 + if (newCall.isMpty) { 1.3878 + conferenceChanged = true; 1.3879 + } 1.3880 + if (!pendingOutgoingCall && 1.3881 + (newCall.state === CALL_STATE_DIALING || 1.3882 + newCall.state === CALL_STATE_ALERTING)) { 1.3883 + // Receive a new outgoing call which is already hung up by user. 1.3884 + if (DEBUG) this.context.debug("Pending outgoing call is hung up by user."); 1.3885 + this.sendHangUpRequest(newCall.callIndex); 1.3886 + } else { 1.3887 + this._addNewVoiceCall(newCall); 1.3888 + } 1.3889 + } 1.3890 + } 1.3891 + 1.3892 + if (clearConferenceRequest) { 1.3893 + this._hasConferenceRequest = false; 1.3894 + } 1.3895 + if (conferenceChanged) { 1.3896 + this._ensureConference(); 1.3897 + } 1.3898 + }, 1.3899 + 1.3900 + _addNewVoiceCall: function(newCall) { 1.3901 + // Format international numbers appropriately. 1.3902 + if (newCall.number && newCall.toa == TOA_INTERNATIONAL && 1.3903 + newCall.number[0] != "+") { 1.3904 + newCall.number = "+" + newCall.number; 1.3905 + } 1.3906 + 1.3907 + if (newCall.state == CALL_STATE_INCOMING) { 1.3908 + newCall.isOutgoing = false; 1.3909 + } else if (newCall.state == CALL_STATE_DIALING) { 1.3910 + newCall.isOutgoing = true; 1.3911 + } 1.3912 + 1.3913 + // Set flag for outgoing emergency call. 1.3914 + newCall.isEmergency = newCall.isOutgoing && 1.3915 + this._isEmergencyNumber(newCall.number); 1.3916 + 1.3917 + // Set flag for conference. 1.3918 + newCall.isConference = newCall.isMpty ? true : false; 1.3919 + 1.3920 + // Add to our map. 1.3921 + if (newCall.isMpty) { 1.3922 + this.currentConference.participants[newCall.callIndex] = newCall; 1.3923 + } 1.3924 + this._handleChangedCallState(newCall); 1.3925 + this.currentCalls[newCall.callIndex] = newCall; 1.3926 + }, 1.3927 + 1.3928 + _removeVoiceCall: function(removedCall, failCause) { 1.3929 + if (this.currentConference.participants[removedCall.callIndex]) { 1.3930 + removedCall.isConference = false; 1.3931 + delete this.currentConference.participants[removedCall.callIndex]; 1.3932 + delete this.currentCalls[removedCall.callIndex]; 1.3933 + // We don't query the fail cause here as it triggers another asynchrouns 1.3934 + // request that leads to a problem of updating all conferece participants 1.3935 + // in one task. 1.3936 + this._handleDisconnectedCall(removedCall); 1.3937 + } else { 1.3938 + delete this.currentCalls[removedCall.callIndex]; 1.3939 + if (failCause) { 1.3940 + removedCall.failCause = failCause; 1.3941 + this._handleDisconnectedCall(removedCall); 1.3942 + } else { 1.3943 + this.getFailCauseCode((function(call, failCause) { 1.3944 + call.failCause = failCause; 1.3945 + this._handleDisconnectedCall(call); 1.3946 + }).bind(this, removedCall)); 1.3947 + } 1.3948 + } 1.3949 + }, 1.3950 + 1.3951 + _createPendingOutgoingCall: function(options) { 1.3952 + if (DEBUG) this.context.debug("Create a pending outgoing call."); 1.3953 + this._addNewVoiceCall({ 1.3954 + number: options.number, 1.3955 + state: CALL_STATE_DIALING, 1.3956 + callIndex: OUTGOING_PLACEHOLDER_CALL_INDEX 1.3957 + }); 1.3958 + }, 1.3959 + 1.3960 + _removePendingOutgoingCall: function(failCause) { 1.3961 + let call = this.currentCalls[OUTGOING_PLACEHOLDER_CALL_INDEX]; 1.3962 + if (!call) { 1.3963 + return; 1.3964 + } 1.3965 + 1.3966 + if (DEBUG) this.context.debug("Remove pending outgoing call."); 1.3967 + this._removeVoiceCall(pendingOutgoingCall, failCause); 1.3968 + }, 1.3969 + 1.3970 + _ensureConference: function() { 1.3971 + let oldState = this.currentConference.state; 1.3972 + let remaining = Object.keys(this.currentConference.participants); 1.3973 + 1.3974 + if (remaining.length == 1) { 1.3975 + // Remove that if only does one remain in a conference call. 1.3976 + let call = this.currentCalls[remaining[0]]; 1.3977 + call.isConference = false; 1.3978 + this._handleChangedCallState(call); 1.3979 + delete this.currentConference.participants[call.callIndex]; 1.3980 + } else if (remaining.length > 1) { 1.3981 + for each (let call in this.currentConference.cache) { 1.3982 + call.isConference = true; 1.3983 + this.currentConference.participants[call.callIndex] = call; 1.3984 + this.currentCalls[call.callIndex] = call; 1.3985 + this._handleChangedCallState(call); 1.3986 + } 1.3987 + } 1.3988 + delete this.currentConference.cache; 1.3989 + 1.3990 + // Update the conference call's state. 1.3991 + let state = CALL_STATE_UNKNOWN; 1.3992 + for each (let call in this.currentConference.participants) { 1.3993 + if (state != CALL_STATE_UNKNOWN && state != call.state) { 1.3994 + // Each participant should have the same state, otherwise something 1.3995 + // wrong happens. 1.3996 + state = CALL_STATE_UNKNOWN; 1.3997 + break; 1.3998 + } 1.3999 + state = call.state; 1.4000 + } 1.4001 + if (oldState != state) { 1.4002 + this.currentConference.state = state; 1.4003 + let message = {rilMessageType: "conferenceCallStateChanged", 1.4004 + state: state}; 1.4005 + this.sendChromeMessage(message); 1.4006 + } 1.4007 + }, 1.4008 + 1.4009 + _handleChangedCallState: function(changedCall) { 1.4010 + let message = {rilMessageType: "callStateChange", 1.4011 + call: changedCall}; 1.4012 + this.sendChromeMessage(message); 1.4013 + }, 1.4014 + 1.4015 + _handleDisconnectedCall: function(disconnectedCall) { 1.4016 + let message = {rilMessageType: "callDisconnected", 1.4017 + call: disconnectedCall}; 1.4018 + this.sendChromeMessage(message); 1.4019 + }, 1.4020 + 1.4021 + _sendDataCallError: function(message, errorCode) { 1.4022 + // Should not include token for unsolicited response. 1.4023 + delete message.rilMessageToken; 1.4024 + message.rilMessageType = "datacallerror"; 1.4025 + if (errorCode == ERROR_GENERIC_FAILURE) { 1.4026 + message.errorMsg = RIL_ERROR_TO_GECKO_ERROR[errorCode]; 1.4027 + } else { 1.4028 + message.errorMsg = RIL_DATACALL_FAILCAUSE_TO_GECKO_DATACALL_ERROR[errorCode]; 1.4029 + } 1.4030 + this.sendChromeMessage(message); 1.4031 + }, 1.4032 + 1.4033 + /** 1.4034 + * @return "deactivate" if <ifname> changes or one of the currentDataCall 1.4035 + * addresses is missing in updatedDataCall, or "identical" if no 1.4036 + * changes found, or "changed" otherwise. 1.4037 + */ 1.4038 + _compareDataCallLink: function(updatedDataCall, currentDataCall) { 1.4039 + // If network interface is changed, report as "deactivate". 1.4040 + if (updatedDataCall.ifname != currentDataCall.ifname) { 1.4041 + return "deactivate"; 1.4042 + } 1.4043 + 1.4044 + // If any existing address is missing, report as "deactivate". 1.4045 + for (let i = 0; i < currentDataCall.addresses.length; i++) { 1.4046 + let address = currentDataCall.addresses[i]; 1.4047 + if (updatedDataCall.addresses.indexOf(address) < 0) { 1.4048 + return "deactivate"; 1.4049 + } 1.4050 + } 1.4051 + 1.4052 + if (currentDataCall.addresses.length != updatedDataCall.addresses.length) { 1.4053 + // Since now all |currentDataCall.addresses| are found in 1.4054 + // |updatedDataCall.addresses|, this means one or more new addresses are 1.4055 + // reported. 1.4056 + return "changed"; 1.4057 + } 1.4058 + 1.4059 + let fields = ["gateways", "dnses"]; 1.4060 + for (let i = 0; i < fields.length; i++) { 1.4061 + // Compare <datacall>.<field>. 1.4062 + let field = fields[i]; 1.4063 + let lhs = updatedDataCall[field], rhs = currentDataCall[field]; 1.4064 + if (lhs.length != rhs.length) { 1.4065 + return "changed"; 1.4066 + } 1.4067 + for (let i = 0; i < lhs.length; i++) { 1.4068 + if (lhs[i] != rhs[i]) { 1.4069 + return "changed"; 1.4070 + } 1.4071 + } 1.4072 + } 1.4073 + 1.4074 + return "identical"; 1.4075 + }, 1.4076 + 1.4077 + _processDataCallList: function(datacalls, newDataCallOptions) { 1.4078 + // Check for possible PDP errors: We check earlier because the datacall 1.4079 + // can be removed if is the same as the current one. 1.4080 + for each (let newDataCall in datacalls) { 1.4081 + if (newDataCall.status != DATACALL_FAIL_NONE) { 1.4082 + if (newDataCallOptions) { 1.4083 + newDataCall.apn = newDataCallOptions.apn; 1.4084 + } 1.4085 + this._sendDataCallError(newDataCall, newDataCall.status); 1.4086 + } 1.4087 + } 1.4088 + 1.4089 + for each (let currentDataCall in this.currentDataCalls) { 1.4090 + let updatedDataCall; 1.4091 + if (datacalls) { 1.4092 + updatedDataCall = datacalls[currentDataCall.cid]; 1.4093 + delete datacalls[currentDataCall.cid]; 1.4094 + } 1.4095 + 1.4096 + if (!updatedDataCall) { 1.4097 + // If datacalls list is coming from REQUEST_SETUP_DATA_CALL response, 1.4098 + // we do not change state for any currentDataCalls not in datacalls list. 1.4099 + if (!newDataCallOptions) { 1.4100 + delete this.currentDataCalls[currentDataCall.cid]; 1.4101 + currentDataCall.state = GECKO_NETWORK_STATE_DISCONNECTED; 1.4102 + currentDataCall.rilMessageType = "datacallstatechange"; 1.4103 + this.sendChromeMessage(currentDataCall); 1.4104 + } 1.4105 + continue; 1.4106 + } 1.4107 + 1.4108 + if (updatedDataCall && !updatedDataCall.ifname) { 1.4109 + delete this.currentDataCalls[currentDataCall.cid]; 1.4110 + currentDataCall.state = GECKO_NETWORK_STATE_UNKNOWN; 1.4111 + currentDataCall.rilMessageType = "datacallstatechange"; 1.4112 + this.sendChromeMessage(currentDataCall); 1.4113 + continue; 1.4114 + } 1.4115 + 1.4116 + this._setDataCallGeckoState(updatedDataCall); 1.4117 + if (updatedDataCall.state != currentDataCall.state) { 1.4118 + if (updatedDataCall.state == GECKO_NETWORK_STATE_DISCONNECTED) { 1.4119 + delete this.currentDataCalls[currentDataCall.cid]; 1.4120 + } 1.4121 + currentDataCall.status = updatedDataCall.status; 1.4122 + currentDataCall.active = updatedDataCall.active; 1.4123 + currentDataCall.state = updatedDataCall.state; 1.4124 + currentDataCall.rilMessageType = "datacallstatechange"; 1.4125 + this.sendChromeMessage(currentDataCall); 1.4126 + continue; 1.4127 + } 1.4128 + 1.4129 + // State not changed, now check links. 1.4130 + let result = 1.4131 + this._compareDataCallLink(updatedDataCall, currentDataCall); 1.4132 + if (result == "identical") { 1.4133 + if (DEBUG) this.context.debug("No changes in data call."); 1.4134 + continue; 1.4135 + } 1.4136 + if (result == "deactivate") { 1.4137 + if (DEBUG) this.context.debug("Data link changed, cleanup."); 1.4138 + this.deactivateDataCall(currentDataCall); 1.4139 + continue; 1.4140 + } 1.4141 + // Minor change, just update and notify. 1.4142 + if (DEBUG) { 1.4143 + this.context.debug("Data link minor change, just update and notify."); 1.4144 + } 1.4145 + currentDataCall.addresses = updatedDataCall.addresses.slice(); 1.4146 + currentDataCall.dnses = updatedDataCall.dnses.slice(); 1.4147 + currentDataCall.gateways = updatedDataCall.gateways.slice(); 1.4148 + currentDataCall.rilMessageType = "datacallstatechange"; 1.4149 + this.sendChromeMessage(currentDataCall); 1.4150 + } 1.4151 + 1.4152 + for each (let newDataCall in datacalls) { 1.4153 + if (!newDataCall.ifname) { 1.4154 + continue; 1.4155 + } 1.4156 + 1.4157 + if (!newDataCallOptions) { 1.4158 + if (DEBUG) { 1.4159 + this.context.debug("Unexpected new data call: " + 1.4160 + JSON.stringify(newDataCall)); 1.4161 + } 1.4162 + continue; 1.4163 + } 1.4164 + 1.4165 + this.currentDataCalls[newDataCall.cid] = newDataCall; 1.4166 + this._setDataCallGeckoState(newDataCall); 1.4167 + 1.4168 + newDataCall.radioTech = newDataCallOptions.radioTech; 1.4169 + newDataCall.apn = newDataCallOptions.apn; 1.4170 + newDataCall.user = newDataCallOptions.user; 1.4171 + newDataCall.passwd = newDataCallOptions.passwd; 1.4172 + newDataCall.chappap = newDataCallOptions.chappap; 1.4173 + newDataCall.pdptype = newDataCallOptions.pdptype; 1.4174 + newDataCallOptions = null; 1.4175 + 1.4176 + newDataCall.rilMessageType = "datacallstatechange"; 1.4177 + this.sendChromeMessage(newDataCall); 1.4178 + } 1.4179 + }, 1.4180 + 1.4181 + _setDataCallGeckoState: function(datacall) { 1.4182 + switch (datacall.active) { 1.4183 + case DATACALL_INACTIVE: 1.4184 + datacall.state = GECKO_NETWORK_STATE_DISCONNECTED; 1.4185 + break; 1.4186 + case DATACALL_ACTIVE_DOWN: 1.4187 + case DATACALL_ACTIVE_UP: 1.4188 + datacall.state = GECKO_NETWORK_STATE_CONNECTED; 1.4189 + break; 1.4190 + } 1.4191 + }, 1.4192 + 1.4193 + _processSuppSvcNotification: function(info) { 1.4194 + if (DEBUG) { 1.4195 + this.context.debug("handle supp svc notification: " + JSON.stringify(info)); 1.4196 + } 1.4197 + 1.4198 + if (info.notificationType !== 1) { 1.4199 + // We haven't supported MO intermediate result code, i.e. 1.4200 + // info.notificationType === 0, which refers to code1 defined in 3GPP 1.4201 + // 27.007 7.17. We only support partial MT unsolicited result code, 1.4202 + // referring to code2, for now. 1.4203 + return; 1.4204 + } 1.4205 + 1.4206 + let notification = null; 1.4207 + let callIndex = -1; 1.4208 + 1.4209 + switch (info.code) { 1.4210 + case SUPP_SVC_NOTIFICATION_CODE2_PUT_ON_HOLD: 1.4211 + case SUPP_SVC_NOTIFICATION_CODE2_RETRIEVED: 1.4212 + notification = GECKO_SUPP_SVC_NOTIFICATION_FROM_CODE2[info.code]; 1.4213 + break; 1.4214 + default: 1.4215 + // Notification type not supported. 1.4216 + return; 1.4217 + } 1.4218 + 1.4219 + // Get the target call object for this notification. 1.4220 + let currentCallIndexes = Object.keys(this.currentCalls); 1.4221 + if (currentCallIndexes.length === 1) { 1.4222 + // Only one call exists. This should be the target. 1.4223 + callIndex = currentCallIndexes[0]; 1.4224 + } else { 1.4225 + // Find the call in |currentCalls| by the given number. 1.4226 + if (info.number) { 1.4227 + for each (let currentCall in this.currentCalls) { 1.4228 + if (currentCall.number == info.number) { 1.4229 + callIndex = currentCall.callIndex; 1.4230 + break; 1.4231 + } 1.4232 + } 1.4233 + } 1.4234 + } 1.4235 + 1.4236 + let message = {rilMessageType: "suppSvcNotification", 1.4237 + notification: notification, 1.4238 + callIndex: callIndex}; 1.4239 + this.sendChromeMessage(message); 1.4240 + }, 1.4241 + 1.4242 + _cancelEmergencyCbModeTimeout: function() { 1.4243 + if (this._exitEmergencyCbModeTimeoutID) { 1.4244 + clearTimeout(this._exitEmergencyCbModeTimeoutID); 1.4245 + this._exitEmergencyCbModeTimeoutID = null; 1.4246 + } 1.4247 + }, 1.4248 + 1.4249 + _handleChangedEmergencyCbMode: function(active) { 1.4250 + this._isInEmergencyCbMode = active; 1.4251 + 1.4252 + // Clear the existed timeout event. 1.4253 + this._cancelEmergencyCbModeTimeout(); 1.4254 + 1.4255 + // Start a new timeout event when entering the mode. 1.4256 + if (active) { 1.4257 + this._exitEmergencyCbModeTimeoutID = setTimeout( 1.4258 + this.exitEmergencyCbMode.bind(this), EMERGENCY_CB_MODE_TIMEOUT_MS); 1.4259 + } 1.4260 + 1.4261 + let message = {rilMessageType: "emergencyCbModeChange", 1.4262 + active: active, 1.4263 + timeoutMs: EMERGENCY_CB_MODE_TIMEOUT_MS}; 1.4264 + this.sendChromeMessage(message); 1.4265 + }, 1.4266 + 1.4267 + _processNetworks: function() { 1.4268 + let strings = this.context.Buf.readStringList(); 1.4269 + let networks = []; 1.4270 + 1.4271 + for (let i = 0; i < strings.length; i += 4) { 1.4272 + let network = { 1.4273 + longName: strings[i], 1.4274 + shortName: strings[i + 1], 1.4275 + mcc: null, 1.4276 + mnc: null, 1.4277 + state: null 1.4278 + }; 1.4279 + 1.4280 + let networkTuple = strings[i + 2]; 1.4281 + try { 1.4282 + this._processNetworkTuple(networkTuple, network); 1.4283 + } catch (e) { 1.4284 + if (DEBUG) this.context.debug("Error processing operator tuple: " + e); 1.4285 + } 1.4286 + 1.4287 + let state = strings[i + 3]; 1.4288 + if (state === NETWORK_STATE_UNKNOWN) { 1.4289 + // TODO: looks like this might conflict in style with 1.4290 + // GECKO_NETWORK_STYLE_UNKNOWN / nsINetworkManager 1.4291 + state = GECKO_QAN_STATE_UNKNOWN; 1.4292 + } 1.4293 + 1.4294 + network.state = state; 1.4295 + networks.push(network); 1.4296 + } 1.4297 + return networks; 1.4298 + }, 1.4299 + 1.4300 + /** 1.4301 + * The "numeric" portion of the operator info is a tuple 1.4302 + * containing MCC (country code) and MNC (network code). 1.4303 + * AFAICT, MCC should always be 3 digits, making the remaining 1.4304 + * portion the MNC. 1.4305 + */ 1.4306 + _processNetworkTuple: function(networkTuple, network) { 1.4307 + let tupleLen = networkTuple.length; 1.4308 + 1.4309 + if (tupleLen == 5 || tupleLen == 6) { 1.4310 + network.mcc = networkTuple.substr(0, 3); 1.4311 + network.mnc = networkTuple.substr(3); 1.4312 + } else { 1.4313 + network.mcc = null; 1.4314 + network.mnc = null; 1.4315 + 1.4316 + throw new Error("Invalid network tuple (should be 5 or 6 digits): " + networkTuple); 1.4317 + } 1.4318 + }, 1.4319 + 1.4320 + /** 1.4321 + * Check if GSM radio access technology group. 1.4322 + */ 1.4323 + _isGsmTechGroup: function(radioTech) { 1.4324 + if (!radioTech) { 1.4325 + return true; 1.4326 + } 1.4327 + 1.4328 + switch(radioTech) { 1.4329 + case NETWORK_CREG_TECH_GPRS: 1.4330 + case NETWORK_CREG_TECH_EDGE: 1.4331 + case NETWORK_CREG_TECH_UMTS: 1.4332 + case NETWORK_CREG_TECH_HSDPA: 1.4333 + case NETWORK_CREG_TECH_HSUPA: 1.4334 + case NETWORK_CREG_TECH_HSPA: 1.4335 + case NETWORK_CREG_TECH_LTE: 1.4336 + case NETWORK_CREG_TECH_HSPAP: 1.4337 + case NETWORK_CREG_TECH_GSM: 1.4338 + return true; 1.4339 + } 1.4340 + 1.4341 + return false; 1.4342 + }, 1.4343 + 1.4344 + /** 1.4345 + * Process radio technology change. 1.4346 + */ 1.4347 + _processRadioTech: function(radioTech) { 1.4348 + let isCdma = !this._isGsmTechGroup(radioTech); 1.4349 + this.radioTech = radioTech; 1.4350 + 1.4351 + if (DEBUG) { 1.4352 + this.context.debug("Radio tech is set to: " + GECKO_RADIO_TECH[radioTech] + 1.4353 + ", it is a " + (isCdma?"cdma":"gsm") + " technology"); 1.4354 + } 1.4355 + 1.4356 + // We should request SIM information when 1.4357 + // 1. Radio state has been changed, so we are waiting for radioTech or 1.4358 + // 2. isCdma is different from this._isCdma. 1.4359 + if (this._waitingRadioTech || isCdma != this._isCdma) { 1.4360 + this._isCdma = isCdma; 1.4361 + this._waitingRadioTech = false; 1.4362 + if (this._isCdma) { 1.4363 + this.getDeviceIdentity(); 1.4364 + } else { 1.4365 + this.getIMEI(); 1.4366 + this.getIMEISV(); 1.4367 + } 1.4368 + this.getICCStatus(); 1.4369 + } 1.4370 + }, 1.4371 + 1.4372 + /** 1.4373 + * Helper for returning the TOA for the given dial string. 1.4374 + */ 1.4375 + _toaFromString: function(number) { 1.4376 + let toa = TOA_UNKNOWN; 1.4377 + if (number && number.length > 0 && number[0] == '+') { 1.4378 + toa = TOA_INTERNATIONAL; 1.4379 + } 1.4380 + return toa; 1.4381 + }, 1.4382 + 1.4383 + /** 1.4384 + * Helper for translating basic service group to call forwarding service class 1.4385 + * parameter. 1.4386 + */ 1.4387 + _siToServiceClass: function(si) { 1.4388 + if (!si) { 1.4389 + return ICC_SERVICE_CLASS_NONE; 1.4390 + } 1.4391 + 1.4392 + let serviceCode = parseInt(si, 10); 1.4393 + switch (serviceCode) { 1.4394 + case 10: 1.4395 + return ICC_SERVICE_CLASS_SMS + ICC_SERVICE_CLASS_FAX + ICC_SERVICE_CLASS_VOICE; 1.4396 + case 11: 1.4397 + return ICC_SERVICE_CLASS_VOICE; 1.4398 + case 12: 1.4399 + return ICC_SERVICE_CLASS_SMS + ICC_SERVICE_CLASS_FAX; 1.4400 + case 13: 1.4401 + return ICC_SERVICE_CLASS_FAX; 1.4402 + case 16: 1.4403 + return ICC_SERVICE_CLASS_SMS; 1.4404 + case 19: 1.4405 + return ICC_SERVICE_CLASS_FAX + ICC_SERVICE_CLASS_VOICE; 1.4406 + case 21: 1.4407 + return ICC_SERVICE_CLASS_PAD + ICC_SERVICE_CLASS_DATA_ASYNC; 1.4408 + case 22: 1.4409 + return ICC_SERVICE_CLASS_PACKET + ICC_SERVICE_CLASS_DATA_SYNC; 1.4410 + case 25: 1.4411 + return ICC_SERVICE_CLASS_DATA_ASYNC; 1.4412 + case 26: 1.4413 + return ICC_SERVICE_CLASS_DATA_SYNC + SERVICE_CLASS_VOICE; 1.4414 + case 99: 1.4415 + return ICC_SERVICE_CLASS_PACKET; 1.4416 + default: 1.4417 + return ICC_SERVICE_CLASS_NONE; 1.4418 + } 1.4419 + }, 1.4420 + 1.4421 + /** 1.4422 + * @param message A decoded SMS-DELIVER message. 1.4423 + * 1.4424 + * @see 3GPP TS 31.111 section 7.1.1 1.4425 + */ 1.4426 + dataDownloadViaSMSPP: function(message) { 1.4427 + let Buf = this.context.Buf; 1.4428 + let GsmPDUHelper = this.context.GsmPDUHelper; 1.4429 + 1.4430 + let options = { 1.4431 + pid: message.pid, 1.4432 + dcs: message.dcs, 1.4433 + encoding: message.encoding, 1.4434 + }; 1.4435 + Buf.newParcel(REQUEST_STK_SEND_ENVELOPE_WITH_STATUS, options); 1.4436 + 1.4437 + Buf.seekIncoming(-1 * (Buf.getCurrentParcelSize() - Buf.getReadAvailable() 1.4438 + - 2 * Buf.UINT32_SIZE)); // Skip response_type & request_type. 1.4439 + let messageStringLength = Buf.readInt32(); // In semi-octets 1.4440 + let smscLength = GsmPDUHelper.readHexOctet(); // In octets, inclusive of TOA 1.4441 + let tpduLength = (messageStringLength / 2) - (smscLength + 1); // In octets 1.4442 + 1.4443 + // Device identities: 4 bytes 1.4444 + // Address: 0 or (2 + smscLength) 1.4445 + // SMS TPDU: (2 or 3) + tpduLength 1.4446 + let berLen = 4 + 1.4447 + (smscLength ? (2 + smscLength) : 0) + 1.4448 + (tpduLength <= 127 ? 2 : 3) + tpduLength; // In octets 1.4449 + 1.4450 + let parcelLength = (berLen <= 127 ? 2 : 3) + berLen; // In octets 1.4451 + Buf.writeInt32(parcelLength * 2); // In semi-octets 1.4452 + 1.4453 + // Write a BER-TLV 1.4454 + GsmPDUHelper.writeHexOctet(BER_SMS_PP_DOWNLOAD_TAG); 1.4455 + if (berLen > 127) { 1.4456 + GsmPDUHelper.writeHexOctet(0x81); 1.4457 + } 1.4458 + GsmPDUHelper.writeHexOctet(berLen); 1.4459 + 1.4460 + // Device Identifies-TLV 1.4461 + GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_DEVICE_ID | 1.4462 + COMPREHENSIONTLV_FLAG_CR); 1.4463 + GsmPDUHelper.writeHexOctet(0x02); 1.4464 + GsmPDUHelper.writeHexOctet(STK_DEVICE_ID_NETWORK); 1.4465 + GsmPDUHelper.writeHexOctet(STK_DEVICE_ID_SIM); 1.4466 + 1.4467 + // Address-TLV 1.4468 + if (smscLength) { 1.4469 + GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_ADDRESS); 1.4470 + GsmPDUHelper.writeHexOctet(smscLength); 1.4471 + Buf.copyIncomingToOutgoing(Buf.PDU_HEX_OCTET_SIZE * smscLength); 1.4472 + } 1.4473 + 1.4474 + // SMS TPDU-TLV 1.4475 + GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_SMS_TPDU | 1.4476 + COMPREHENSIONTLV_FLAG_CR); 1.4477 + if (tpduLength > 127) { 1.4478 + GsmPDUHelper.writeHexOctet(0x81); 1.4479 + } 1.4480 + GsmPDUHelper.writeHexOctet(tpduLength); 1.4481 + Buf.copyIncomingToOutgoing(Buf.PDU_HEX_OCTET_SIZE * tpduLength); 1.4482 + 1.4483 + // Write 2 string delimitors for the total string length must be even. 1.4484 + Buf.writeStringDelimiter(0); 1.4485 + 1.4486 + Buf.sendParcel(); 1.4487 + }, 1.4488 + 1.4489 + /** 1.4490 + * @param success A boolean value indicating the result of previous 1.4491 + * SMS-DELIVER message handling. 1.4492 + * @param responsePduLen ICC IO response PDU length in octets. 1.4493 + * @param options An object that contains four attributes: `pid`, `dcs`, 1.4494 + * `encoding` and `responsePduLen`. 1.4495 + * 1.4496 + * @see 3GPP TS 23.040 section 9.2.2.1a 1.4497 + */ 1.4498 + acknowledgeIncomingGsmSmsWithPDU: function(success, responsePduLen, options) { 1.4499 + let Buf = this.context.Buf; 1.4500 + let GsmPDUHelper = this.context.GsmPDUHelper; 1.4501 + 1.4502 + Buf.newParcel(REQUEST_ACKNOWLEDGE_INCOMING_GSM_SMS_WITH_PDU); 1.4503 + 1.4504 + // Two strings. 1.4505 + Buf.writeInt32(2); 1.4506 + 1.4507 + // String 1: Success 1.4508 + Buf.writeString(success ? "1" : "0"); 1.4509 + 1.4510 + // String 2: RP-ACK/RP-ERROR PDU 1.4511 + Buf.writeInt32(2 * (responsePduLen + (success ? 5 : 6))); // In semi-octet 1.4512 + // 1. TP-MTI & TP-UDHI 1.4513 + GsmPDUHelper.writeHexOctet(PDU_MTI_SMS_DELIVER); 1.4514 + if (!success) { 1.4515 + // 2. TP-FCS 1.4516 + GsmPDUHelper.writeHexOctet(PDU_FCS_USIM_DATA_DOWNLOAD_ERROR); 1.4517 + } 1.4518 + // 3. TP-PI 1.4519 + GsmPDUHelper.writeHexOctet(PDU_PI_USER_DATA_LENGTH | 1.4520 + PDU_PI_DATA_CODING_SCHEME | 1.4521 + PDU_PI_PROTOCOL_IDENTIFIER); 1.4522 + // 4. TP-PID 1.4523 + GsmPDUHelper.writeHexOctet(options.pid); 1.4524 + // 5. TP-DCS 1.4525 + GsmPDUHelper.writeHexOctet(options.dcs); 1.4526 + // 6. TP-UDL 1.4527 + if (options.encoding == PDU_DCS_MSG_CODING_7BITS_ALPHABET) { 1.4528 + GsmPDUHelper.writeHexOctet(Math.floor(responsePduLen * 8 / 7)); 1.4529 + } else { 1.4530 + GsmPDUHelper.writeHexOctet(responsePduLen); 1.4531 + } 1.4532 + // TP-UD 1.4533 + Buf.copyIncomingToOutgoing(Buf.PDU_HEX_OCTET_SIZE * responsePduLen); 1.4534 + // Write 2 string delimitors for the total string length must be even. 1.4535 + Buf.writeStringDelimiter(0); 1.4536 + 1.4537 + Buf.sendParcel(); 1.4538 + }, 1.4539 + 1.4540 + /** 1.4541 + * @param message A decoded SMS-DELIVER message. 1.4542 + */ 1.4543 + writeSmsToSIM: function(message) { 1.4544 + let Buf = this.context.Buf; 1.4545 + let GsmPDUHelper = this.context.GsmPDUHelper; 1.4546 + 1.4547 + Buf.newParcel(REQUEST_WRITE_SMS_TO_SIM); 1.4548 + 1.4549 + // Write EFsms Status 1.4550 + Buf.writeInt32(EFSMS_STATUS_FREE); 1.4551 + 1.4552 + Buf.seekIncoming(-1 * (Buf.getCurrentParcelSize() - Buf.getReadAvailable() 1.4553 + - 2 * Buf.UINT32_SIZE)); // Skip response_type & request_type. 1.4554 + let messageStringLength = Buf.readInt32(); // In semi-octets 1.4555 + let smscLength = GsmPDUHelper.readHexOctet(); // In octets, inclusive of TOA 1.4556 + let pduLength = (messageStringLength / 2) - (smscLength + 1); // In octets 1.4557 + 1.4558 + // 1. Write PDU first. 1.4559 + if (smscLength > 0) { 1.4560 + Buf.seekIncoming(smscLength * Buf.PDU_HEX_OCTET_SIZE); 1.4561 + } 1.4562 + // Write EFsms PDU string length 1.4563 + Buf.writeInt32(2 * pduLength); // In semi-octets 1.4564 + if (pduLength) { 1.4565 + Buf.copyIncomingToOutgoing(Buf.PDU_HEX_OCTET_SIZE * pduLength); 1.4566 + } 1.4567 + // Write 2 string delimitors for the total string length must be even. 1.4568 + Buf.writeStringDelimiter(0); 1.4569 + 1.4570 + // 2. Write SMSC 1.4571 + // Write EFsms SMSC string length 1.4572 + Buf.writeInt32(2 * (smscLength + 1)); // Plus smscLength itself, in semi-octets 1.4573 + // Write smscLength 1.4574 + GsmPDUHelper.writeHexOctet(smscLength); 1.4575 + // Write TOA & SMSC Address 1.4576 + if (smscLength) { 1.4577 + Buf.seekIncoming(-1 * (Buf.getCurrentParcelSize() - Buf.getReadAvailable() 1.4578 + - 2 * Buf.UINT32_SIZE // Skip response_type, request_type. 1.4579 + - 2 * Buf.PDU_HEX_OCTET_SIZE)); // Skip messageStringLength & smscLength. 1.4580 + Buf.copyIncomingToOutgoing(Buf.PDU_HEX_OCTET_SIZE * smscLength); 1.4581 + } 1.4582 + // Write 2 string delimitors for the total string length must be even. 1.4583 + Buf.writeStringDelimiter(0); 1.4584 + 1.4585 + Buf.sendParcel(); 1.4586 + }, 1.4587 + 1.4588 + /** 1.4589 + * Helper to delegate the received sms segment to RadioInterface to process. 1.4590 + * 1.4591 + * @param message 1.4592 + * Received sms message. 1.4593 + * 1.4594 + * @return MOZ_FCS_WAIT_FOR_EXPLICIT_ACK 1.4595 + */ 1.4596 + _processSmsMultipart: function(message) { 1.4597 + message.rilMessageType = "sms-received"; 1.4598 + 1.4599 + this.sendChromeMessage(message); 1.4600 + 1.4601 + return MOZ_FCS_WAIT_FOR_EXPLICIT_ACK; 1.4602 + }, 1.4603 + 1.4604 + /** 1.4605 + * Helper for processing SMS-STATUS-REPORT PDUs. 1.4606 + * 1.4607 + * @param length 1.4608 + * Length of SMS string in the incoming parcel. 1.4609 + * 1.4610 + * @return A failure cause defined in 3GPP 23.040 clause 9.2.3.22. 1.4611 + */ 1.4612 + _processSmsStatusReport: function(length) { 1.4613 + let [message, result] = this.context.GsmPDUHelper.processReceivedSms(length); 1.4614 + if (!message) { 1.4615 + if (DEBUG) this.context.debug("invalid SMS-STATUS-REPORT"); 1.4616 + return PDU_FCS_UNSPECIFIED; 1.4617 + } 1.4618 + 1.4619 + let options = this._pendingSentSmsMap[message.messageRef]; 1.4620 + if (!options) { 1.4621 + if (DEBUG) this.context.debug("no pending SMS-SUBMIT message"); 1.4622 + return PDU_FCS_OK; 1.4623 + } 1.4624 + 1.4625 + let status = message.status; 1.4626 + 1.4627 + // 3GPP TS 23.040 9.2.3.15 `The MS shall interpret any reserved values as 1.4628 + // "Service Rejected"(01100011) but shall store them exactly as received.` 1.4629 + if ((status >= 0x80) 1.4630 + || ((status >= PDU_ST_0_RESERVED_BEGIN) 1.4631 + && (status < PDU_ST_0_SC_SPECIFIC_BEGIN)) 1.4632 + || ((status >= PDU_ST_1_RESERVED_BEGIN) 1.4633 + && (status < PDU_ST_1_SC_SPECIFIC_BEGIN)) 1.4634 + || ((status >= PDU_ST_2_RESERVED_BEGIN) 1.4635 + && (status < PDU_ST_2_SC_SPECIFIC_BEGIN)) 1.4636 + || ((status >= PDU_ST_3_RESERVED_BEGIN) 1.4637 + && (status < PDU_ST_3_SC_SPECIFIC_BEGIN)) 1.4638 + ) { 1.4639 + status = PDU_ST_3_SERVICE_REJECTED; 1.4640 + } 1.4641 + 1.4642 + // Pending. Waiting for next status report. 1.4643 + if ((status >>> 5) == 0x01) { 1.4644 + if (DEBUG) this.context.debug("SMS-STATUS-REPORT: delivery still pending"); 1.4645 + return PDU_FCS_OK; 1.4646 + } 1.4647 + 1.4648 + delete this._pendingSentSmsMap[message.messageRef]; 1.4649 + 1.4650 + let deliveryStatus = ((status >>> 5) === 0x00) 1.4651 + ? GECKO_SMS_DELIVERY_STATUS_SUCCESS 1.4652 + : GECKO_SMS_DELIVERY_STATUS_ERROR; 1.4653 + this.sendChromeMessage({ 1.4654 + rilMessageType: options.rilMessageType, 1.4655 + rilMessageToken: options.rilMessageToken, 1.4656 + deliveryStatus: deliveryStatus 1.4657 + }); 1.4658 + 1.4659 + return PDU_FCS_OK; 1.4660 + }, 1.4661 + 1.4662 + /** 1.4663 + * Helper for processing CDMA SMS Delivery Acknowledgment Message 1.4664 + * 1.4665 + * @param message 1.4666 + * decoded SMS Delivery ACK message from CdmaPDUHelper. 1.4667 + * 1.4668 + * @return A failure cause defined in 3GPP 23.040 clause 9.2.3.22. 1.4669 + */ 1.4670 + _processCdmaSmsStatusReport: function(message) { 1.4671 + let options = this._pendingSentSmsMap[message.msgId]; 1.4672 + if (!options) { 1.4673 + if (DEBUG) this.context.debug("no pending SMS-SUBMIT message"); 1.4674 + return PDU_FCS_OK; 1.4675 + } 1.4676 + 1.4677 + if (message.errorClass === 2) { 1.4678 + if (DEBUG) { 1.4679 + this.context.debug("SMS-STATUS-REPORT: delivery still pending, " + 1.4680 + "msgStatus: " + message.msgStatus); 1.4681 + } 1.4682 + return PDU_FCS_OK; 1.4683 + } 1.4684 + 1.4685 + delete this._pendingSentSmsMap[message.msgId]; 1.4686 + 1.4687 + if (message.errorClass === -1 && message.body) { 1.4688 + // Process as normal incoming SMS, if errorClass is invalid 1.4689 + // but message body is available. 1.4690 + return this._processSmsMultipart(message); 1.4691 + } 1.4692 + 1.4693 + let deliveryStatus = (message.errorClass === 0) 1.4694 + ? GECKO_SMS_DELIVERY_STATUS_SUCCESS 1.4695 + : GECKO_SMS_DELIVERY_STATUS_ERROR; 1.4696 + this.sendChromeMessage({ 1.4697 + rilMessageType: options.rilMessageType, 1.4698 + rilMessageToken: options.rilMessageToken, 1.4699 + deliveryStatus: deliveryStatus 1.4700 + }); 1.4701 + 1.4702 + return PDU_FCS_OK; 1.4703 + }, 1.4704 + 1.4705 + /** 1.4706 + * Helper for processing CDMA SMS WAP Push Message 1.4707 + * 1.4708 + * @param message 1.4709 + * decoded WAP message from CdmaPDUHelper. 1.4710 + * 1.4711 + * @return A failure cause defined in 3GPP 23.040 clause 9.2.3.22. 1.4712 + */ 1.4713 + _processCdmaSmsWapPush: function(message) { 1.4714 + if (!message.data) { 1.4715 + if (DEBUG) this.context.debug("no data inside WAP Push message."); 1.4716 + return PDU_FCS_OK; 1.4717 + } 1.4718 + 1.4719 + // See 6.5. MAPPING OF WDP TO CDMA SMS in WAP-295-WDP. 1.4720 + // 1.4721 + // Field | Length (bits) 1.4722 + // ----------------------------------------- 1.4723 + // MSG_TYPE | 8 1.4724 + // TOTAL_SEGMENTS | 8 1.4725 + // SEGMENT_NUMBER | 8 1.4726 + // DATAGRAM | (NUM_FIELDS – 3) * 8 1.4727 + let index = 0; 1.4728 + if (message.data[index++] !== 0) { 1.4729 + if (DEBUG) this.context.debug("Ignore a WAP Message which is not WDP."); 1.4730 + return PDU_FCS_OK; 1.4731 + } 1.4732 + 1.4733 + // 1. Originator Address in SMS-TL + Message_Id in SMS-TS are used to identify a unique WDP datagram. 1.4734 + // 2. TOTAL_SEGMENTS, SEGMENT_NUMBER are used to verify that a complete 1.4735 + // datagram has been received and is ready to be passed to a higher layer. 1.4736 + message.header = { 1.4737 + segmentRef: message.msgId, 1.4738 + segmentMaxSeq: message.data[index++], 1.4739 + segmentSeq: message.data[index++] + 1 // It's zero-based in CDMA WAP Push. 1.4740 + }; 1.4741 + 1.4742 + if (message.header.segmentSeq > message.header.segmentMaxSeq) { 1.4743 + if (DEBUG) this.context.debug("Wrong WDP segment info."); 1.4744 + return PDU_FCS_OK; 1.4745 + } 1.4746 + 1.4747 + // Ports are only specified in 1st segment. 1.4748 + if (message.header.segmentSeq == 1) { 1.4749 + message.header.originatorPort = message.data[index++] << 8; 1.4750 + message.header.originatorPort |= message.data[index++]; 1.4751 + message.header.destinationPort = message.data[index++] << 8; 1.4752 + message.header.destinationPort |= message.data[index++]; 1.4753 + } 1.4754 + 1.4755 + message.data = message.data.subarray(index); 1.4756 + 1.4757 + return this._processSmsMultipart(message); 1.4758 + }, 1.4759 + 1.4760 + /** 1.4761 + * Helper for processing sent multipart SMS. 1.4762 + */ 1.4763 + _processSentSmsSegment: function(options) { 1.4764 + // Setup attributes for sending next segment 1.4765 + let next = options.segmentSeq; 1.4766 + options.body = options.segments[next].body; 1.4767 + options.encodedBodyLength = options.segments[next].encodedBodyLength; 1.4768 + options.segmentSeq = next + 1; 1.4769 + 1.4770 + this.sendSMS(options); 1.4771 + }, 1.4772 + 1.4773 + /** 1.4774 + * Helper for processing result of send SMS. 1.4775 + * 1.4776 + * @param length 1.4777 + * Length of SMS string in the incoming parcel. 1.4778 + * @param options 1.4779 + * Sms information. 1.4780 + */ 1.4781 + _processSmsSendResult: function(length, options) { 1.4782 + if (options.rilRequestError) { 1.4783 + if (DEBUG) { 1.4784 + this.context.debug("_processSmsSendResult: rilRequestError = " + 1.4785 + options.rilRequestError); 1.4786 + } 1.4787 + switch (options.rilRequestError) { 1.4788 + case ERROR_SMS_SEND_FAIL_RETRY: 1.4789 + if (options.retryCount < SMS_RETRY_MAX) { 1.4790 + options.retryCount++; 1.4791 + // TODO: bug 736702 TP-MR, retry interval, retry timeout 1.4792 + this.sendSMS(options); 1.4793 + break; 1.4794 + } 1.4795 + // Fallback to default error handling if it meets max retry count. 1.4796 + // Fall through. 1.4797 + default: 1.4798 + this.sendChromeMessage({ 1.4799 + rilMessageType: options.rilMessageType, 1.4800 + rilMessageToken: options.rilMessageToken, 1.4801 + errorMsg: options.rilRequestError, 1.4802 + }); 1.4803 + break; 1.4804 + } 1.4805 + return; 1.4806 + } 1.4807 + 1.4808 + let Buf = this.context.Buf; 1.4809 + options.messageRef = Buf.readInt32(); 1.4810 + options.ackPDU = Buf.readString(); 1.4811 + options.errorCode = Buf.readInt32(); 1.4812 + 1.4813 + if ((options.segmentMaxSeq > 1) 1.4814 + && (options.segmentSeq < options.segmentMaxSeq)) { 1.4815 + // Not last segment 1.4816 + this._processSentSmsSegment(options); 1.4817 + } else { 1.4818 + // Last segment sent with success. 1.4819 + if (options.requestStatusReport) { 1.4820 + if (DEBUG) { 1.4821 + this.context.debug("waiting SMS-STATUS-REPORT for messageRef " + 1.4822 + options.messageRef); 1.4823 + } 1.4824 + this._pendingSentSmsMap[options.messageRef] = options; 1.4825 + } 1.4826 + 1.4827 + this.sendChromeMessage({ 1.4828 + rilMessageType: options.rilMessageType, 1.4829 + rilMessageToken: options.rilMessageToken, 1.4830 + }); 1.4831 + } 1.4832 + }, 1.4833 + 1.4834 + _processReceivedSmsCbPage: function(original) { 1.4835 + if (original.numPages <= 1) { 1.4836 + if (original.body) { 1.4837 + original.fullBody = original.body; 1.4838 + delete original.body; 1.4839 + } else if (original.data) { 1.4840 + original.fullData = original.data; 1.4841 + delete original.data; 1.4842 + } 1.4843 + return original; 1.4844 + } 1.4845 + 1.4846 + // Hash = <serial>:<mcc>:<mnc>:<lac>:<cid> 1.4847 + let hash = original.serial + ":" + this.iccInfo.mcc + ":" 1.4848 + + this.iccInfo.mnc + ":"; 1.4849 + switch (original.geographicalScope) { 1.4850 + case CB_GSM_GEOGRAPHICAL_SCOPE_CELL_WIDE_IMMEDIATE: 1.4851 + case CB_GSM_GEOGRAPHICAL_SCOPE_CELL_WIDE: 1.4852 + hash += this.voiceRegistrationState.cell.gsmLocationAreaCode + ":" 1.4853 + + this.voiceRegistrationState.cell.gsmCellId; 1.4854 + break; 1.4855 + case CB_GSM_GEOGRAPHICAL_SCOPE_LOCATION_AREA_WIDE: 1.4856 + hash += this.voiceRegistrationState.cell.gsmLocationAreaCode + ":"; 1.4857 + break; 1.4858 + default: 1.4859 + hash += ":"; 1.4860 + break; 1.4861 + } 1.4862 + 1.4863 + let index = original.pageIndex; 1.4864 + 1.4865 + let options = this._receivedSmsCbPagesMap[hash]; 1.4866 + if (!options) { 1.4867 + options = original; 1.4868 + this._receivedSmsCbPagesMap[hash] = options; 1.4869 + 1.4870 + options.receivedPages = 0; 1.4871 + options.pages = []; 1.4872 + } else if (options.pages[index]) { 1.4873 + // Duplicated page? 1.4874 + if (DEBUG) { 1.4875 + this.context.debug("Got duplicated page no." + index + 1.4876 + " of a multipage SMSCB: " + JSON.stringify(original)); 1.4877 + } 1.4878 + return null; 1.4879 + } 1.4880 + 1.4881 + if (options.encoding == PDU_DCS_MSG_CODING_8BITS_ALPHABET) { 1.4882 + options.pages[index] = original.data; 1.4883 + delete original.data; 1.4884 + } else { 1.4885 + options.pages[index] = original.body; 1.4886 + delete original.body; 1.4887 + } 1.4888 + options.receivedPages++; 1.4889 + if (options.receivedPages < options.numPages) { 1.4890 + if (DEBUG) { 1.4891 + this.context.debug("Got page no." + index + " of a multipage SMSCB: " + 1.4892 + JSON.stringify(options)); 1.4893 + } 1.4894 + return null; 1.4895 + } 1.4896 + 1.4897 + // Remove from map 1.4898 + delete this._receivedSmsCbPagesMap[hash]; 1.4899 + 1.4900 + // Rebuild full body 1.4901 + if (options.encoding == PDU_DCS_MSG_CODING_8BITS_ALPHABET) { 1.4902 + // Uint8Array doesn't have `concat`, so we have to merge all pages by hand. 1.4903 + let fullDataLen = 0; 1.4904 + for (let i = 1; i <= options.numPages; i++) { 1.4905 + fullDataLen += options.pages[i].length; 1.4906 + } 1.4907 + 1.4908 + options.fullData = new Uint8Array(fullDataLen); 1.4909 + for (let d= 0, i = 1; i <= options.numPages; i++) { 1.4910 + let data = options.pages[i]; 1.4911 + for (let j = 0; j < data.length; j++) { 1.4912 + options.fullData[d++] = data[j]; 1.4913 + } 1.4914 + } 1.4915 + } else { 1.4916 + options.fullBody = options.pages.join(""); 1.4917 + } 1.4918 + 1.4919 + if (DEBUG) { 1.4920 + this.context.debug("Got full multipage SMSCB: " + JSON.stringify(options)); 1.4921 + } 1.4922 + 1.4923 + return options; 1.4924 + }, 1.4925 + 1.4926 + _mergeCellBroadcastConfigs: function(list, from, to) { 1.4927 + if (!list) { 1.4928 + return [from, to]; 1.4929 + } 1.4930 + 1.4931 + for (let i = 0, f1, t1; i < list.length;) { 1.4932 + f1 = list[i++]; 1.4933 + t1 = list[i++]; 1.4934 + if (to == f1) { 1.4935 + // ...[from]...[to|f1]...(t1) 1.4936 + list[i - 2] = from; 1.4937 + return list; 1.4938 + } 1.4939 + 1.4940 + if (to < f1) { 1.4941 + // ...[from]...(to)...[f1] or ...[from]...(to)[f1] 1.4942 + if (i > 2) { 1.4943 + // Not the first range pair, merge three arrays. 1.4944 + return list.slice(0, i - 2).concat([from, to]).concat(list.slice(i - 2)); 1.4945 + } else { 1.4946 + return [from, to].concat(list); 1.4947 + } 1.4948 + } 1.4949 + 1.4950 + if (from > t1) { 1.4951 + // ...[f1]...(t1)[from] or ...[f1]...(t1)...[from] 1.4952 + continue; 1.4953 + } 1.4954 + 1.4955 + // Have overlap or merge-able adjacency with [f1]...(t1). Replace it 1.4956 + // with [min(from, f1)]...(max(to, t1)). 1.4957 + 1.4958 + let changed = false; 1.4959 + if (from < f1) { 1.4960 + // [from]...[f1]...(t1) or [from][f1]...(t1) 1.4961 + // Save minimum from value. 1.4962 + list[i - 2] = from; 1.4963 + changed = true; 1.4964 + } 1.4965 + 1.4966 + if (to <= t1) { 1.4967 + // [from]...[to](t1) or [from]...(to|t1) 1.4968 + // Can't have further merge-able adjacency. Return. 1.4969 + return list; 1.4970 + } 1.4971 + 1.4972 + // Try merging possible next adjacent range. 1.4973 + let j = i; 1.4974 + for (let f2, t2; j < list.length;) { 1.4975 + f2 = list[j++]; 1.4976 + t2 = list[j++]; 1.4977 + if (to > t2) { 1.4978 + // [from]...[f2]...[t2]...(to) or [from]...[f2]...[t2](to) 1.4979 + // Merge next adjacent range again. 1.4980 + continue; 1.4981 + } 1.4982 + 1.4983 + if (to < t2) { 1.4984 + if (to < f2) { 1.4985 + // [from]...(to)[f2] or [from]...(to)...[f2] 1.4986 + // Roll back and give up. 1.4987 + j -= 2; 1.4988 + } else if (to < t2) { 1.4989 + // [from]...[to|f2]...(t2), or [from]...[f2]...[to](t2) 1.4990 + // Merge to [from]...(t2) and give up. 1.4991 + to = t2; 1.4992 + } 1.4993 + } 1.4994 + 1.4995 + break; 1.4996 + } 1.4997 + 1.4998 + // Save maximum to value. 1.4999 + list[i - 1] = to; 1.5000 + 1.5001 + if (j != i) { 1.5002 + // Remove merged adjacent ranges. 1.5003 + let ret = list.slice(0, i); 1.5004 + if (j < list.length) { 1.5005 + ret = ret.concat(list.slice(j)); 1.5006 + } 1.5007 + return ret; 1.5008 + } 1.5009 + 1.5010 + return list; 1.5011 + } 1.5012 + 1.5013 + // Append to the end. 1.5014 + list.push(from); 1.5015 + list.push(to); 1.5016 + 1.5017 + return list; 1.5018 + }, 1.5019 + 1.5020 + _isCellBroadcastConfigReady: function() { 1.5021 + if (!("MMI" in this.cellBroadcastConfigs)) { 1.5022 + return false; 1.5023 + } 1.5024 + 1.5025 + // CBMI should be ready in GSM. 1.5026 + if (!this._isCdma && 1.5027 + (!("CBMI" in this.cellBroadcastConfigs) || 1.5028 + !("CBMID" in this.cellBroadcastConfigs) || 1.5029 + !("CBMIR" in this.cellBroadcastConfigs))) { 1.5030 + return false; 1.5031 + } 1.5032 + 1.5033 + return true; 1.5034 + }, 1.5035 + 1.5036 + /** 1.5037 + * Merge all members of cellBroadcastConfigs into mergedCellBroadcastConfig. 1.5038 + */ 1.5039 + _mergeAllCellBroadcastConfigs: function() { 1.5040 + if (!this._isCellBroadcastConfigReady()) { 1.5041 + if (DEBUG) { 1.5042 + this.context.debug("cell broadcast configs not ready, waiting ..."); 1.5043 + } 1.5044 + return; 1.5045 + } 1.5046 + 1.5047 + // Prepare cell broadcast config. CBMI* are only used in GSM. 1.5048 + let usedCellBroadcastConfigs = {MMI: this.cellBroadcastConfigs.MMI}; 1.5049 + if (!this._isCdma) { 1.5050 + usedCellBroadcastConfigs.CBMI = this.cellBroadcastConfigs.CBMI; 1.5051 + usedCellBroadcastConfigs.CBMID = this.cellBroadcastConfigs.CBMID; 1.5052 + usedCellBroadcastConfigs.CBMIR = this.cellBroadcastConfigs.CBMIR; 1.5053 + } 1.5054 + 1.5055 + if (DEBUG) { 1.5056 + this.context.debug("Cell Broadcast search lists: " + 1.5057 + JSON.stringify(usedCellBroadcastConfigs)); 1.5058 + } 1.5059 + 1.5060 + let list = null; 1.5061 + for each (let ll in usedCellBroadcastConfigs) { 1.5062 + if (ll == null) { 1.5063 + continue; 1.5064 + } 1.5065 + 1.5066 + for (let i = 0; i < ll.length; i += 2) { 1.5067 + list = this._mergeCellBroadcastConfigs(list, ll[i], ll[i + 1]); 1.5068 + } 1.5069 + } 1.5070 + 1.5071 + if (DEBUG) { 1.5072 + this.context.debug("Cell Broadcast search lists(merged): " + 1.5073 + JSON.stringify(list)); 1.5074 + } 1.5075 + this.mergedCellBroadcastConfig = list; 1.5076 + this.updateCellBroadcastConfig(); 1.5077 + }, 1.5078 + 1.5079 + /** 1.5080 + * Check whether search list from settings is settable by MMI, that is, 1.5081 + * whether the range is bounded in any entries of CB_NON_MMI_SETTABLE_RANGES. 1.5082 + */ 1.5083 + _checkCellBroadcastMMISettable: function(from, to) { 1.5084 + if ((to <= from) || (from >= 65536) || (from < 0)) { 1.5085 + return false; 1.5086 + } 1.5087 + 1.5088 + if (!this._isCdma) { 1.5089 + // GSM not settable ranges. 1.5090 + for (let i = 0, f, t; i < CB_NON_MMI_SETTABLE_RANGES.length;) { 1.5091 + f = CB_NON_MMI_SETTABLE_RANGES[i++]; 1.5092 + t = CB_NON_MMI_SETTABLE_RANGES[i++]; 1.5093 + if ((from < t) && (to > f)) { 1.5094 + // Have overlap. 1.5095 + return false; 1.5096 + } 1.5097 + } 1.5098 + } 1.5099 + 1.5100 + return true; 1.5101 + }, 1.5102 + 1.5103 + /** 1.5104 + * Convert Cell Broadcast settings string into search list. 1.5105 + */ 1.5106 + _convertCellBroadcastSearchList: function(searchListStr) { 1.5107 + let parts = searchListStr && searchListStr.split(","); 1.5108 + if (!parts) { 1.5109 + return null; 1.5110 + } 1.5111 + 1.5112 + let list = null; 1.5113 + let result, from, to; 1.5114 + for (let range of parts) { 1.5115 + // Match "12" or "12-34". The result will be ["12", "12", null] or 1.5116 + // ["12-34", "12", "34"]. 1.5117 + result = range.match(/^(\d+)(?:-(\d+))?$/); 1.5118 + if (!result) { 1.5119 + throw "Invalid format"; 1.5120 + } 1.5121 + 1.5122 + from = parseInt(result[1], 10); 1.5123 + to = (result[2]) ? parseInt(result[2], 10) + 1 : from + 1; 1.5124 + if (!this._checkCellBroadcastMMISettable(from, to)) { 1.5125 + throw "Invalid range"; 1.5126 + } 1.5127 + 1.5128 + if (list == null) { 1.5129 + list = []; 1.5130 + } 1.5131 + list.push(from); 1.5132 + list.push(to); 1.5133 + } 1.5134 + 1.5135 + return list; 1.5136 + }, 1.5137 + 1.5138 + /** 1.5139 + * Handle incoming messages from the main UI thread. 1.5140 + * 1.5141 + * @param message 1.5142 + * Object containing the message. Messages are supposed 1.5143 + */ 1.5144 + handleChromeMessage: function(message) { 1.5145 + if (DEBUG) { 1.5146 + this.context.debug("Received chrome message " + JSON.stringify(message)); 1.5147 + } 1.5148 + let method = this[message.rilMessageType]; 1.5149 + if (typeof method != "function") { 1.5150 + if (DEBUG) { 1.5151 + this.context.debug("Don't know what to do with message " + 1.5152 + JSON.stringify(message)); 1.5153 + } 1.5154 + return; 1.5155 + } 1.5156 + method.call(this, message); 1.5157 + }, 1.5158 + 1.5159 + /** 1.5160 + * Get a list of current voice calls. 1.5161 + */ 1.5162 + enumerateCalls: function(options) { 1.5163 + if (DEBUG) this.context.debug("Sending all current calls"); 1.5164 + let calls = []; 1.5165 + for each (let call in this.currentCalls) { 1.5166 + calls.push(call); 1.5167 + } 1.5168 + options.calls = calls; 1.5169 + this.sendChromeMessage(options); 1.5170 + }, 1.5171 + 1.5172 + /** 1.5173 + * Process STK Proactive Command. 1.5174 + */ 1.5175 + processStkProactiveCommand: function() { 1.5176 + let Buf = this.context.Buf; 1.5177 + let length = Buf.readInt32(); 1.5178 + let berTlv; 1.5179 + try { 1.5180 + berTlv = this.context.BerTlvHelper.decode(length / 2); 1.5181 + } catch (e) { 1.5182 + if (DEBUG) this.context.debug("processStkProactiveCommand : " + e); 1.5183 + this.sendStkTerminalResponse({ 1.5184 + resultCode: STK_RESULT_CMD_DATA_NOT_UNDERSTOOD}); 1.5185 + return; 1.5186 + } 1.5187 + 1.5188 + Buf.readStringDelimiter(length); 1.5189 + 1.5190 + let ctlvs = berTlv.value; 1.5191 + let ctlv = this.context.StkProactiveCmdHelper.searchForTag( 1.5192 + COMPREHENSIONTLV_TAG_COMMAND_DETAILS, ctlvs); 1.5193 + if (!ctlv) { 1.5194 + this.sendStkTerminalResponse({ 1.5195 + resultCode: STK_RESULT_CMD_DATA_NOT_UNDERSTOOD}); 1.5196 + throw new Error("Can't find COMMAND_DETAILS ComprehensionTlv"); 1.5197 + } 1.5198 + 1.5199 + let cmdDetails = ctlv.value; 1.5200 + if (DEBUG) { 1.5201 + this.context.debug("commandNumber = " + cmdDetails.commandNumber + 1.5202 + " typeOfCommand = " + cmdDetails.typeOfCommand.toString(16) + 1.5203 + " commandQualifier = " + cmdDetails.commandQualifier); 1.5204 + } 1.5205 + 1.5206 + // STK_CMD_MORE_TIME need not to propagate event to chrome. 1.5207 + if (cmdDetails.typeOfCommand == STK_CMD_MORE_TIME) { 1.5208 + this.sendStkTerminalResponse({ 1.5209 + command: cmdDetails, 1.5210 + resultCode: STK_RESULT_OK}); 1.5211 + return; 1.5212 + } 1.5213 + 1.5214 + cmdDetails.rilMessageType = "stkcommand"; 1.5215 + cmdDetails.options = 1.5216 + this.context.StkCommandParamsFactory.createParam(cmdDetails, ctlvs); 1.5217 + this.sendChromeMessage(cmdDetails); 1.5218 + }, 1.5219 + 1.5220 + /** 1.5221 + * Send messages to the main thread. 1.5222 + */ 1.5223 + sendChromeMessage: function(message) { 1.5224 + message.rilMessageClientId = this.context.clientId; 1.5225 + postMessage(message); 1.5226 + }, 1.5227 + 1.5228 + /** 1.5229 + * Handle incoming requests from the RIL. We find the method that 1.5230 + * corresponds to the request type. Incidentally, the request type 1.5231 + * _is_ the method name, so that's easy. 1.5232 + */ 1.5233 + 1.5234 + handleParcel: function(request_type, length, options) { 1.5235 + let method = this[request_type]; 1.5236 + if (typeof method == "function") { 1.5237 + if (DEBUG) this.context.debug("Handling parcel as " + method.name); 1.5238 + method.call(this, length, options); 1.5239 + } 1.5240 + } 1.5241 +}; 1.5242 + 1.5243 +RilObject.prototype[REQUEST_GET_SIM_STATUS] = function REQUEST_GET_SIM_STATUS(length, options) { 1.5244 + if (options.rilRequestError) { 1.5245 + return; 1.5246 + } 1.5247 + 1.5248 + let iccStatus = {}; 1.5249 + let Buf = this.context.Buf; 1.5250 + iccStatus.cardState = Buf.readInt32(); // CARD_STATE_* 1.5251 + iccStatus.universalPINState = Buf.readInt32(); // CARD_PINSTATE_* 1.5252 + iccStatus.gsmUmtsSubscriptionAppIndex = Buf.readInt32(); 1.5253 + iccStatus.cdmaSubscriptionAppIndex = Buf.readInt32(); 1.5254 + if (!this.v5Legacy) { 1.5255 + iccStatus.imsSubscriptionAppIndex = Buf.readInt32(); 1.5256 + } 1.5257 + 1.5258 + let apps_length = Buf.readInt32(); 1.5259 + if (apps_length > CARD_MAX_APPS) { 1.5260 + apps_length = CARD_MAX_APPS; 1.5261 + } 1.5262 + 1.5263 + iccStatus.apps = []; 1.5264 + for (let i = 0 ; i < apps_length ; i++) { 1.5265 + iccStatus.apps.push({ 1.5266 + app_type: Buf.readInt32(), // CARD_APPTYPE_* 1.5267 + app_state: Buf.readInt32(), // CARD_APPSTATE_* 1.5268 + perso_substate: Buf.readInt32(), // CARD_PERSOSUBSTATE_* 1.5269 + aid: Buf.readString(), 1.5270 + app_label: Buf.readString(), 1.5271 + pin1_replaced: Buf.readInt32(), 1.5272 + pin1: Buf.readInt32(), 1.5273 + pin2: Buf.readInt32() 1.5274 + }); 1.5275 + if (RILQUIRKS_SIM_APP_STATE_EXTRA_FIELDS) { 1.5276 + Buf.readInt32(); 1.5277 + Buf.readInt32(); 1.5278 + Buf.readInt32(); 1.5279 + Buf.readInt32(); 1.5280 + } 1.5281 + } 1.5282 + 1.5283 + if (DEBUG) this.context.debug("iccStatus: " + JSON.stringify(iccStatus)); 1.5284 + this._processICCStatus(iccStatus); 1.5285 +}; 1.5286 +RilObject.prototype[REQUEST_ENTER_SIM_PIN] = function REQUEST_ENTER_SIM_PIN(length, options) { 1.5287 + this._processEnterAndChangeICCResponses(length, options); 1.5288 +}; 1.5289 +RilObject.prototype[REQUEST_ENTER_SIM_PUK] = function REQUEST_ENTER_SIM_PUK(length, options) { 1.5290 + this._processEnterAndChangeICCResponses(length, options); 1.5291 +}; 1.5292 +RilObject.prototype[REQUEST_ENTER_SIM_PIN2] = function REQUEST_ENTER_SIM_PIN2(length, options) { 1.5293 + this._processEnterAndChangeICCResponses(length, options); 1.5294 +}; 1.5295 +RilObject.prototype[REQUEST_ENTER_SIM_PUK2] = function REQUEST_ENTER_SIM_PUK(length, options) { 1.5296 + this._processEnterAndChangeICCResponses(length, options); 1.5297 +}; 1.5298 +RilObject.prototype[REQUEST_CHANGE_SIM_PIN] = function REQUEST_CHANGE_SIM_PIN(length, options) { 1.5299 + this._processEnterAndChangeICCResponses(length, options); 1.5300 +}; 1.5301 +RilObject.prototype[REQUEST_CHANGE_SIM_PIN2] = function REQUEST_CHANGE_SIM_PIN2(length, options) { 1.5302 + this._processEnterAndChangeICCResponses(length, options); 1.5303 +}; 1.5304 +RilObject.prototype[REQUEST_ENTER_NETWORK_DEPERSONALIZATION_CODE] = 1.5305 + function REQUEST_ENTER_NETWORK_DEPERSONALIZATION_CODE(length, options) { 1.5306 + this._processEnterAndChangeICCResponses(length, options); 1.5307 +}; 1.5308 +RilObject.prototype[REQUEST_GET_CURRENT_CALLS] = function REQUEST_GET_CURRENT_CALLS(length, options) { 1.5309 + if (options.rilRequestError) { 1.5310 + return; 1.5311 + } 1.5312 + 1.5313 + let Buf = this.context.Buf; 1.5314 + let calls_length = 0; 1.5315 + // The RIL won't even send us the length integer if there are no active calls. 1.5316 + // So only read this integer if the parcel actually has it. 1.5317 + if (length) { 1.5318 + calls_length = Buf.readInt32(); 1.5319 + } 1.5320 + if (!calls_length) { 1.5321 + this._processCalls(null); 1.5322 + return; 1.5323 + } 1.5324 + 1.5325 + let calls = {}; 1.5326 + for (let i = 0; i < calls_length; i++) { 1.5327 + let call = {}; 1.5328 + 1.5329 + // Extra uint32 field to get correct callIndex and rest of call data for 1.5330 + // call waiting feature. 1.5331 + if (RILQUIRKS_EXTRA_UINT32_2ND_CALL && i > 0) { 1.5332 + Buf.readInt32(); 1.5333 + } 1.5334 + 1.5335 + call.state = Buf.readInt32(); // CALL_STATE_* 1.5336 + call.callIndex = Buf.readInt32(); // GSM index (1-based) 1.5337 + call.toa = Buf.readInt32(); 1.5338 + call.isMpty = Boolean(Buf.readInt32()); 1.5339 + call.isMT = Boolean(Buf.readInt32()); 1.5340 + call.als = Buf.readInt32(); 1.5341 + call.isVoice = Boolean(Buf.readInt32()); 1.5342 + call.isVoicePrivacy = Boolean(Buf.readInt32()); 1.5343 + if (RILQUIRKS_CALLSTATE_EXTRA_UINT32) { 1.5344 + Buf.readInt32(); 1.5345 + } 1.5346 + call.number = Buf.readString(); //TODO munge with TOA 1.5347 + call.numberPresentation = Buf.readInt32(); // CALL_PRESENTATION_* 1.5348 + call.name = Buf.readString(); 1.5349 + call.namePresentation = Buf.readInt32(); 1.5350 + 1.5351 + call.uusInfo = null; 1.5352 + let uusInfoPresent = Buf.readInt32(); 1.5353 + if (uusInfoPresent == 1) { 1.5354 + call.uusInfo = { 1.5355 + type: Buf.readInt32(), 1.5356 + dcs: Buf.readInt32(), 1.5357 + userData: null //XXX TODO byte array?!? 1.5358 + }; 1.5359 + } 1.5360 + 1.5361 + calls[call.callIndex] = call; 1.5362 + } 1.5363 + this._processCalls(calls); 1.5364 +}; 1.5365 +RilObject.prototype[REQUEST_DIAL] = function REQUEST_DIAL(length, options) { 1.5366 + // We already return a successful response before. Don't respond it again! 1.5367 + if (options.rilRequestError) { 1.5368 + this.getFailCauseCode((function(failCause) { 1.5369 + this._removePendingOutgoingCall(failCause); 1.5370 + }).bind(this)); 1.5371 + } 1.5372 +}; 1.5373 +RilObject.prototype[REQUEST_DIAL_EMERGENCY_CALL] = RilObject.prototype[REQUEST_DIAL]; 1.5374 +RilObject.prototype[REQUEST_GET_IMSI] = function REQUEST_GET_IMSI(length, options) { 1.5375 + if (options.rilRequestError) { 1.5376 + return; 1.5377 + } 1.5378 + 1.5379 + this.iccInfoPrivate.imsi = this.context.Buf.readString(); 1.5380 + if (DEBUG) { 1.5381 + this.context.debug("IMSI: " + this.iccInfoPrivate.imsi); 1.5382 + } 1.5383 + 1.5384 + options.rilMessageType = "iccimsi"; 1.5385 + options.imsi = this.iccInfoPrivate.imsi; 1.5386 + this.sendChromeMessage(options); 1.5387 +}; 1.5388 +RilObject.prototype[REQUEST_HANGUP] = function REQUEST_HANGUP(length, options) { 1.5389 + if (options.rilRequestError) { 1.5390 + return; 1.5391 + } 1.5392 + 1.5393 + this.getCurrentCalls(); 1.5394 +}; 1.5395 +RilObject.prototype[REQUEST_HANGUP_WAITING_OR_BACKGROUND] = function REQUEST_HANGUP_WAITING_OR_BACKGROUND(length, options) { 1.5396 + if (options.rilRequestError) { 1.5397 + return; 1.5398 + } 1.5399 + 1.5400 + this.getCurrentCalls(); 1.5401 +}; 1.5402 +RilObject.prototype[REQUEST_HANGUP_FOREGROUND_RESUME_BACKGROUND] = function REQUEST_HANGUP_FOREGROUND_RESUME_BACKGROUND(length, options) { 1.5403 + if (options.rilRequestError) { 1.5404 + return; 1.5405 + } 1.5406 + 1.5407 + this.getCurrentCalls(); 1.5408 +}; 1.5409 +RilObject.prototype[REQUEST_SWITCH_WAITING_OR_HOLDING_AND_ACTIVE] = function REQUEST_SWITCH_WAITING_OR_HOLDING_AND_ACTIVE(length, options) { 1.5410 + options.success = (options.rilRequestError === 0); 1.5411 + if (!options.success) { 1.5412 + options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError]; 1.5413 + this.sendChromeMessage(options); 1.5414 + return; 1.5415 + } 1.5416 + 1.5417 + this.sendChromeMessage(options); 1.5418 + this.getCurrentCalls(); 1.5419 +}; 1.5420 +RilObject.prototype[REQUEST_CONFERENCE] = function REQUEST_CONFERENCE(length, options) { 1.5421 + options.success = (options.rilRequestError === 0); 1.5422 + if (!options.success) { 1.5423 + this._hasConferenceRequest = false; 1.5424 + options.errorName = "addError"; 1.5425 + options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError]; 1.5426 + this.sendChromeMessage(options); 1.5427 + return; 1.5428 + } 1.5429 + 1.5430 + this.sendChromeMessage(options); 1.5431 +}; 1.5432 +RilObject.prototype[REQUEST_UDUB] = null; 1.5433 +RilObject.prototype[REQUEST_LAST_CALL_FAIL_CAUSE] = function REQUEST_LAST_CALL_FAIL_CAUSE(length, options) { 1.5434 + let Buf = this.context.Buf; 1.5435 + let num = length ? Buf.readInt32() : 0; 1.5436 + let failCause = num ? RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[Buf.readInt32()] : null; 1.5437 + if (options.callback) { 1.5438 + options.callback(failCause); 1.5439 + } 1.5440 +}; 1.5441 +RilObject.prototype[REQUEST_SIGNAL_STRENGTH] = function REQUEST_SIGNAL_STRENGTH(length, options) { 1.5442 + this._receivedNetworkInfo(NETWORK_INFO_SIGNAL); 1.5443 + 1.5444 + if (options.rilRequestError) { 1.5445 + return; 1.5446 + } 1.5447 + 1.5448 + let Buf = this.context.Buf; 1.5449 + let signal = { 1.5450 + gsmSignalStrength: Buf.readInt32(), 1.5451 + gsmBitErrorRate: Buf.readInt32(), 1.5452 + cdmaDBM: Buf.readInt32(), 1.5453 + cdmaECIO: Buf.readInt32(), 1.5454 + evdoDBM: Buf.readInt32(), 1.5455 + evdoECIO: Buf.readInt32(), 1.5456 + evdoSNR: Buf.readInt32() 1.5457 + }; 1.5458 + 1.5459 + if (!this.v5Legacy) { 1.5460 + signal.lteSignalStrength = Buf.readInt32(); 1.5461 + signal.lteRSRP = Buf.readInt32(); 1.5462 + signal.lteRSRQ = Buf.readInt32(); 1.5463 + signal.lteRSSNR = Buf.readInt32(); 1.5464 + signal.lteCQI = Buf.readInt32(); 1.5465 + } 1.5466 + 1.5467 + if (DEBUG) this.context.debug("signal strength: " + JSON.stringify(signal)); 1.5468 + 1.5469 + this._processSignalStrength(signal); 1.5470 +}; 1.5471 +RilObject.prototype[REQUEST_VOICE_REGISTRATION_STATE] = function REQUEST_VOICE_REGISTRATION_STATE(length, options) { 1.5472 + this._receivedNetworkInfo(NETWORK_INFO_VOICE_REGISTRATION_STATE); 1.5473 + 1.5474 + if (options.rilRequestError) { 1.5475 + return; 1.5476 + } 1.5477 + 1.5478 + let state = this.context.Buf.readStringList(); 1.5479 + if (DEBUG) this.context.debug("voice registration state: " + state); 1.5480 + 1.5481 + this._processVoiceRegistrationState(state); 1.5482 + 1.5483 + if (this.cachedDialRequest && 1.5484 + (this.voiceRegistrationState.emergencyCallsOnly || 1.5485 + this.voiceRegistrationState.connected) && 1.5486 + this.voiceRegistrationState.radioTech != NETWORK_CREG_TECH_UNKNOWN) { 1.5487 + // Radio is ready for making the cached emergency call. 1.5488 + this.cachedDialRequest.callback(); 1.5489 + this.cachedDialRequest = null; 1.5490 + } 1.5491 +}; 1.5492 +RilObject.prototype[REQUEST_DATA_REGISTRATION_STATE] = function REQUEST_DATA_REGISTRATION_STATE(length, options) { 1.5493 + this._receivedNetworkInfo(NETWORK_INFO_DATA_REGISTRATION_STATE); 1.5494 + 1.5495 + if (options.rilRequestError) { 1.5496 + return; 1.5497 + } 1.5498 + 1.5499 + let state = this.context.Buf.readStringList(); 1.5500 + this._processDataRegistrationState(state); 1.5501 +}; 1.5502 +RilObject.prototype[REQUEST_OPERATOR] = function REQUEST_OPERATOR(length, options) { 1.5503 + this._receivedNetworkInfo(NETWORK_INFO_OPERATOR); 1.5504 + 1.5505 + if (options.rilRequestError) { 1.5506 + return; 1.5507 + } 1.5508 + 1.5509 + let operatorData = this.context.Buf.readStringList(); 1.5510 + if (DEBUG) this.context.debug("Operator: " + operatorData); 1.5511 + this._processOperator(operatorData); 1.5512 +}; 1.5513 +RilObject.prototype[REQUEST_RADIO_POWER] = function REQUEST_RADIO_POWER(length, options) { 1.5514 + if (options.rilMessageType == null) { 1.5515 + // The request was made by ril_worker itself. 1.5516 + if (options.rilRequestError) { 1.5517 + if (this.cachedDialRequest && options.enabled) { 1.5518 + // Turning on radio fails. Notify the error of making an emergency call. 1.5519 + this.cachedDialRequest.onerror(GECKO_ERROR_RADIO_NOT_AVAILABLE); 1.5520 + this.cachedDialRequest = null; 1.5521 + } 1.5522 + } 1.5523 + return; 1.5524 + } 1.5525 + 1.5526 + options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError]; 1.5527 + this.sendChromeMessage(options); 1.5528 +}; 1.5529 +RilObject.prototype[REQUEST_DTMF] = null; 1.5530 +RilObject.prototype[REQUEST_SEND_SMS] = function REQUEST_SEND_SMS(length, options) { 1.5531 + this._processSmsSendResult(length, options); 1.5532 +}; 1.5533 +RilObject.prototype[REQUEST_SEND_SMS_EXPECT_MORE] = null; 1.5534 + 1.5535 +RilObject.prototype.readSetupDataCall_v5 = function readSetupDataCall_v5(options) { 1.5536 + if (!options) { 1.5537 + options = {}; 1.5538 + } 1.5539 + let [cid, ifname, addresses, dnses, gateways] = this.context.Buf.readStringList(); 1.5540 + options.cid = cid; 1.5541 + options.ifname = ifname; 1.5542 + options.addresses = addresses ? [addresses] : []; 1.5543 + options.dnses = dnses ? [dnses] : []; 1.5544 + options.gateways = gateways ? [gateways] : []; 1.5545 + options.active = DATACALL_ACTIVE_UNKNOWN; 1.5546 + options.state = GECKO_NETWORK_STATE_CONNECTING; 1.5547 + return options; 1.5548 +}; 1.5549 + 1.5550 +RilObject.prototype[REQUEST_SETUP_DATA_CALL] = function REQUEST_SETUP_DATA_CALL(length, options) { 1.5551 + if (options.rilRequestError) { 1.5552 + // On Data Call generic errors, we shall notify caller 1.5553 + this._sendDataCallError(options, options.rilRequestError); 1.5554 + return; 1.5555 + } 1.5556 + 1.5557 + if (this.v5Legacy) { 1.5558 + // Populate the `options` object with the data call information. That way 1.5559 + // we retain the APN and other info about how the data call was set up. 1.5560 + this.readSetupDataCall_v5(options); 1.5561 + this.currentDataCalls[options.cid] = options; 1.5562 + options.rilMessageType = "datacallstatechange"; 1.5563 + this.sendChromeMessage(options); 1.5564 + // Let's get the list of data calls to ensure we know whether it's active 1.5565 + // or not. 1.5566 + this.getDataCallList(); 1.5567 + return; 1.5568 + } 1.5569 + // Pass `options` along. That way we retain the APN and other info about 1.5570 + // how the data call was set up. 1.5571 + this[REQUEST_DATA_CALL_LIST](length, options); 1.5572 +}; 1.5573 +RilObject.prototype[REQUEST_SIM_IO] = function REQUEST_SIM_IO(length, options) { 1.5574 + let ICCIOHelper = this.context.ICCIOHelper; 1.5575 + if (!length) { 1.5576 + ICCIOHelper.processICCIOError(options); 1.5577 + return; 1.5578 + } 1.5579 + 1.5580 + // Don't need to read rilRequestError since we can know error status from 1.5581 + // sw1 and sw2. 1.5582 + let Buf = this.context.Buf; 1.5583 + options.sw1 = Buf.readInt32(); 1.5584 + options.sw2 = Buf.readInt32(); 1.5585 + if (options.sw1 != ICC_STATUS_NORMAL_ENDING) { 1.5586 + ICCIOHelper.processICCIOError(options); 1.5587 + return; 1.5588 + } 1.5589 + ICCIOHelper.processICCIO(options); 1.5590 +}; 1.5591 +RilObject.prototype[REQUEST_SEND_USSD] = function REQUEST_SEND_USSD(length, options) { 1.5592 + if (DEBUG) { 1.5593 + this.context.debug("REQUEST_SEND_USSD " + JSON.stringify(options)); 1.5594 + } 1.5595 + options.success = (this._ussdSession = options.rilRequestError === 0); 1.5596 + options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError]; 1.5597 + this.sendChromeMessage(options); 1.5598 +}; 1.5599 +RilObject.prototype[REQUEST_CANCEL_USSD] = function REQUEST_CANCEL_USSD(length, options) { 1.5600 + if (DEBUG) { 1.5601 + this.context.debug("REQUEST_CANCEL_USSD" + JSON.stringify(options)); 1.5602 + } 1.5603 + options.success = (options.rilRequestError === 0); 1.5604 + this._ussdSession = !options.success; 1.5605 + options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError]; 1.5606 + this.sendChromeMessage(options); 1.5607 +}; 1.5608 +RilObject.prototype[REQUEST_GET_CLIR] = function REQUEST_GET_CLIR(length, options) { 1.5609 + options.success = (options.rilRequestError === 0); 1.5610 + if (!options.success) { 1.5611 + options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError]; 1.5612 + this.sendChromeMessage(options); 1.5613 + return; 1.5614 + } 1.5615 + 1.5616 + let Buf = this.context.Buf; 1.5617 + let bufLength = Buf.readInt32(); 1.5618 + if (!bufLength || bufLength < 2) { 1.5619 + options.success = false; 1.5620 + options.errorMsg = GECKO_ERROR_GENERIC_FAILURE; 1.5621 + this.sendChromeMessage(options); 1.5622 + return; 1.5623 + } 1.5624 + 1.5625 + options.n = Buf.readInt32(); // Will be TS 27.007 +CLIR parameter 'n'. 1.5626 + options.m = Buf.readInt32(); // Will be TS 27.007 +CLIR parameter 'm'. 1.5627 + 1.5628 + if (options.rilMessageType === "sendMMI") { 1.5629 + // TS 27.007 +CLIR parameter 'm'. 1.5630 + switch (options.m) { 1.5631 + // CLIR not provisioned. 1.5632 + case 0: 1.5633 + options.statusMessage = MMI_SM_KS_SERVICE_NOT_PROVISIONED; 1.5634 + break; 1.5635 + // CLIR provisioned in permanent mode. 1.5636 + case 1: 1.5637 + options.statusMessage = MMI_SM_KS_CLIR_PERMANENT; 1.5638 + break; 1.5639 + // Unknown (e.g. no network, etc.). 1.5640 + case 2: 1.5641 + options.success = false; 1.5642 + options.errorMsg = MMI_ERROR_KS_ERROR; 1.5643 + break; 1.5644 + // CLIR temporary mode presentation restricted. 1.5645 + case 3: 1.5646 + // TS 27.007 +CLIR parameter 'n'. 1.5647 + switch (options.n) { 1.5648 + // Default. 1.5649 + case 0: 1.5650 + // CLIR invocation. 1.5651 + case 1: 1.5652 + options.statusMessage = MMI_SM_KS_CLIR_DEFAULT_ON_NEXT_CALL_ON; 1.5653 + break; 1.5654 + // CLIR suppression. 1.5655 + case 2: 1.5656 + options.statusMessage = MMI_SM_KS_CLIR_DEFAULT_ON_NEXT_CALL_OFF; 1.5657 + break; 1.5658 + default: 1.5659 + options.success = false; 1.5660 + options.errorMsg = GECKO_ERROR_GENERIC_FAILURE; 1.5661 + break; 1.5662 + } 1.5663 + break; 1.5664 + // CLIR temporary mode presentation allowed. 1.5665 + case 4: 1.5666 + // TS 27.007 +CLIR parameter 'n'. 1.5667 + switch (options.n) { 1.5668 + // Default. 1.5669 + case 0: 1.5670 + // CLIR suppression. 1.5671 + case 2: 1.5672 + options.statusMessage = MMI_SM_KS_CLIR_DEFAULT_OFF_NEXT_CALL_OFF; 1.5673 + break; 1.5674 + // CLIR invocation. 1.5675 + case 1: 1.5676 + options.statusMessage = MMI_SM_KS_CLIR_DEFAULT_OFF_NEXT_CALL_ON; 1.5677 + break; 1.5678 + default: 1.5679 + options.success = false; 1.5680 + options.errorMsg = GECKO_ERROR_GENERIC_FAILURE; 1.5681 + break; 1.5682 + } 1.5683 + break; 1.5684 + default: 1.5685 + options.success = false; 1.5686 + options.errorMsg = GECKO_ERROR_GENERIC_FAILURE; 1.5687 + break; 1.5688 + } 1.5689 + } 1.5690 + 1.5691 + this.sendChromeMessage(options); 1.5692 +}; 1.5693 +RilObject.prototype[REQUEST_SET_CLIR] = function REQUEST_SET_CLIR(length, options) { 1.5694 + if (options.rilMessageType == null) { 1.5695 + // The request was made by ril_worker itself automatically. Don't report. 1.5696 + return; 1.5697 + } 1.5698 + options.success = (options.rilRequestError === 0); 1.5699 + if (!options.success) { 1.5700 + options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError]; 1.5701 + } else if (options.rilMessageType === "sendMMI") { 1.5702 + switch (options.procedure) { 1.5703 + case MMI_PROCEDURE_ACTIVATION: 1.5704 + options.statusMessage = MMI_SM_KS_SERVICE_ENABLED; 1.5705 + break; 1.5706 + case MMI_PROCEDURE_DEACTIVATION: 1.5707 + options.statusMessage = MMI_SM_KS_SERVICE_DISABLED; 1.5708 + break; 1.5709 + } 1.5710 + } 1.5711 + this.sendChromeMessage(options); 1.5712 +}; 1.5713 + 1.5714 +RilObject.prototype[REQUEST_QUERY_CALL_FORWARD_STATUS] = 1.5715 + function REQUEST_QUERY_CALL_FORWARD_STATUS(length, options) { 1.5716 + options.success = (options.rilRequestError === 0); 1.5717 + if (!options.success) { 1.5718 + options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError]; 1.5719 + this.sendChromeMessage(options); 1.5720 + return; 1.5721 + } 1.5722 + 1.5723 + let Buf = this.context.Buf; 1.5724 + let rulesLength = 0; 1.5725 + if (length) { 1.5726 + rulesLength = Buf.readInt32(); 1.5727 + } 1.5728 + if (!rulesLength) { 1.5729 + options.success = false; 1.5730 + options.errorMsg = GECKO_ERROR_GENERIC_FAILURE; 1.5731 + this.sendChromeMessage(options); 1.5732 + return; 1.5733 + } 1.5734 + let rules = new Array(rulesLength); 1.5735 + for (let i = 0; i < rulesLength; i++) { 1.5736 + let rule = {}; 1.5737 + rule.active = Buf.readInt32() == 1; // CALL_FORWARD_STATUS_* 1.5738 + rule.reason = Buf.readInt32(); // CALL_FORWARD_REASON_* 1.5739 + rule.serviceClass = Buf.readInt32(); 1.5740 + rule.toa = Buf.readInt32(); 1.5741 + rule.number = Buf.readString(); 1.5742 + rule.timeSeconds = Buf.readInt32(); 1.5743 + rules[i] = rule; 1.5744 + } 1.5745 + options.rules = rules; 1.5746 + if (options.rilMessageType === "sendMMI") { 1.5747 + options.statusMessage = MMI_SM_KS_SERVICE_INTERROGATED; 1.5748 + // MMI query call forwarding options request returns a set of rules that 1.5749 + // will be exposed in the form of an array of nsIDOMMozMobileCFInfo 1.5750 + // instances. 1.5751 + options.additionalInformation = rules; 1.5752 + } 1.5753 + this.sendChromeMessage(options); 1.5754 +}; 1.5755 +RilObject.prototype[REQUEST_SET_CALL_FORWARD] = 1.5756 + function REQUEST_SET_CALL_FORWARD(length, options) { 1.5757 + options.success = (options.rilRequestError === 0); 1.5758 + if (!options.success) { 1.5759 + options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError]; 1.5760 + } else if (options.rilMessageType === "sendMMI") { 1.5761 + switch (options.action) { 1.5762 + case CALL_FORWARD_ACTION_ENABLE: 1.5763 + options.statusMessage = MMI_SM_KS_SERVICE_ENABLED; 1.5764 + break; 1.5765 + case CALL_FORWARD_ACTION_DISABLE: 1.5766 + options.statusMessage = MMI_SM_KS_SERVICE_DISABLED; 1.5767 + break; 1.5768 + case CALL_FORWARD_ACTION_REGISTRATION: 1.5769 + options.statusMessage = MMI_SM_KS_SERVICE_REGISTERED; 1.5770 + break; 1.5771 + case CALL_FORWARD_ACTION_ERASURE: 1.5772 + options.statusMessage = MMI_SM_KS_SERVICE_ERASED; 1.5773 + break; 1.5774 + } 1.5775 + } 1.5776 + this.sendChromeMessage(options); 1.5777 +}; 1.5778 +RilObject.prototype[REQUEST_QUERY_CALL_WAITING] = 1.5779 + function REQUEST_QUERY_CALL_WAITING(length, options) { 1.5780 + options.success = (options.rilRequestError === 0); 1.5781 + if (!options.success) { 1.5782 + options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError]; 1.5783 + this.sendChromeMessage(options); 1.5784 + return; 1.5785 + } 1.5786 + 1.5787 + if (options.callback) { 1.5788 + options.callback.call(this, options); 1.5789 + return; 1.5790 + } 1.5791 + 1.5792 + let Buf = this.context.Buf; 1.5793 + options.length = Buf.readInt32(); 1.5794 + options.enabled = ((Buf.readInt32() == 1) && 1.5795 + ((Buf.readInt32() & ICC_SERVICE_CLASS_VOICE) == 0x01)); 1.5796 + this.sendChromeMessage(options); 1.5797 +}; 1.5798 + 1.5799 +RilObject.prototype[REQUEST_SET_CALL_WAITING] = function REQUEST_SET_CALL_WAITING(length, options) { 1.5800 + options.success = (options.rilRequestError === 0); 1.5801 + if (!options.success) { 1.5802 + options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError]; 1.5803 + this.sendChromeMessage(options); 1.5804 + return; 1.5805 + } 1.5806 + 1.5807 + if (options.callback) { 1.5808 + options.callback.call(this, options); 1.5809 + return; 1.5810 + } 1.5811 + 1.5812 + this.sendChromeMessage(options); 1.5813 +}; 1.5814 +RilObject.prototype[REQUEST_SMS_ACKNOWLEDGE] = null; 1.5815 +RilObject.prototype[REQUEST_GET_IMEI] = function REQUEST_GET_IMEI(length, options) { 1.5816 + this.IMEI = this.context.Buf.readString(); 1.5817 + let rilMessageType = options.rilMessageType; 1.5818 + // So far we only send the IMEI back to chrome if it was requested via MMI. 1.5819 + if (rilMessageType !== "sendMMI") { 1.5820 + return; 1.5821 + } 1.5822 + 1.5823 + options.success = (options.rilRequestError === 0); 1.5824 + options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError]; 1.5825 + if ((!options.success || this.IMEI == null) && !options.errorMsg) { 1.5826 + options.errorMsg = GECKO_ERROR_GENERIC_FAILURE; 1.5827 + } 1.5828 + options.statusMessage = this.IMEI; 1.5829 + this.sendChromeMessage(options); 1.5830 +}; 1.5831 +RilObject.prototype[REQUEST_GET_IMEISV] = function REQUEST_GET_IMEISV(length, options) { 1.5832 + if (options.rilRequestError) { 1.5833 + return; 1.5834 + } 1.5835 + 1.5836 + this.IMEISV = this.context.Buf.readString(); 1.5837 +}; 1.5838 +RilObject.prototype[REQUEST_ANSWER] = null; 1.5839 +RilObject.prototype[REQUEST_DEACTIVATE_DATA_CALL] = function REQUEST_DEACTIVATE_DATA_CALL(length, options) { 1.5840 + if (options.rilRequestError) { 1.5841 + return; 1.5842 + } 1.5843 + 1.5844 + let datacall = this.currentDataCalls[options.cid]; 1.5845 + delete this.currentDataCalls[options.cid]; 1.5846 + datacall.state = GECKO_NETWORK_STATE_UNKNOWN; 1.5847 + datacall.rilMessageType = "datacallstatechange"; 1.5848 + this.sendChromeMessage(datacall); 1.5849 +}; 1.5850 +RilObject.prototype[REQUEST_QUERY_FACILITY_LOCK] = function REQUEST_QUERY_FACILITY_LOCK(length, options) { 1.5851 + options.success = (options.rilRequestError === 0); 1.5852 + if (!options.success) { 1.5853 + options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError]; 1.5854 + } 1.5855 + 1.5856 + let services; 1.5857 + if (length) { 1.5858 + // Buf.readInt32List()[0] for Call Barring is a bit vector of services. 1.5859 + services = this.context.Buf.readInt32List()[0]; 1.5860 + } else { 1.5861 + options.success = false; 1.5862 + options.errorMsg = GECKO_ERROR_GENERIC_FAILURE; 1.5863 + this.sendChromeMessage(options); 1.5864 + return; 1.5865 + } 1.5866 + 1.5867 + options.enabled = services === 0 ? false : true; 1.5868 + 1.5869 + if (options.success && (options.rilMessageType === "sendMMI")) { 1.5870 + if (!options.enabled) { 1.5871 + options.statusMessage = MMI_SM_KS_SERVICE_DISABLED; 1.5872 + } else { 1.5873 + options.statusMessage = MMI_SM_KS_SERVICE_ENABLED_FOR; 1.5874 + let serviceClass = []; 1.5875 + for (let serviceClassMask = 1; 1.5876 + serviceClassMask <= ICC_SERVICE_CLASS_MAX; 1.5877 + serviceClassMask <<= 1) { 1.5878 + if ((serviceClassMask & services) !== 0) { 1.5879 + serviceClass.push(MMI_KS_SERVICE_CLASS_MAPPING[serviceClassMask]); 1.5880 + } 1.5881 + } 1.5882 + 1.5883 + options.additionalInformation = serviceClass; 1.5884 + } 1.5885 + } 1.5886 + this.sendChromeMessage(options); 1.5887 +}; 1.5888 +RilObject.prototype[REQUEST_SET_FACILITY_LOCK] = function REQUEST_SET_FACILITY_LOCK(length, options) { 1.5889 + options.success = (options.rilRequestError === 0); 1.5890 + if (!options.success) { 1.5891 + options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError]; 1.5892 + } 1.5893 + 1.5894 + options.retryCount = length ? this.context.Buf.readInt32List()[0] : -1; 1.5895 + 1.5896 + if (options.success && (options.rilMessageType === "sendMMI")) { 1.5897 + switch (options.procedure) { 1.5898 + case MMI_PROCEDURE_ACTIVATION: 1.5899 + options.statusMessage = MMI_SM_KS_SERVICE_ENABLED; 1.5900 + break; 1.5901 + case MMI_PROCEDURE_DEACTIVATION: 1.5902 + options.statusMessage = MMI_SM_KS_SERVICE_DISABLED; 1.5903 + break; 1.5904 + } 1.5905 + } 1.5906 + this.sendChromeMessage(options); 1.5907 +}; 1.5908 +RilObject.prototype[REQUEST_CHANGE_BARRING_PASSWORD] = 1.5909 + function REQUEST_CHANGE_BARRING_PASSWORD(length, options) { 1.5910 + if (options.rilRequestError) { 1.5911 + options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError]; 1.5912 + } 1.5913 + this.sendChromeMessage(options); 1.5914 +}; 1.5915 +RilObject.prototype[REQUEST_SIM_OPEN_CHANNEL] = function REQUEST_SIM_OPEN_CHANNEL(length, options) { 1.5916 + if (options.rilRequestError) { 1.5917 + options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError]; 1.5918 + this.sendChromeMessage(options); 1.5919 + return; 1.5920 + } 1.5921 + 1.5922 + options.channel = this.context.Buf.readInt32(); 1.5923 + if (DEBUG) { 1.5924 + this.context.debug("Setting channel number in options: " + options.channel); 1.5925 + } 1.5926 + this.sendChromeMessage(options); 1.5927 +}; 1.5928 +RilObject.prototype[REQUEST_SIM_CLOSE_CHANNEL] = function REQUEST_SIM_CLOSE_CHANNEL(length, options) { 1.5929 + if (options.rilRequestError) { 1.5930 + options.error = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError]; 1.5931 + this.sendChromeMessage(options); 1.5932 + return; 1.5933 + } 1.5934 + 1.5935 + // No return value 1.5936 + this.sendChromeMessage(options); 1.5937 +}; 1.5938 +RilObject.prototype[REQUEST_SIM_ACCESS_CHANNEL] = function REQUEST_SIM_ACCESS_CHANNEL(length, options) { 1.5939 + if (options.rilRequestError) { 1.5940 + options.error = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError]; 1.5941 + this.sendChromeMessage(options); 1.5942 + } 1.5943 + 1.5944 + let Buf = this.context.Buf; 1.5945 + options.sw1 = Buf.readInt32(); 1.5946 + options.sw2 = Buf.readInt32(); 1.5947 + options.simResponse = Buf.readString(); 1.5948 + if (DEBUG) { 1.5949 + this.context.debug("Setting return values for RIL[REQUEST_SIM_ACCESS_CHANNEL]: [" + 1.5950 + options.sw1 + "," + 1.5951 + options.sw2 + ", " + 1.5952 + options.simResponse + "]"); 1.5953 + } 1.5954 + this.sendChromeMessage(options); 1.5955 +}; 1.5956 +RilObject.prototype[REQUEST_QUERY_NETWORK_SELECTION_MODE] = function REQUEST_QUERY_NETWORK_SELECTION_MODE(length, options) { 1.5957 + this._receivedNetworkInfo(NETWORK_INFO_NETWORK_SELECTION_MODE); 1.5958 + 1.5959 + if (options.rilRequestError) { 1.5960 + return; 1.5961 + } 1.5962 + 1.5963 + let mode = this.context.Buf.readInt32List(); 1.5964 + let selectionMode; 1.5965 + 1.5966 + switch (mode[0]) { 1.5967 + case NETWORK_SELECTION_MODE_AUTOMATIC: 1.5968 + selectionMode = GECKO_NETWORK_SELECTION_AUTOMATIC; 1.5969 + break; 1.5970 + case NETWORK_SELECTION_MODE_MANUAL: 1.5971 + selectionMode = GECKO_NETWORK_SELECTION_MANUAL; 1.5972 + break; 1.5973 + default: 1.5974 + selectionMode = GECKO_NETWORK_SELECTION_UNKNOWN; 1.5975 + break; 1.5976 + } 1.5977 + 1.5978 + if (this.networkSelectionMode != selectionMode) { 1.5979 + this.networkSelectionMode = options.mode = selectionMode; 1.5980 + options.rilMessageType = "networkselectionmodechange"; 1.5981 + this._sendNetworkInfoMessage(NETWORK_INFO_NETWORK_SELECTION_MODE, options); 1.5982 + } 1.5983 +}; 1.5984 +RilObject.prototype[REQUEST_SET_NETWORK_SELECTION_AUTOMATIC] = function REQUEST_SET_NETWORK_SELECTION_AUTOMATIC(length, options) { 1.5985 + if (options.rilRequestError) { 1.5986 + options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError]; 1.5987 + } 1.5988 + 1.5989 + this.sendChromeMessage(options); 1.5990 +}; 1.5991 +RilObject.prototype[REQUEST_SET_NETWORK_SELECTION_MANUAL] = function REQUEST_SET_NETWORK_SELECTION_MANUAL(length, options) { 1.5992 + if (options.rilRequestError) { 1.5993 + options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError]; 1.5994 + } 1.5995 + 1.5996 + this.sendChromeMessage(options); 1.5997 +}; 1.5998 +RilObject.prototype[REQUEST_QUERY_AVAILABLE_NETWORKS] = function REQUEST_QUERY_AVAILABLE_NETWORKS(length, options) { 1.5999 + if (options.rilRequestError) { 1.6000 + options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError]; 1.6001 + } else { 1.6002 + options.networks = this._processNetworks(); 1.6003 + } 1.6004 + this.sendChromeMessage(options); 1.6005 +}; 1.6006 +RilObject.prototype[REQUEST_DTMF_START] = null; 1.6007 +RilObject.prototype[REQUEST_DTMF_STOP] = null; 1.6008 +RilObject.prototype[REQUEST_BASEBAND_VERSION] = function REQUEST_BASEBAND_VERSION(length, options) { 1.6009 + if (options.rilRequestError) { 1.6010 + return; 1.6011 + } 1.6012 + 1.6013 + this.basebandVersion = this.context.Buf.readString(); 1.6014 + if (DEBUG) this.context.debug("Baseband version: " + this.basebandVersion); 1.6015 +}; 1.6016 +RilObject.prototype[REQUEST_SEPARATE_CONNECTION] = function REQUEST_SEPARATE_CONNECTION(length, options) { 1.6017 + options.success = (options.rilRequestError === 0); 1.6018 + if (!options.success) { 1.6019 + options.errorName = "removeError"; 1.6020 + options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError]; 1.6021 + this.sendChromeMessage(options); 1.6022 + return; 1.6023 + } 1.6024 + 1.6025 + this.sendChromeMessage(options); 1.6026 +}; 1.6027 +RilObject.prototype[REQUEST_SET_MUTE] = null; 1.6028 +RilObject.prototype[REQUEST_GET_MUTE] = null; 1.6029 +RilObject.prototype[REQUEST_QUERY_CLIP] = function REQUEST_QUERY_CLIP(length, options) { 1.6030 + options.success = (options.rilRequestError === 0); 1.6031 + if (!options.success) { 1.6032 + options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError]; 1.6033 + this.sendChromeMessage(options); 1.6034 + return; 1.6035 + } 1.6036 + 1.6037 + let Buf = this.context.Buf; 1.6038 + let bufLength = Buf.readInt32(); 1.6039 + if (!bufLength) { 1.6040 + options.success = false; 1.6041 + options.errorMsg = GECKO_ERROR_GENERIC_FAILURE; 1.6042 + this.sendChromeMessage(options); 1.6043 + return; 1.6044 + } 1.6045 + 1.6046 + // options.provisioned informs about the called party receives the calling 1.6047 + // party's address information: 1.6048 + // 0 for CLIP not provisioned 1.6049 + // 1 for CLIP provisioned 1.6050 + // 2 for unknown 1.6051 + options.provisioned = Buf.readInt32(); 1.6052 + if (options.rilMessageType === "sendMMI") { 1.6053 + switch (options.provisioned) { 1.6054 + case 0: 1.6055 + options.statusMessage = MMI_SM_KS_SERVICE_DISABLED; 1.6056 + break; 1.6057 + case 1: 1.6058 + options.statusMessage = MMI_SM_KS_SERVICE_ENABLED; 1.6059 + break; 1.6060 + default: 1.6061 + options.success = false; 1.6062 + options.errorMsg = MMI_ERROR_KS_ERROR; 1.6063 + break; 1.6064 + } 1.6065 + } 1.6066 + this.sendChromeMessage(options); 1.6067 +}; 1.6068 +RilObject.prototype[REQUEST_LAST_DATA_CALL_FAIL_CAUSE] = null; 1.6069 + 1.6070 +/** 1.6071 + * V3: 1.6072 + * # address - A space-delimited list of addresses. 1.6073 + * 1.6074 + * V4: 1.6075 + * # address - An address. 1.6076 + * 1.6077 + * V5: 1.6078 + * # addresses - A space-delimited list of addresses. 1.6079 + * # dnses - A space-delimited list of DNS server addresses. 1.6080 + * 1.6081 + * V6: 1.6082 + * # addresses - A space-delimited list of addresses with optional "/" prefix 1.6083 + * length. 1.6084 + * # dnses - A space-delimited list of DNS server addresses. 1.6085 + * # gateways - A space-delimited list of default gateway addresses. 1.6086 + */ 1.6087 +RilObject.prototype.readDataCall_v5 = function(options) { 1.6088 + if (!options) { 1.6089 + options = {}; 1.6090 + } 1.6091 + let Buf = this.context.Buf; 1.6092 + options.cid = Buf.readInt32().toString(); 1.6093 + options.active = Buf.readInt32(); // DATACALL_ACTIVE_* 1.6094 + options.type = Buf.readString(); 1.6095 + options.apn = Buf.readString(); 1.6096 + let addresses = Buf.readString(); 1.6097 + let dnses = Buf.readString(); 1.6098 + options.addresses = addresses ? addresses.split(" ") : []; 1.6099 + options.dnses = dnses ? dnses.split(" ") : []; 1.6100 + options.gateways = []; 1.6101 + return options; 1.6102 +}; 1.6103 + 1.6104 +RilObject.prototype.readDataCall_v6 = function(options) { 1.6105 + if (!options) { 1.6106 + options = {}; 1.6107 + } 1.6108 + let Buf = this.context.Buf; 1.6109 + options.status = Buf.readInt32(); // DATACALL_FAIL_* 1.6110 + options.suggestedRetryTime = Buf.readInt32(); 1.6111 + options.cid = Buf.readInt32().toString(); 1.6112 + options.active = Buf.readInt32(); // DATACALL_ACTIVE_* 1.6113 + options.type = Buf.readString(); 1.6114 + options.ifname = Buf.readString(); 1.6115 + let addresses = Buf.readString(); 1.6116 + let dnses = Buf.readString(); 1.6117 + let gateways = Buf.readString(); 1.6118 + options.addresses = addresses ? addresses.split(" ") : []; 1.6119 + options.dnses = dnses ? dnses.split(" ") : []; 1.6120 + options.gateways = gateways ? gateways.split(" ") : []; 1.6121 + return options; 1.6122 +}; 1.6123 + 1.6124 +RilObject.prototype[REQUEST_DATA_CALL_LIST] = function REQUEST_DATA_CALL_LIST(length, options) { 1.6125 + if (options.rilRequestError) { 1.6126 + return; 1.6127 + } 1.6128 + 1.6129 + if (!length) { 1.6130 + this._processDataCallList(null); 1.6131 + return; 1.6132 + } 1.6133 + 1.6134 + let Buf = this.context.Buf; 1.6135 + let version = 0; 1.6136 + if (!this.v5Legacy) { 1.6137 + version = Buf.readInt32(); 1.6138 + } 1.6139 + let num = Buf.readInt32(); 1.6140 + let datacalls = {}; 1.6141 + for (let i = 0; i < num; i++) { 1.6142 + let datacall; 1.6143 + if (version < 6) { 1.6144 + datacall = this.readDataCall_v5(); 1.6145 + } else { 1.6146 + datacall = this.readDataCall_v6(); 1.6147 + } 1.6148 + datacalls[datacall.cid] = datacall; 1.6149 + } 1.6150 + 1.6151 + let newDataCallOptions = null; 1.6152 + if (options.rilRequestType == REQUEST_SETUP_DATA_CALL) { 1.6153 + newDataCallOptions = options; 1.6154 + } 1.6155 + this._processDataCallList(datacalls, newDataCallOptions); 1.6156 +}; 1.6157 +RilObject.prototype[REQUEST_RESET_RADIO] = null; 1.6158 +RilObject.prototype[REQUEST_OEM_HOOK_RAW] = null; 1.6159 +RilObject.prototype[REQUEST_OEM_HOOK_STRINGS] = null; 1.6160 +RilObject.prototype[REQUEST_SCREEN_STATE] = null; 1.6161 +RilObject.prototype[REQUEST_SET_SUPP_SVC_NOTIFICATION] = null; 1.6162 +RilObject.prototype[REQUEST_WRITE_SMS_TO_SIM] = function REQUEST_WRITE_SMS_TO_SIM(length, options) { 1.6163 + if (options.rilRequestError) { 1.6164 + // `The MS shall return a "protocol error, unspecified" error message if 1.6165 + // the short message cannot be stored in the (U)SIM, and there is other 1.6166 + // message storage available at the MS` ~ 3GPP TS 23.038 section 4. Here 1.6167 + // we assume we always have indexed db as another storage. 1.6168 + this.acknowledgeGsmSms(false, PDU_FCS_PROTOCOL_ERROR); 1.6169 + } else { 1.6170 + this.acknowledgeGsmSms(true, PDU_FCS_OK); 1.6171 + } 1.6172 +}; 1.6173 +RilObject.prototype[REQUEST_DELETE_SMS_ON_SIM] = null; 1.6174 +RilObject.prototype[REQUEST_SET_BAND_MODE] = null; 1.6175 +RilObject.prototype[REQUEST_QUERY_AVAILABLE_BAND_MODE] = null; 1.6176 +RilObject.prototype[REQUEST_STK_GET_PROFILE] = null; 1.6177 +RilObject.prototype[REQUEST_STK_SET_PROFILE] = null; 1.6178 +RilObject.prototype[REQUEST_STK_SEND_ENVELOPE_COMMAND] = null; 1.6179 +RilObject.prototype[REQUEST_STK_SEND_TERMINAL_RESPONSE] = null; 1.6180 +RilObject.prototype[REQUEST_STK_HANDLE_CALL_SETUP_REQUESTED_FROM_SIM] = null; 1.6181 +RilObject.prototype[REQUEST_EXPLICIT_CALL_TRANSFER] = null; 1.6182 +RilObject.prototype[REQUEST_SET_PREFERRED_NETWORK_TYPE] = function REQUEST_SET_PREFERRED_NETWORK_TYPE(length, options) { 1.6183 + if (options.networkType == null) { 1.6184 + // The request was made by ril_worker itself automatically. Don't report. 1.6185 + return; 1.6186 + } 1.6187 + 1.6188 + if (options.rilRequestError) { 1.6189 + options.success = false; 1.6190 + options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError]; 1.6191 + } else { 1.6192 + options.success = true; 1.6193 + } 1.6194 + this.sendChromeMessage(options); 1.6195 +}; 1.6196 +RilObject.prototype[REQUEST_GET_PREFERRED_NETWORK_TYPE] = function REQUEST_GET_PREFERRED_NETWORK_TYPE(length, options) { 1.6197 + if (options.rilRequestError) { 1.6198 + options.success = false; 1.6199 + options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError]; 1.6200 + this.sendChromeMessage(options); 1.6201 + return; 1.6202 + } 1.6203 + 1.6204 + let results = this.context.Buf.readInt32List(); 1.6205 + options.networkType = this.preferredNetworkType = results[0]; 1.6206 + options.success = true; 1.6207 + 1.6208 + this.sendChromeMessage(options); 1.6209 +}; 1.6210 +RilObject.prototype[REQUEST_GET_NEIGHBORING_CELL_IDS] = null; 1.6211 +RilObject.prototype[REQUEST_SET_LOCATION_UPDATES] = null; 1.6212 +RilObject.prototype[REQUEST_CDMA_SET_SUBSCRIPTION_SOURCE] = null; 1.6213 +RilObject.prototype[REQUEST_CDMA_SET_ROAMING_PREFERENCE] = function REQUEST_CDMA_SET_ROAMING_PREFERENCE(length, options) { 1.6214 + if (options.rilRequestError) { 1.6215 + options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError]; 1.6216 + } 1.6217 + this.sendChromeMessage(options); 1.6218 +}; 1.6219 +RilObject.prototype[REQUEST_CDMA_QUERY_ROAMING_PREFERENCE] = function REQUEST_CDMA_QUERY_ROAMING_PREFERENCE(length, options) { 1.6220 + if (options.rilRequestError) { 1.6221 + options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError]; 1.6222 + } else { 1.6223 + let mode = this.context.Buf.readInt32List(); 1.6224 + options.mode = CDMA_ROAMING_PREFERENCE_TO_GECKO[mode[0]]; 1.6225 + } 1.6226 + this.sendChromeMessage(options); 1.6227 +}; 1.6228 +RilObject.prototype[REQUEST_SET_TTY_MODE] = null; 1.6229 +RilObject.prototype[REQUEST_QUERY_TTY_MODE] = null; 1.6230 +RilObject.prototype[REQUEST_CDMA_SET_PREFERRED_VOICE_PRIVACY_MODE] = function REQUEST_CDMA_SET_PREFERRED_VOICE_PRIVACY_MODE(length, options) { 1.6231 + if (options.rilRequestError) { 1.6232 + options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError]; 1.6233 + this.sendChromeMessage(options); 1.6234 + return; 1.6235 + } 1.6236 + 1.6237 + this.sendChromeMessage(options); 1.6238 +}; 1.6239 +RilObject.prototype[REQUEST_CDMA_QUERY_PREFERRED_VOICE_PRIVACY_MODE] = function REQUEST_CDMA_QUERY_PREFERRED_VOICE_PRIVACY_MODE(length, options) { 1.6240 + if (options.rilRequestError) { 1.6241 + options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError]; 1.6242 + this.sendChromeMessage(options); 1.6243 + return; 1.6244 + } 1.6245 + 1.6246 + let enabled = this.context.Buf.readInt32List(); 1.6247 + options.enabled = enabled[0] ? true : false; 1.6248 + this.sendChromeMessage(options); 1.6249 +}; 1.6250 +RilObject.prototype[REQUEST_CDMA_FLASH] = function REQUEST_CDMA_FLASH(length, options) { 1.6251 + options.success = (options.rilRequestError === 0); 1.6252 + if (!options.success) { 1.6253 + if (options.rilMessageType === "conferenceCall") { 1.6254 + options.errorName = "addError"; 1.6255 + } else if (options.rilMessageType === "separateCall") { 1.6256 + options.errorName = "removeError"; 1.6257 + } 1.6258 + options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError]; 1.6259 + } 1.6260 + 1.6261 + this.sendChromeMessage(options); 1.6262 +}; 1.6263 +RilObject.prototype[REQUEST_CDMA_BURST_DTMF] = null; 1.6264 +RilObject.prototype[REQUEST_CDMA_VALIDATE_AND_WRITE_AKEY] = null; 1.6265 +RilObject.prototype[REQUEST_CDMA_SEND_SMS] = function REQUEST_CDMA_SEND_SMS(length, options) { 1.6266 + this._processSmsSendResult(length, options); 1.6267 +}; 1.6268 +RilObject.prototype[REQUEST_CDMA_SMS_ACKNOWLEDGE] = null; 1.6269 +RilObject.prototype[REQUEST_GSM_GET_BROADCAST_SMS_CONFIG] = null; 1.6270 +RilObject.prototype[REQUEST_GSM_SET_BROADCAST_SMS_CONFIG] = function REQUEST_GSM_SET_BROADCAST_SMS_CONFIG(length, options) { 1.6271 + if (options.rilRequestError == ERROR_SUCCESS) { 1.6272 + this.setSmsBroadcastActivation(true); 1.6273 + } 1.6274 +}; 1.6275 +RilObject.prototype[REQUEST_GSM_SMS_BROADCAST_ACTIVATION] = null; 1.6276 +RilObject.prototype[REQUEST_CDMA_GET_BROADCAST_SMS_CONFIG] = null; 1.6277 +RilObject.prototype[REQUEST_CDMA_SET_BROADCAST_SMS_CONFIG] = null; 1.6278 +RilObject.prototype[REQUEST_CDMA_SMS_BROADCAST_ACTIVATION] = null; 1.6279 +RilObject.prototype[REQUEST_CDMA_SUBSCRIPTION] = function REQUEST_CDMA_SUBSCRIPTION(length, options) { 1.6280 + if (options.rilRequestError) { 1.6281 + return; 1.6282 + } 1.6283 + 1.6284 + let result = this.context.Buf.readStringList(); 1.6285 + 1.6286 + this.iccInfo.mdn = result[0]; 1.6287 + // The result[1] is Home SID. (Already be handled in readCDMAHome()) 1.6288 + // The result[2] is Home NID. (Already be handled in readCDMAHome()) 1.6289 + // The result[3] is MIN. 1.6290 + this.iccInfo.prlVersion = parseInt(result[4], 10); 1.6291 + 1.6292 + this.context.ICCUtilsHelper.handleICCInfoChange(); 1.6293 +}; 1.6294 +RilObject.prototype[REQUEST_CDMA_WRITE_SMS_TO_RUIM] = null; 1.6295 +RilObject.prototype[REQUEST_CDMA_DELETE_SMS_ON_RUIM] = null; 1.6296 +RilObject.prototype[REQUEST_DEVICE_IDENTITY] = function REQUEST_DEVICE_IDENTITY(length, options) { 1.6297 + if (options.rilRequestError) { 1.6298 + return; 1.6299 + } 1.6300 + 1.6301 + let result = this.context.Buf.readStringList(); 1.6302 + 1.6303 + // The result[0] is for IMEI. (Already be handled in REQUEST_GET_IMEI) 1.6304 + // The result[1] is for IMEISV. (Already be handled in REQUEST_GET_IMEISV) 1.6305 + // They are both ignored. 1.6306 + this.ESN = result[2]; 1.6307 + this.MEID = result[3]; 1.6308 +}; 1.6309 +RilObject.prototype[REQUEST_EXIT_EMERGENCY_CALLBACK_MODE] = function REQUEST_EXIT_EMERGENCY_CALLBACK_MODE(length, options) { 1.6310 + if (options.internal) { 1.6311 + return; 1.6312 + } 1.6313 + 1.6314 + options.success = (options.rilRequestError === 0); 1.6315 + if (!options.success) { 1.6316 + options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError]; 1.6317 + } 1.6318 + this.sendChromeMessage(options); 1.6319 +}; 1.6320 +RilObject.prototype[REQUEST_GET_SMSC_ADDRESS] = function REQUEST_GET_SMSC_ADDRESS(length, options) { 1.6321 + this.SMSC = options.rilRequestError ? null : this.context.Buf.readString(); 1.6322 + 1.6323 + if (!options.rilMessageType || options.rilMessageType !== "getSmscAddress") { 1.6324 + return; 1.6325 + } 1.6326 + 1.6327 + options.smscAddress = this.SMSC; 1.6328 + options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError]; 1.6329 + this.sendChromeMessage(options); 1.6330 +}; 1.6331 +RilObject.prototype[REQUEST_SET_SMSC_ADDRESS] = null; 1.6332 +RilObject.prototype[REQUEST_REPORT_SMS_MEMORY_STATUS] = null; 1.6333 +RilObject.prototype[REQUEST_REPORT_STK_SERVICE_IS_RUNNING] = null; 1.6334 +RilObject.prototype[REQUEST_ACKNOWLEDGE_INCOMING_GSM_SMS_WITH_PDU] = null; 1.6335 +RilObject.prototype[REQUEST_STK_SEND_ENVELOPE_WITH_STATUS] = function REQUEST_STK_SEND_ENVELOPE_WITH_STATUS(length, options) { 1.6336 + if (options.rilRequestError) { 1.6337 + this.acknowledgeGsmSms(false, PDU_FCS_UNSPECIFIED); 1.6338 + return; 1.6339 + } 1.6340 + 1.6341 + let Buf = this.context.Buf; 1.6342 + let sw1 = Buf.readInt32(); 1.6343 + let sw2 = Buf.readInt32(); 1.6344 + if ((sw1 == ICC_STATUS_SAT_BUSY) && (sw2 === 0x00)) { 1.6345 + this.acknowledgeGsmSms(false, PDU_FCS_USAT_BUSY); 1.6346 + return; 1.6347 + } 1.6348 + 1.6349 + let success = ((sw1 == ICC_STATUS_NORMAL_ENDING) && (sw2 === 0x00)) 1.6350 + || (sw1 == ICC_STATUS_NORMAL_ENDING_WITH_EXTRA); 1.6351 + 1.6352 + let messageStringLength = Buf.readInt32(); // In semi-octets 1.6353 + let responsePduLen = messageStringLength / 2; // In octets 1.6354 + if (!responsePduLen) { 1.6355 + this.acknowledgeGsmSms(success, success ? PDU_FCS_OK 1.6356 + : PDU_FCS_USIM_DATA_DOWNLOAD_ERROR); 1.6357 + return; 1.6358 + } 1.6359 + 1.6360 + this.acknowledgeIncomingGsmSmsWithPDU(success, responsePduLen, options); 1.6361 +}; 1.6362 +RilObject.prototype[REQUEST_VOICE_RADIO_TECH] = function REQUEST_VOICE_RADIO_TECH(length, options) { 1.6363 + if (options.rilRequestError) { 1.6364 + if (DEBUG) { 1.6365 + this.context.debug("Error when getting voice radio tech: " + 1.6366 + options.rilRequestError); 1.6367 + } 1.6368 + return; 1.6369 + } 1.6370 + let radioTech = this.context.Buf.readInt32List(); 1.6371 + this._processRadioTech(radioTech[0]); 1.6372 +}; 1.6373 +RilObject.prototype[REQUEST_GET_UNLOCK_RETRY_COUNT] = function REQUEST_GET_UNLOCK_RETRY_COUNT(length, options) { 1.6374 + options.success = (options.rilRequestError === 0); 1.6375 + if (!options.success) { 1.6376 + options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError]; 1.6377 + } 1.6378 + options.retryCount = length ? this.context.Buf.readInt32List()[0] : -1; 1.6379 + this.sendChromeMessage(options); 1.6380 +}; 1.6381 +RilObject.prototype[RIL_REQUEST_GPRS_ATTACH] = null; 1.6382 +RilObject.prototype[RIL_REQUEST_GPRS_DETACH] = null; 1.6383 +RilObject.prototype[UNSOLICITED_RESPONSE_RADIO_STATE_CHANGED] = function UNSOLICITED_RESPONSE_RADIO_STATE_CHANGED() { 1.6384 + let radioState = this.context.Buf.readInt32(); 1.6385 + let newState; 1.6386 + if (radioState == RADIO_STATE_UNAVAILABLE) { 1.6387 + newState = GECKO_RADIOSTATE_UNAVAILABLE; 1.6388 + } else if (radioState == RADIO_STATE_OFF) { 1.6389 + newState = GECKO_RADIOSTATE_OFF; 1.6390 + } else { 1.6391 + newState = GECKO_RADIOSTATE_READY; 1.6392 + } 1.6393 + 1.6394 + if (DEBUG) { 1.6395 + this.context.debug("Radio state changed from '" + this.radioState + 1.6396 + "' to '" + newState + "'"); 1.6397 + } 1.6398 + if (this.radioState == newState) { 1.6399 + return; 1.6400 + } 1.6401 + 1.6402 + switch (radioState) { 1.6403 + case RADIO_STATE_SIM_READY: 1.6404 + case RADIO_STATE_SIM_NOT_READY: 1.6405 + case RADIO_STATE_SIM_LOCKED_OR_ABSENT: 1.6406 + this._isCdma = false; 1.6407 + this._waitingRadioTech = false; 1.6408 + break; 1.6409 + case RADIO_STATE_RUIM_READY: 1.6410 + case RADIO_STATE_RUIM_NOT_READY: 1.6411 + case RADIO_STATE_RUIM_LOCKED_OR_ABSENT: 1.6412 + case RADIO_STATE_NV_READY: 1.6413 + case RADIO_STATE_NV_NOT_READY: 1.6414 + this._isCdma = true; 1.6415 + this._waitingRadioTech = false; 1.6416 + break; 1.6417 + case RADIO_STATE_ON: // RIL v7 1.6418 + // This value is defined in RIL v7, we will retrieve radio tech by another 1.6419 + // request. We leave _isCdma untouched, and it will be set once we get the 1.6420 + // radio technology. 1.6421 + this._waitingRadioTech = true; 1.6422 + this.getVoiceRadioTechnology(); 1.6423 + break; 1.6424 + } 1.6425 + 1.6426 + if ((this.radioState == GECKO_RADIOSTATE_UNAVAILABLE || 1.6427 + this.radioState == GECKO_RADIOSTATE_OFF) && 1.6428 + newState == GECKO_RADIOSTATE_READY) { 1.6429 + // The radio became available, let's get its info. 1.6430 + if (!this._waitingRadioTech) { 1.6431 + if (this._isCdma) { 1.6432 + this.getDeviceIdentity(); 1.6433 + } else { 1.6434 + this.getIMEI(); 1.6435 + this.getIMEISV(); 1.6436 + } 1.6437 + } 1.6438 + this.getBasebandVersion(); 1.6439 + this.updateCellBroadcastConfig(); 1.6440 + this.setPreferredNetworkType(); 1.6441 + this.setCLIR(); 1.6442 + if (RILQUIRKS_DATA_REGISTRATION_ON_DEMAND && this._attachDataRegistration) { 1.6443 + this.setDataRegistration({attach: true}); 1.6444 + } 1.6445 + } 1.6446 + 1.6447 + this.radioState = newState; 1.6448 + this.sendChromeMessage({ 1.6449 + rilMessageType: "radiostatechange", 1.6450 + radioState: newState 1.6451 + }); 1.6452 + 1.6453 + // If the radio is up and on, so let's query the card state. 1.6454 + // On older RILs only if the card is actually ready, though. 1.6455 + // If _waitingRadioTech is set, we don't need to get icc status now. 1.6456 + if (radioState == RADIO_STATE_UNAVAILABLE || 1.6457 + radioState == RADIO_STATE_OFF || 1.6458 + this._waitingRadioTech) { 1.6459 + return; 1.6460 + } 1.6461 + this.getICCStatus(); 1.6462 +}; 1.6463 +RilObject.prototype[UNSOLICITED_RESPONSE_CALL_STATE_CHANGED] = function UNSOLICITED_RESPONSE_CALL_STATE_CHANGED() { 1.6464 + this.getCurrentCalls(); 1.6465 +}; 1.6466 +RilObject.prototype[UNSOLICITED_RESPONSE_VOICE_NETWORK_STATE_CHANGED] = function UNSOLICITED_RESPONSE_VOICE_NETWORK_STATE_CHANGED() { 1.6467 + if (DEBUG) { 1.6468 + this.context.debug("Network state changed, re-requesting phone state and " + 1.6469 + "ICC status"); 1.6470 + } 1.6471 + this.getICCStatus(); 1.6472 + this.requestNetworkInfo(); 1.6473 +}; 1.6474 +RilObject.prototype[UNSOLICITED_RESPONSE_NEW_SMS] = function UNSOLICITED_RESPONSE_NEW_SMS(length) { 1.6475 + let [message, result] = this.context.GsmPDUHelper.processReceivedSms(length); 1.6476 + 1.6477 + if (message) { 1.6478 + result = this._processSmsMultipart(message); 1.6479 + } 1.6480 + 1.6481 + if (result == PDU_FCS_RESERVED || result == MOZ_FCS_WAIT_FOR_EXPLICIT_ACK) { 1.6482 + return; 1.6483 + } 1.6484 + 1.6485 + // Not reserved FCS values, send ACK now. 1.6486 + this.acknowledgeGsmSms(result == PDU_FCS_OK, result); 1.6487 +}; 1.6488 +RilObject.prototype[UNSOLICITED_RESPONSE_NEW_SMS_STATUS_REPORT] = function UNSOLICITED_RESPONSE_NEW_SMS_STATUS_REPORT(length) { 1.6489 + let result = this._processSmsStatusReport(length); 1.6490 + this.acknowledgeGsmSms(result == PDU_FCS_OK, result); 1.6491 +}; 1.6492 +RilObject.prototype[UNSOLICITED_RESPONSE_NEW_SMS_ON_SIM] = function UNSOLICITED_RESPONSE_NEW_SMS_ON_SIM(length) { 1.6493 + let recordNumber = this.context.Buf.readInt32List()[0]; 1.6494 + 1.6495 + this.context.SimRecordHelper.readSMS( 1.6496 + recordNumber, 1.6497 + function onsuccess(message) { 1.6498 + if (message && message.simStatus === 3) { //New Unread SMS 1.6499 + this._processSmsMultipart(message); 1.6500 + } 1.6501 + }.bind(this), 1.6502 + function onerror(errorMsg) { 1.6503 + if (DEBUG) { 1.6504 + this.context.debug("Failed to Read NEW SMS on SIM #" + recordNumber + 1.6505 + ", errorMsg: " + errorMsg); 1.6506 + } 1.6507 + }); 1.6508 +}; 1.6509 +RilObject.prototype[UNSOLICITED_ON_USSD] = function UNSOLICITED_ON_USSD() { 1.6510 + let [typeCode, message] = this.context.Buf.readStringList(); 1.6511 + if (DEBUG) { 1.6512 + this.context.debug("On USSD. Type Code: " + typeCode + " Message: " + message); 1.6513 + } 1.6514 + 1.6515 + this._ussdSession = (typeCode != "0" && typeCode != "2"); 1.6516 + 1.6517 + this.sendChromeMessage({rilMessageType: "USSDReceived", 1.6518 + message: message, 1.6519 + sessionEnded: !this._ussdSession}); 1.6520 +}; 1.6521 +RilObject.prototype[UNSOLICITED_NITZ_TIME_RECEIVED] = function UNSOLICITED_NITZ_TIME_RECEIVED() { 1.6522 + let dateString = this.context.Buf.readString(); 1.6523 + 1.6524 + // The data contained in the NITZ message is 1.6525 + // in the form "yy/mm/dd,hh:mm:ss(+/-)tz,dt" 1.6526 + // for example: 12/02/16,03:36:08-20,00,310410 1.6527 + // See also bug 714352 - Listen for NITZ updates from rild. 1.6528 + 1.6529 + if (DEBUG) this.context.debug("DateTimeZone string " + dateString); 1.6530 + 1.6531 + let now = Date.now(); 1.6532 + 1.6533 + let year = parseInt(dateString.substr(0, 2), 10); 1.6534 + let month = parseInt(dateString.substr(3, 2), 10); 1.6535 + let day = parseInt(dateString.substr(6, 2), 10); 1.6536 + let hours = parseInt(dateString.substr(9, 2), 10); 1.6537 + let minutes = parseInt(dateString.substr(12, 2), 10); 1.6538 + let seconds = parseInt(dateString.substr(15, 2), 10); 1.6539 + // Note that |tz| is in 15-min units. 1.6540 + let tz = parseInt(dateString.substr(17, 3), 10); 1.6541 + // Note that |dst| is in 1-hour units and is already applied in |tz|. 1.6542 + let dst = parseInt(dateString.substr(21, 2), 10); 1.6543 + 1.6544 + let timeInMS = Date.UTC(year + PDU_TIMESTAMP_YEAR_OFFSET, month - 1, day, 1.6545 + hours, minutes, seconds); 1.6546 + 1.6547 + if (isNaN(timeInMS)) { 1.6548 + if (DEBUG) this.context.debug("NITZ failed to convert date"); 1.6549 + return; 1.6550 + } 1.6551 + 1.6552 + this.sendChromeMessage({rilMessageType: "nitzTime", 1.6553 + networkTimeInMS: timeInMS, 1.6554 + networkTimeZoneInMinutes: -(tz * 15), 1.6555 + networkDSTInMinutes: -(dst * 60), 1.6556 + receiveTimeInMS: now}); 1.6557 +}; 1.6558 + 1.6559 +RilObject.prototype[UNSOLICITED_SIGNAL_STRENGTH] = function UNSOLICITED_SIGNAL_STRENGTH(length) { 1.6560 + this[REQUEST_SIGNAL_STRENGTH](length, {rilRequestError: ERROR_SUCCESS}); 1.6561 +}; 1.6562 +RilObject.prototype[UNSOLICITED_DATA_CALL_LIST_CHANGED] = function UNSOLICITED_DATA_CALL_LIST_CHANGED(length) { 1.6563 + if (this.v5Legacy) { 1.6564 + this.getDataCallList(); 1.6565 + return; 1.6566 + } 1.6567 + this[REQUEST_DATA_CALL_LIST](length, {rilRequestError: ERROR_SUCCESS}); 1.6568 +}; 1.6569 +RilObject.prototype[UNSOLICITED_SUPP_SVC_NOTIFICATION] = function UNSOLICITED_SUPP_SVC_NOTIFICATION(length) { 1.6570 + let Buf = this.context.Buf; 1.6571 + let info = {}; 1.6572 + info.notificationType = Buf.readInt32(); 1.6573 + info.code = Buf.readInt32(); 1.6574 + info.index = Buf.readInt32(); 1.6575 + info.type = Buf.readInt32(); 1.6576 + info.number = Buf.readString(); 1.6577 + 1.6578 + this._processSuppSvcNotification(info); 1.6579 +}; 1.6580 + 1.6581 +RilObject.prototype[UNSOLICITED_STK_SESSION_END] = function UNSOLICITED_STK_SESSION_END() { 1.6582 + this.sendChromeMessage({rilMessageType: "stksessionend"}); 1.6583 +}; 1.6584 +RilObject.prototype[UNSOLICITED_STK_PROACTIVE_COMMAND] = function UNSOLICITED_STK_PROACTIVE_COMMAND() { 1.6585 + this.processStkProactiveCommand(); 1.6586 +}; 1.6587 +RilObject.prototype[UNSOLICITED_STK_EVENT_NOTIFY] = function UNSOLICITED_STK_EVENT_NOTIFY() { 1.6588 + this.processStkProactiveCommand(); 1.6589 +}; 1.6590 +RilObject.prototype[UNSOLICITED_STK_CALL_SETUP] = null; 1.6591 +RilObject.prototype[UNSOLICITED_SIM_SMS_STORAGE_FULL] = null; 1.6592 +RilObject.prototype[UNSOLICITED_SIM_REFRESH] = null; 1.6593 +RilObject.prototype[UNSOLICITED_CALL_RING] = function UNSOLICITED_CALL_RING() { 1.6594 + let Buf = this.context.Buf; 1.6595 + let info = {rilMessageType: "callRing"}; 1.6596 + let isCDMA = false; //XXX TODO hard-code this for now 1.6597 + if (isCDMA) { 1.6598 + info.isPresent = Buf.readInt32(); 1.6599 + info.signalType = Buf.readInt32(); 1.6600 + info.alertPitch = Buf.readInt32(); 1.6601 + info.signal = Buf.readInt32(); 1.6602 + } 1.6603 + // At this point we don't know much other than the fact there's an incoming 1.6604 + // call, but that's enough to bring up the Phone app already. We'll know 1.6605 + // details once we get a call state changed notification and can then 1.6606 + // dispatch DOM events etc. 1.6607 + this.sendChromeMessage(info); 1.6608 +}; 1.6609 +RilObject.prototype[UNSOLICITED_RESPONSE_SIM_STATUS_CHANGED] = function UNSOLICITED_RESPONSE_SIM_STATUS_CHANGED() { 1.6610 + this.getICCStatus(); 1.6611 +}; 1.6612 +RilObject.prototype[UNSOLICITED_RESPONSE_CDMA_NEW_SMS] = function UNSOLICITED_RESPONSE_CDMA_NEW_SMS(length) { 1.6613 + let [message, result] = this.context.CdmaPDUHelper.processReceivedSms(length); 1.6614 + 1.6615 + if (message) { 1.6616 + if (message.teleservice === PDU_CDMA_MSG_TELESERIVCIE_ID_WAP) { 1.6617 + result = this._processCdmaSmsWapPush(message); 1.6618 + } else if (message.subMsgType === PDU_CDMA_MSG_TYPE_DELIVER_ACK) { 1.6619 + result = this._processCdmaSmsStatusReport(message); 1.6620 + } else { 1.6621 + result = this._processSmsMultipart(message); 1.6622 + } 1.6623 + } 1.6624 + 1.6625 + if (result == PDU_FCS_RESERVED || result == MOZ_FCS_WAIT_FOR_EXPLICIT_ACK) { 1.6626 + return; 1.6627 + } 1.6628 + 1.6629 + // Not reserved FCS values, send ACK now. 1.6630 + this.acknowledgeCdmaSms(result == PDU_FCS_OK, result); 1.6631 +}; 1.6632 +RilObject.prototype[UNSOLICITED_RESPONSE_NEW_BROADCAST_SMS] = function UNSOLICITED_RESPONSE_NEW_BROADCAST_SMS(length) { 1.6633 + let message; 1.6634 + try { 1.6635 + message = 1.6636 + this.context.GsmPDUHelper.readCbMessage(this.context.Buf.readInt32()); 1.6637 + } catch (e) { 1.6638 + if (DEBUG) { 1.6639 + this.context.debug("Failed to parse Cell Broadcast message: " + 1.6640 + JSON.stringify(e)); 1.6641 + } 1.6642 + return; 1.6643 + } 1.6644 + 1.6645 + message = this._processReceivedSmsCbPage(message); 1.6646 + if (!message) { 1.6647 + return; 1.6648 + } 1.6649 + 1.6650 + message.rilMessageType = "cellbroadcast-received"; 1.6651 + this.sendChromeMessage(message); 1.6652 +}; 1.6653 +RilObject.prototype[UNSOLICITED_CDMA_RUIM_SMS_STORAGE_FULL] = null; 1.6654 +RilObject.prototype[UNSOLICITED_RESTRICTED_STATE_CHANGED] = null; 1.6655 +RilObject.prototype[UNSOLICITED_ENTER_EMERGENCY_CALLBACK_MODE] = function UNSOLICITED_ENTER_EMERGENCY_CALLBACK_MODE() { 1.6656 + this._handleChangedEmergencyCbMode(true); 1.6657 +}; 1.6658 +RilObject.prototype[UNSOLICITED_CDMA_CALL_WAITING] = function UNSOLICITED_CDMA_CALL_WAITING(length) { 1.6659 + let Buf = this.context.Buf; 1.6660 + let call = {}; 1.6661 + call.number = Buf.readString(); 1.6662 + call.numberPresentation = Buf.readInt32(); 1.6663 + call.name = Buf.readString(); 1.6664 + call.namePresentation = Buf.readInt32(); 1.6665 + call.isPresent = Buf.readInt32(); 1.6666 + call.signalType = Buf.readInt32(); 1.6667 + call.alertPitch = Buf.readInt32(); 1.6668 + call.signal = Buf.readInt32(); 1.6669 + this.sendChromeMessage({rilMessageType: "cdmaCallWaiting", 1.6670 + number: call.number}); 1.6671 +}; 1.6672 +RilObject.prototype[UNSOLICITED_CDMA_OTA_PROVISION_STATUS] = function UNSOLICITED_CDMA_OTA_PROVISION_STATUS() { 1.6673 + let status = this.context.Buf.readInt32List()[0]; 1.6674 + this.sendChromeMessage({rilMessageType: "otastatuschange", 1.6675 + status: status}); 1.6676 +}; 1.6677 +RilObject.prototype[UNSOLICITED_CDMA_INFO_REC] = function UNSOLICITED_CDMA_INFO_REC(length) { 1.6678 + let record = this.context.CdmaPDUHelper.decodeInformationRecord(); 1.6679 + record.rilMessageType = "cdma-info-rec-received"; 1.6680 + this.sendChromeMessage(record); 1.6681 +}; 1.6682 +RilObject.prototype[UNSOLICITED_OEM_HOOK_RAW] = null; 1.6683 +RilObject.prototype[UNSOLICITED_RINGBACK_TONE] = null; 1.6684 +RilObject.prototype[UNSOLICITED_RESEND_INCALL_MUTE] = null; 1.6685 +RilObject.prototype[UNSOLICITED_CDMA_SUBSCRIPTION_SOURCE_CHANGED] = null; 1.6686 +RilObject.prototype[UNSOLICITED_CDMA_PRL_CHANGED] = function UNSOLICITED_CDMA_PRL_CHANGED(length) { 1.6687 + let version = this.context.Buf.readInt32List()[0]; 1.6688 + if (version !== this.iccInfo.prlVersion) { 1.6689 + this.iccInfo.prlVersion = version; 1.6690 + this.context.ICCUtilsHelper.handleICCInfoChange(); 1.6691 + } 1.6692 +}; 1.6693 +RilObject.prototype[UNSOLICITED_EXIT_EMERGENCY_CALLBACK_MODE] = function UNSOLICITED_EXIT_EMERGENCY_CALLBACK_MODE() { 1.6694 + this._handleChangedEmergencyCbMode(false); 1.6695 +}; 1.6696 +RilObject.prototype[UNSOLICITED_RIL_CONNECTED] = function UNSOLICITED_RIL_CONNECTED(length) { 1.6697 + // Prevent response id collision between UNSOLICITED_RIL_CONNECTED and 1.6698 + // UNSOLICITED_VOICE_RADIO_TECH_CHANGED for Akami on gingerbread branch. 1.6699 + if (!length) { 1.6700 + return; 1.6701 + } 1.6702 + 1.6703 + let version = this.context.Buf.readInt32List()[0]; 1.6704 + this.v5Legacy = (version < 5); 1.6705 + if (DEBUG) { 1.6706 + this.context.debug("Detected RIL version " + version); 1.6707 + this.context.debug("this.v5Legacy is " + this.v5Legacy); 1.6708 + } 1.6709 + 1.6710 + this.initRILState(); 1.6711 + // Always ensure that we are not in emergency callback mode when init. 1.6712 + this.exitEmergencyCbMode(); 1.6713 + // Reset radio in the case that b2g restart (or crash). 1.6714 + this.setRadioEnabled({enabled: false}); 1.6715 +}; 1.6716 + 1.6717 +/** 1.6718 + * This object exposes the functionality to parse and serialize PDU strings 1.6719 + * 1.6720 + * A PDU is a string containing a series of hexadecimally encoded octets 1.6721 + * or nibble-swapped binary-coded decimals (BCDs). It contains not only the 1.6722 + * message text but information about the sender, the SMS service center, 1.6723 + * timestamp, etc. 1.6724 + */ 1.6725 +function GsmPDUHelperObject(aContext) { 1.6726 + this.context = aContext; 1.6727 +} 1.6728 +GsmPDUHelperObject.prototype = { 1.6729 + context: null, 1.6730 + 1.6731 + /** 1.6732 + * Read one character (2 bytes) from a RIL string and decode as hex. 1.6733 + * 1.6734 + * @return the nibble as a number. 1.6735 + */ 1.6736 + readHexNibble: function() { 1.6737 + let nibble = this.context.Buf.readUint16(); 1.6738 + if (nibble >= 48 && nibble <= 57) { 1.6739 + nibble -= 48; // ASCII '0'..'9' 1.6740 + } else if (nibble >= 65 && nibble <= 70) { 1.6741 + nibble -= 55; // ASCII 'A'..'F' 1.6742 + } else if (nibble >= 97 && nibble <= 102) { 1.6743 + nibble -= 87; // ASCII 'a'..'f' 1.6744 + } else { 1.6745 + throw "Found invalid nibble during PDU parsing: " + 1.6746 + String.fromCharCode(nibble); 1.6747 + } 1.6748 + return nibble; 1.6749 + }, 1.6750 + 1.6751 + /** 1.6752 + * Encode a nibble as one hex character in a RIL string (2 bytes). 1.6753 + * 1.6754 + * @param nibble 1.6755 + * The nibble to encode (represented as a number) 1.6756 + */ 1.6757 + writeHexNibble: function(nibble) { 1.6758 + nibble &= 0x0f; 1.6759 + if (nibble < 10) { 1.6760 + nibble += 48; // ASCII '0' 1.6761 + } else { 1.6762 + nibble += 55; // ASCII 'A' 1.6763 + } 1.6764 + this.context.Buf.writeUint16(nibble); 1.6765 + }, 1.6766 + 1.6767 + /** 1.6768 + * Read a hex-encoded octet (two nibbles). 1.6769 + * 1.6770 + * @return the octet as a number. 1.6771 + */ 1.6772 + readHexOctet: function() { 1.6773 + return (this.readHexNibble() << 4) | this.readHexNibble(); 1.6774 + }, 1.6775 + 1.6776 + /** 1.6777 + * Write an octet as two hex-encoded nibbles. 1.6778 + * 1.6779 + * @param octet 1.6780 + * The octet (represented as a number) to encode. 1.6781 + */ 1.6782 + writeHexOctet: function(octet) { 1.6783 + this.writeHexNibble(octet >> 4); 1.6784 + this.writeHexNibble(octet); 1.6785 + }, 1.6786 + 1.6787 + /** 1.6788 + * Read an array of hex-encoded octets. 1.6789 + */ 1.6790 + readHexOctetArray: function(length) { 1.6791 + let array = new Uint8Array(length); 1.6792 + for (let i = 0; i < length; i++) { 1.6793 + array[i] = this.readHexOctet(); 1.6794 + } 1.6795 + return array; 1.6796 + }, 1.6797 + 1.6798 + /** 1.6799 + * Convert an octet (number) to a BCD number. 1.6800 + * 1.6801 + * Any nibbles that are not in the BCD range count as 0. 1.6802 + * 1.6803 + * @param octet 1.6804 + * The octet (a number, as returned by getOctet()) 1.6805 + * 1.6806 + * @return the corresponding BCD number. 1.6807 + */ 1.6808 + octetToBCD: function(octet) { 1.6809 + return ((octet & 0xf0) <= 0x90) * ((octet >> 4) & 0x0f) + 1.6810 + ((octet & 0x0f) <= 0x09) * (octet & 0x0f) * 10; 1.6811 + }, 1.6812 + 1.6813 + /** 1.6814 + * Convert a BCD number to an octet (number) 1.6815 + * 1.6816 + * Only take two digits with absolute value. 1.6817 + * 1.6818 + * @param bcd 1.6819 + * 1.6820 + * @return the corresponding octet. 1.6821 + */ 1.6822 + BCDToOctet: function(bcd) { 1.6823 + bcd = Math.abs(bcd); 1.6824 + return ((bcd % 10) << 4) + (Math.floor(bcd / 10) % 10); 1.6825 + }, 1.6826 + 1.6827 + /** 1.6828 + * Convert a semi-octet (number) to a GSM BCD char, or return empty string 1.6829 + * if invalid semiOctet and supressException is set to true. 1.6830 + * 1.6831 + * @param semiOctet 1.6832 + * Nibble to be converted to. 1.6833 + * @param [optional] supressException 1.6834 + * Supress exception if invalid semiOctet and supressException is set 1.6835 + * to true. 1.6836 + * 1.6837 + * @return GSM BCD char, or empty string. 1.6838 + */ 1.6839 + bcdChars: "0123456789*#,;", 1.6840 + semiOctetToBcdChar: function(semiOctet, supressException) { 1.6841 + if (semiOctet >= 14) { 1.6842 + if (supressException) { 1.6843 + return ""; 1.6844 + } else { 1.6845 + throw new RangeError(); 1.6846 + } 1.6847 + } 1.6848 + 1.6849 + return this.bcdChars.charAt(semiOctet); 1.6850 + }, 1.6851 + 1.6852 + /** 1.6853 + * Read a *swapped nibble* binary coded decimal (BCD) 1.6854 + * 1.6855 + * @param pairs 1.6856 + * Number of nibble *pairs* to read. 1.6857 + * 1.6858 + * @return the decimal as a number. 1.6859 + */ 1.6860 + readSwappedNibbleBcdNum: function(pairs) { 1.6861 + let number = 0; 1.6862 + for (let i = 0; i < pairs; i++) { 1.6863 + let octet = this.readHexOctet(); 1.6864 + // Ignore 'ff' octets as they're often used as filler. 1.6865 + if (octet == 0xff) { 1.6866 + continue; 1.6867 + } 1.6868 + // If the first nibble is an "F" , only the second nibble is to be taken 1.6869 + // into account. 1.6870 + if ((octet & 0xf0) == 0xf0) { 1.6871 + number *= 10; 1.6872 + number += octet & 0x0f; 1.6873 + continue; 1.6874 + } 1.6875 + number *= 100; 1.6876 + number += this.octetToBCD(octet); 1.6877 + } 1.6878 + return number; 1.6879 + }, 1.6880 + 1.6881 + /** 1.6882 + * Read a *swapped nibble* binary coded string (BCD) 1.6883 + * 1.6884 + * @param pairs 1.6885 + * Number of nibble *pairs* to read. 1.6886 + * @param [optional] supressException 1.6887 + * Supress exception if invalid semiOctet and supressException is set 1.6888 + * to true. 1.6889 + * 1.6890 + * @return The BCD string. 1.6891 + */ 1.6892 + readSwappedNibbleBcdString: function(pairs, supressException) { 1.6893 + let str = ""; 1.6894 + for (let i = 0; i < pairs; i++) { 1.6895 + let nibbleH = this.readHexNibble(); 1.6896 + let nibbleL = this.readHexNibble(); 1.6897 + if (nibbleL == 0x0F) { 1.6898 + break; 1.6899 + } 1.6900 + 1.6901 + str += this.semiOctetToBcdChar(nibbleL, supressException); 1.6902 + if (nibbleH != 0x0F) { 1.6903 + str += this.semiOctetToBcdChar(nibbleH, supressException); 1.6904 + } 1.6905 + } 1.6906 + 1.6907 + return str; 1.6908 + }, 1.6909 + 1.6910 + /** 1.6911 + * Write numerical data as swapped nibble BCD. 1.6912 + * 1.6913 + * @param data 1.6914 + * Data to write (as a string or a number) 1.6915 + */ 1.6916 + writeSwappedNibbleBCD: function(data) { 1.6917 + data = data.toString(); 1.6918 + if (data.length % 2) { 1.6919 + data += "F"; 1.6920 + } 1.6921 + let Buf = this.context.Buf; 1.6922 + for (let i = 0; i < data.length; i += 2) { 1.6923 + Buf.writeUint16(data.charCodeAt(i + 1)); 1.6924 + Buf.writeUint16(data.charCodeAt(i)); 1.6925 + } 1.6926 + }, 1.6927 + 1.6928 + /** 1.6929 + * Write numerical data as swapped nibble BCD. 1.6930 + * If the number of digit of data is even, add '0' at the beginning. 1.6931 + * 1.6932 + * @param data 1.6933 + * Data to write (as a string or a number) 1.6934 + */ 1.6935 + writeSwappedNibbleBCDNum: function(data) { 1.6936 + data = data.toString(); 1.6937 + if (data.length % 2) { 1.6938 + data = "0" + data; 1.6939 + } 1.6940 + let Buf = this.context.Buf; 1.6941 + for (let i = 0; i < data.length; i += 2) { 1.6942 + Buf.writeUint16(data.charCodeAt(i + 1)); 1.6943 + Buf.writeUint16(data.charCodeAt(i)); 1.6944 + } 1.6945 + }, 1.6946 + 1.6947 + /** 1.6948 + * Read user data, convert to septets, look up relevant characters in a 1.6949 + * 7-bit alphabet, and construct string. 1.6950 + * 1.6951 + * @param length 1.6952 + * Number of septets to read (*not* octets) 1.6953 + * @param paddingBits 1.6954 + * Number of padding bits in the first byte of user data. 1.6955 + * @param langIndex 1.6956 + * Table index used for normal 7-bit encoded character lookup. 1.6957 + * @param langShiftIndex 1.6958 + * Table index used for escaped 7-bit encoded character lookup. 1.6959 + * 1.6960 + * @return a string. 1.6961 + */ 1.6962 + readSeptetsToString: function(length, paddingBits, langIndex, langShiftIndex) { 1.6963 + let ret = ""; 1.6964 + let byteLength = Math.ceil((length * 7 + paddingBits) / 8); 1.6965 + 1.6966 + /** 1.6967 + * |<- last byte in header ->| 1.6968 + * |<- incompleteBits ->|<- last header septet->| 1.6969 + * +===7===|===6===|===5===|===4===|===3===|===2===|===1===|===0===| 1.6970 + * 1.6971 + * |<- 1st byte in user data ->| 1.6972 + * |<- data septet 1 ->|<-paddingBits->| 1.6973 + * +===7===|===6===|===5===|===4===|===3===|===2===|===1===|===0===| 1.6974 + * 1.6975 + * |<- 2nd byte in user data ->| 1.6976 + * |<- data spetet 2 ->|<-ds1->| 1.6977 + * +===7===|===6===|===5===|===4===|===3===|===2===|===1===|===0===| 1.6978 + */ 1.6979 + let data = 0; 1.6980 + let dataBits = 0; 1.6981 + if (paddingBits) { 1.6982 + data = this.readHexOctet() >> paddingBits; 1.6983 + dataBits = 8 - paddingBits; 1.6984 + --byteLength; 1.6985 + } 1.6986 + 1.6987 + let escapeFound = false; 1.6988 + const langTable = PDU_NL_LOCKING_SHIFT_TABLES[langIndex]; 1.6989 + const langShiftTable = PDU_NL_SINGLE_SHIFT_TABLES[langShiftIndex]; 1.6990 + do { 1.6991 + // Read as much as fits in 32bit word 1.6992 + let bytesToRead = Math.min(byteLength, dataBits ? 3 : 4); 1.6993 + for (let i = 0; i < bytesToRead; i++) { 1.6994 + data |= this.readHexOctet() << dataBits; 1.6995 + dataBits += 8; 1.6996 + --byteLength; 1.6997 + } 1.6998 + 1.6999 + // Consume available full septets 1.7000 + for (; dataBits >= 7; dataBits -= 7) { 1.7001 + let septet = data & 0x7F; 1.7002 + data >>>= 7; 1.7003 + 1.7004 + if (escapeFound) { 1.7005 + escapeFound = false; 1.7006 + if (septet == PDU_NL_EXTENDED_ESCAPE) { 1.7007 + // According to 3GPP TS 23.038, section 6.2.1.1, NOTE 1, "On 1.7008 + // receipt of this code, a receiving entity shall display a space 1.7009 + // until another extensiion table is defined." 1.7010 + ret += " "; 1.7011 + } else if (septet == PDU_NL_RESERVED_CONTROL) { 1.7012 + // According to 3GPP TS 23.038 B.2, "This code represents a control 1.7013 + // character and therefore must not be used for language specific 1.7014 + // characters." 1.7015 + ret += " "; 1.7016 + } else { 1.7017 + ret += langShiftTable[septet]; 1.7018 + } 1.7019 + } else if (septet == PDU_NL_EXTENDED_ESCAPE) { 1.7020 + escapeFound = true; 1.7021 + 1.7022 + // <escape> is not an effective character 1.7023 + --length; 1.7024 + } else { 1.7025 + ret += langTable[septet]; 1.7026 + } 1.7027 + } 1.7028 + } while (byteLength); 1.7029 + 1.7030 + if (ret.length != length) { 1.7031 + /** 1.7032 + * If num of effective characters does not equal to the length of read 1.7033 + * string, cut the tail off. This happens when the last octet of user 1.7034 + * data has following layout: 1.7035 + * 1.7036 + * |<- penultimate octet in user data ->| 1.7037 + * |<- data septet N ->|<- dsN-1 ->| 1.7038 + * +===7===|===6===|===5===|===4===|===3===|===2===|===1===|===0===| 1.7039 + * 1.7040 + * |<- last octet in user data ->| 1.7041 + * |<- fill bits ->|<-dsN->| 1.7042 + * +===7===|===6===|===5===|===4===|===3===|===2===|===1===|===0===| 1.7043 + * 1.7044 + * The fill bits in the last octet may happen to form a full septet and 1.7045 + * be appended at the end of result string. 1.7046 + */ 1.7047 + ret = ret.slice(0, length); 1.7048 + } 1.7049 + return ret; 1.7050 + }, 1.7051 + 1.7052 + writeStringAsSeptets: function(message, paddingBits, langIndex, langShiftIndex) { 1.7053 + const langTable = PDU_NL_LOCKING_SHIFT_TABLES[langIndex]; 1.7054 + const langShiftTable = PDU_NL_SINGLE_SHIFT_TABLES[langShiftIndex]; 1.7055 + 1.7056 + let dataBits = paddingBits; 1.7057 + let data = 0; 1.7058 + for (let i = 0; i < message.length; i++) { 1.7059 + let c = message.charAt(i); 1.7060 + let septet = langTable.indexOf(c); 1.7061 + if (septet == PDU_NL_EXTENDED_ESCAPE) { 1.7062 + continue; 1.7063 + } 1.7064 + 1.7065 + if (septet >= 0) { 1.7066 + data |= septet << dataBits; 1.7067 + dataBits += 7; 1.7068 + } else { 1.7069 + septet = langShiftTable.indexOf(c); 1.7070 + if (septet == -1) { 1.7071 + throw new Error("'" + c + "' is not in 7 bit alphabet " 1.7072 + + langIndex + ":" + langShiftIndex + "!"); 1.7073 + } 1.7074 + 1.7075 + if (septet == PDU_NL_RESERVED_CONTROL) { 1.7076 + continue; 1.7077 + } 1.7078 + 1.7079 + data |= PDU_NL_EXTENDED_ESCAPE << dataBits; 1.7080 + dataBits += 7; 1.7081 + data |= septet << dataBits; 1.7082 + dataBits += 7; 1.7083 + } 1.7084 + 1.7085 + for (; dataBits >= 8; dataBits -= 8) { 1.7086 + this.writeHexOctet(data & 0xFF); 1.7087 + data >>>= 8; 1.7088 + } 1.7089 + } 1.7090 + 1.7091 + if (dataBits !== 0) { 1.7092 + this.writeHexOctet(data & 0xFF); 1.7093 + } 1.7094 + }, 1.7095 + 1.7096 + /** 1.7097 + * Read user data and decode as a UCS2 string. 1.7098 + * 1.7099 + * @param numOctets 1.7100 + * Number of octets to be read as UCS2 string. 1.7101 + * 1.7102 + * @return a string. 1.7103 + */ 1.7104 + readUCS2String: function(numOctets) { 1.7105 + let str = ""; 1.7106 + let length = numOctets / 2; 1.7107 + for (let i = 0; i < length; ++i) { 1.7108 + let code = (this.readHexOctet() << 8) | this.readHexOctet(); 1.7109 + str += String.fromCharCode(code); 1.7110 + } 1.7111 + 1.7112 + if (DEBUG) this.context.debug("Read UCS2 string: " + str); 1.7113 + 1.7114 + return str; 1.7115 + }, 1.7116 + 1.7117 + /** 1.7118 + * Write user data as a UCS2 string. 1.7119 + * 1.7120 + * @param message 1.7121 + * Message string to encode as UCS2 in hex-encoded octets. 1.7122 + */ 1.7123 + writeUCS2String: function(message) { 1.7124 + for (let i = 0; i < message.length; ++i) { 1.7125 + let code = message.charCodeAt(i); 1.7126 + this.writeHexOctet((code >> 8) & 0xFF); 1.7127 + this.writeHexOctet(code & 0xFF); 1.7128 + } 1.7129 + }, 1.7130 + 1.7131 + /** 1.7132 + * Read 1 + UDHL octets and construct user data header. 1.7133 + * 1.7134 + * @param msg 1.7135 + * message object for output. 1.7136 + * 1.7137 + * @see 3GPP TS 23.040 9.2.3.24 1.7138 + */ 1.7139 + readUserDataHeader: function(msg) { 1.7140 + /** 1.7141 + * A header object with properties contained in received message. 1.7142 + * The properties set include: 1.7143 + * 1.7144 + * length: totoal length of the header, default 0. 1.7145 + * langIndex: used locking shift table index, default 1.7146 + * PDU_NL_IDENTIFIER_DEFAULT. 1.7147 + * langShiftIndex: used locking shift table index, default 1.7148 + * PDU_NL_IDENTIFIER_DEFAULT. 1.7149 + * 1.7150 + */ 1.7151 + let header = { 1.7152 + length: 0, 1.7153 + langIndex: PDU_NL_IDENTIFIER_DEFAULT, 1.7154 + langShiftIndex: PDU_NL_IDENTIFIER_DEFAULT 1.7155 + }; 1.7156 + 1.7157 + header.length = this.readHexOctet(); 1.7158 + if (DEBUG) this.context.debug("Read UDH length: " + header.length); 1.7159 + 1.7160 + let dataAvailable = header.length; 1.7161 + while (dataAvailable >= 2) { 1.7162 + let id = this.readHexOctet(); 1.7163 + let length = this.readHexOctet(); 1.7164 + if (DEBUG) this.context.debug("Read UDH id: " + id + ", length: " + length); 1.7165 + 1.7166 + dataAvailable -= 2; 1.7167 + 1.7168 + switch (id) { 1.7169 + case PDU_IEI_CONCATENATED_SHORT_MESSAGES_8BIT: { 1.7170 + let ref = this.readHexOctet(); 1.7171 + let max = this.readHexOctet(); 1.7172 + let seq = this.readHexOctet(); 1.7173 + dataAvailable -= 3; 1.7174 + if (max && seq && (seq <= max)) { 1.7175 + header.segmentRef = ref; 1.7176 + header.segmentMaxSeq = max; 1.7177 + header.segmentSeq = seq; 1.7178 + } 1.7179 + break; 1.7180 + } 1.7181 + case PDU_IEI_APPLICATION_PORT_ADDRESSING_SCHEME_8BIT: { 1.7182 + let dstp = this.readHexOctet(); 1.7183 + let orip = this.readHexOctet(); 1.7184 + dataAvailable -= 2; 1.7185 + if ((dstp < PDU_APA_RESERVED_8BIT_PORTS) 1.7186 + || (orip < PDU_APA_RESERVED_8BIT_PORTS)) { 1.7187 + // 3GPP TS 23.040 clause 9.2.3.24.3: "A receiving entity shall 1.7188 + // ignore any information element where the value of the 1.7189 + // Information-Element-Data is Reserved or not supported" 1.7190 + break; 1.7191 + } 1.7192 + header.destinationPort = dstp; 1.7193 + header.originatorPort = orip; 1.7194 + break; 1.7195 + } 1.7196 + case PDU_IEI_APPLICATION_PORT_ADDRESSING_SCHEME_16BIT: { 1.7197 + let dstp = (this.readHexOctet() << 8) | this.readHexOctet(); 1.7198 + let orip = (this.readHexOctet() << 8) | this.readHexOctet(); 1.7199 + dataAvailable -= 4; 1.7200 + // 3GPP TS 23.040 clause 9.2.3.24.4: "A receiving entity shall 1.7201 + // ignore any information element where the value of the 1.7202 + // Information-Element-Data is Reserved or not supported" 1.7203 + if ((dstp < PDU_APA_VALID_16BIT_PORTS) 1.7204 + && (orip < PDU_APA_VALID_16BIT_PORTS)) { 1.7205 + header.destinationPort = dstp; 1.7206 + header.originatorPort = orip; 1.7207 + } 1.7208 + break; 1.7209 + } 1.7210 + case PDU_IEI_CONCATENATED_SHORT_MESSAGES_16BIT: { 1.7211 + let ref = (this.readHexOctet() << 8) | this.readHexOctet(); 1.7212 + let max = this.readHexOctet(); 1.7213 + let seq = this.readHexOctet(); 1.7214 + dataAvailable -= 4; 1.7215 + if (max && seq && (seq <= max)) { 1.7216 + header.segmentRef = ref; 1.7217 + header.segmentMaxSeq = max; 1.7218 + header.segmentSeq = seq; 1.7219 + } 1.7220 + break; 1.7221 + } 1.7222 + case PDU_IEI_NATIONAL_LANGUAGE_SINGLE_SHIFT: 1.7223 + let langShiftIndex = this.readHexOctet(); 1.7224 + --dataAvailable; 1.7225 + if (langShiftIndex < PDU_NL_SINGLE_SHIFT_TABLES.length) { 1.7226 + header.langShiftIndex = langShiftIndex; 1.7227 + } 1.7228 + break; 1.7229 + case PDU_IEI_NATIONAL_LANGUAGE_LOCKING_SHIFT: 1.7230 + let langIndex = this.readHexOctet(); 1.7231 + --dataAvailable; 1.7232 + if (langIndex < PDU_NL_LOCKING_SHIFT_TABLES.length) { 1.7233 + header.langIndex = langIndex; 1.7234 + } 1.7235 + break; 1.7236 + case PDU_IEI_SPECIAL_SMS_MESSAGE_INDICATION: 1.7237 + let msgInd = this.readHexOctet() & 0xFF; 1.7238 + let msgCount = this.readHexOctet(); 1.7239 + dataAvailable -= 2; 1.7240 + 1.7241 + 1.7242 + /* 1.7243 + * TS 23.040 V6.8.1 Sec 9.2.3.24.2 1.7244 + * bits 1 0 : basic message indication type 1.7245 + * bits 4 3 2 : extended message indication type 1.7246 + * bits 6 5 : Profile id 1.7247 + * bit 7 : storage type 1.7248 + */ 1.7249 + let storeType = msgInd & PDU_MWI_STORE_TYPE_BIT; 1.7250 + let mwi = msg.mwi; 1.7251 + if (!mwi) { 1.7252 + mwi = msg.mwi = {}; 1.7253 + } 1.7254 + 1.7255 + if (storeType == PDU_MWI_STORE_TYPE_STORE) { 1.7256 + // Store message because TP_UDH indicates so, note this may override 1.7257 + // the setting in DCS, but that is expected 1.7258 + mwi.discard = false; 1.7259 + } else if (mwi.discard === undefined) { 1.7260 + // storeType == PDU_MWI_STORE_TYPE_DISCARD 1.7261 + // only override mwi.discard here if it hasn't already been set 1.7262 + mwi.discard = true; 1.7263 + } 1.7264 + 1.7265 + mwi.msgCount = msgCount & 0xFF; 1.7266 + mwi.active = mwi.msgCount > 0; 1.7267 + 1.7268 + if (DEBUG) { 1.7269 + this.context.debug("MWI in TP_UDH received: " + JSON.stringify(mwi)); 1.7270 + } 1.7271 + 1.7272 + break; 1.7273 + default: 1.7274 + if (DEBUG) { 1.7275 + this.context.debug("readUserDataHeader: unsupported IEI(" + id + 1.7276 + "), " + length + " bytes."); 1.7277 + } 1.7278 + 1.7279 + // Read out unsupported data 1.7280 + if (length) { 1.7281 + let octets; 1.7282 + if (DEBUG) octets = new Uint8Array(length); 1.7283 + 1.7284 + for (let i = 0; i < length; i++) { 1.7285 + let octet = this.readHexOctet(); 1.7286 + if (DEBUG) octets[i] = octet; 1.7287 + } 1.7288 + dataAvailable -= length; 1.7289 + 1.7290 + if (DEBUG) { 1.7291 + this.context.debug("readUserDataHeader: " + Array.slice(octets)); 1.7292 + } 1.7293 + } 1.7294 + break; 1.7295 + } 1.7296 + } 1.7297 + 1.7298 + if (dataAvailable !== 0) { 1.7299 + throw new Error("Illegal user data header found!"); 1.7300 + } 1.7301 + 1.7302 + msg.header = header; 1.7303 + }, 1.7304 + 1.7305 + /** 1.7306 + * Write out user data header. 1.7307 + * 1.7308 + * @param options 1.7309 + * Options containing information for user data header write-out. The 1.7310 + * `userDataHeaderLength` property must be correctly pre-calculated. 1.7311 + */ 1.7312 + writeUserDataHeader: function(options) { 1.7313 + this.writeHexOctet(options.userDataHeaderLength); 1.7314 + 1.7315 + if (options.segmentMaxSeq > 1) { 1.7316 + if (options.segmentRef16Bit) { 1.7317 + this.writeHexOctet(PDU_IEI_CONCATENATED_SHORT_MESSAGES_16BIT); 1.7318 + this.writeHexOctet(4); 1.7319 + this.writeHexOctet((options.segmentRef >> 8) & 0xFF); 1.7320 + } else { 1.7321 + this.writeHexOctet(PDU_IEI_CONCATENATED_SHORT_MESSAGES_8BIT); 1.7322 + this.writeHexOctet(3); 1.7323 + } 1.7324 + this.writeHexOctet(options.segmentRef & 0xFF); 1.7325 + this.writeHexOctet(options.segmentMaxSeq & 0xFF); 1.7326 + this.writeHexOctet(options.segmentSeq & 0xFF); 1.7327 + } 1.7328 + 1.7329 + if (options.dcs == PDU_DCS_MSG_CODING_7BITS_ALPHABET) { 1.7330 + if (options.langIndex != PDU_NL_IDENTIFIER_DEFAULT) { 1.7331 + this.writeHexOctet(PDU_IEI_NATIONAL_LANGUAGE_LOCKING_SHIFT); 1.7332 + this.writeHexOctet(1); 1.7333 + this.writeHexOctet(options.langIndex); 1.7334 + } 1.7335 + 1.7336 + if (options.langShiftIndex != PDU_NL_IDENTIFIER_DEFAULT) { 1.7337 + this.writeHexOctet(PDU_IEI_NATIONAL_LANGUAGE_SINGLE_SHIFT); 1.7338 + this.writeHexOctet(1); 1.7339 + this.writeHexOctet(options.langShiftIndex); 1.7340 + } 1.7341 + } 1.7342 + }, 1.7343 + 1.7344 + /** 1.7345 + * Read SM-TL Address. 1.7346 + * 1.7347 + * @param len 1.7348 + * Length of useful semi-octets within the Address-Value field. For 1.7349 + * example, the lenth of "12345" should be 5, and 4 for "1234". 1.7350 + * 1.7351 + * @see 3GPP TS 23.040 9.1.2.5 1.7352 + */ 1.7353 + readAddress: function(len) { 1.7354 + // Address Length 1.7355 + if (!len || (len < 0)) { 1.7356 + if (DEBUG) { 1.7357 + this.context.debug("PDU error: invalid sender address length: " + len); 1.7358 + } 1.7359 + return null; 1.7360 + } 1.7361 + if (len % 2 == 1) { 1.7362 + len += 1; 1.7363 + } 1.7364 + if (DEBUG) this.context.debug("PDU: Going to read address: " + len); 1.7365 + 1.7366 + // Type-of-Address 1.7367 + let toa = this.readHexOctet(); 1.7368 + let addr = ""; 1.7369 + 1.7370 + if ((toa & 0xF0) == PDU_TOA_ALPHANUMERIC) { 1.7371 + addr = this.readSeptetsToString(Math.floor(len * 4 / 7), 0, 1.7372 + PDU_NL_IDENTIFIER_DEFAULT , PDU_NL_IDENTIFIER_DEFAULT ); 1.7373 + return addr; 1.7374 + } 1.7375 + addr = this.readSwappedNibbleBcdString(len / 2); 1.7376 + if (addr.length <= 0) { 1.7377 + if (DEBUG) this.context.debug("PDU error: no number provided"); 1.7378 + return null; 1.7379 + } 1.7380 + if ((toa & 0xF0) == (PDU_TOA_INTERNATIONAL)) { 1.7381 + addr = '+' + addr; 1.7382 + } 1.7383 + 1.7384 + return addr; 1.7385 + }, 1.7386 + 1.7387 + /** 1.7388 + * Read TP-Protocol-Indicator(TP-PID). 1.7389 + * 1.7390 + * @param msg 1.7391 + * message object for output. 1.7392 + * 1.7393 + * @see 3GPP TS 23.040 9.2.3.9 1.7394 + */ 1.7395 + readProtocolIndicator: function(msg) { 1.7396 + // `The MS shall interpret reserved, obsolete, or unsupported values as the 1.7397 + // value 00000000 but shall store them exactly as received.` 1.7398 + msg.pid = this.readHexOctet(); 1.7399 + 1.7400 + msg.epid = msg.pid; 1.7401 + switch (msg.epid & 0xC0) { 1.7402 + case 0x40: 1.7403 + // Bit 7..0 = 01xxxxxx 1.7404 + switch (msg.epid) { 1.7405 + case PDU_PID_SHORT_MESSAGE_TYPE_0: 1.7406 + case PDU_PID_ANSI_136_R_DATA: 1.7407 + case PDU_PID_USIM_DATA_DOWNLOAD: 1.7408 + return; 1.7409 + } 1.7410 + break; 1.7411 + } 1.7412 + 1.7413 + msg.epid = PDU_PID_DEFAULT; 1.7414 + }, 1.7415 + 1.7416 + /** 1.7417 + * Read TP-Data-Coding-Scheme(TP-DCS) 1.7418 + * 1.7419 + * @param msg 1.7420 + * message object for output. 1.7421 + * 1.7422 + * @see 3GPP TS 23.040 9.2.3.10, 3GPP TS 23.038 4. 1.7423 + */ 1.7424 + readDataCodingScheme: function(msg) { 1.7425 + let dcs = this.readHexOctet(); 1.7426 + if (DEBUG) this.context.debug("PDU: read SMS dcs: " + dcs); 1.7427 + 1.7428 + // No message class by default. 1.7429 + let messageClass = PDU_DCS_MSG_CLASS_NORMAL; 1.7430 + // 7 bit is the default fallback encoding. 1.7431 + let encoding = PDU_DCS_MSG_CODING_7BITS_ALPHABET; 1.7432 + switch (dcs & PDU_DCS_CODING_GROUP_BITS) { 1.7433 + case 0x40: // bits 7..4 = 01xx 1.7434 + case 0x50: 1.7435 + case 0x60: 1.7436 + case 0x70: 1.7437 + // Bit 5..0 are coded exactly the same as Group 00xx 1.7438 + case 0x00: // bits 7..4 = 00xx 1.7439 + case 0x10: 1.7440 + case 0x20: 1.7441 + case 0x30: 1.7442 + if (dcs & 0x10) { 1.7443 + messageClass = dcs & PDU_DCS_MSG_CLASS_BITS; 1.7444 + } 1.7445 + switch (dcs & 0x0C) { 1.7446 + case 0x4: 1.7447 + encoding = PDU_DCS_MSG_CODING_8BITS_ALPHABET; 1.7448 + break; 1.7449 + case 0x8: 1.7450 + encoding = PDU_DCS_MSG_CODING_16BITS_ALPHABET; 1.7451 + break; 1.7452 + } 1.7453 + break; 1.7454 + 1.7455 + case 0xE0: // bits 7..4 = 1110 1.7456 + encoding = PDU_DCS_MSG_CODING_16BITS_ALPHABET; 1.7457 + // Bit 3..0 are coded exactly the same as Message Waiting Indication 1.7458 + // Group 1101. 1.7459 + // Fall through. 1.7460 + case 0xC0: // bits 7..4 = 1100 1.7461 + case 0xD0: // bits 7..4 = 1101 1.7462 + // Indiciates voicemail indicator set or clear 1.7463 + let active = (dcs & PDU_DCS_MWI_ACTIVE_BITS) == PDU_DCS_MWI_ACTIVE_VALUE; 1.7464 + 1.7465 + // If TP-UDH is present, these values will be overwritten 1.7466 + switch (dcs & PDU_DCS_MWI_TYPE_BITS) { 1.7467 + case PDU_DCS_MWI_TYPE_VOICEMAIL: 1.7468 + let mwi = msg.mwi; 1.7469 + if (!mwi) { 1.7470 + mwi = msg.mwi = {}; 1.7471 + } 1.7472 + 1.7473 + mwi.active = active; 1.7474 + mwi.discard = (dcs & PDU_DCS_CODING_GROUP_BITS) == 0xC0; 1.7475 + mwi.msgCount = active ? GECKO_VOICEMAIL_MESSAGE_COUNT_UNKNOWN : 0; 1.7476 + 1.7477 + if (DEBUG) { 1.7478 + this.context.debug("MWI in DCS received for voicemail: " + 1.7479 + JSON.stringify(mwi)); 1.7480 + } 1.7481 + break; 1.7482 + case PDU_DCS_MWI_TYPE_FAX: 1.7483 + if (DEBUG) this.context.debug("MWI in DCS received for fax"); 1.7484 + break; 1.7485 + case PDU_DCS_MWI_TYPE_EMAIL: 1.7486 + if (DEBUG) this.context.debug("MWI in DCS received for email"); 1.7487 + break; 1.7488 + default: 1.7489 + if (DEBUG) this.context.debug("MWI in DCS received for \"other\""); 1.7490 + break; 1.7491 + } 1.7492 + break; 1.7493 + 1.7494 + case 0xF0: // bits 7..4 = 1111 1.7495 + if (dcs & 0x04) { 1.7496 + encoding = PDU_DCS_MSG_CODING_8BITS_ALPHABET; 1.7497 + } 1.7498 + messageClass = dcs & PDU_DCS_MSG_CLASS_BITS; 1.7499 + break; 1.7500 + 1.7501 + default: 1.7502 + // Falling back to default encoding. 1.7503 + break; 1.7504 + } 1.7505 + 1.7506 + msg.dcs = dcs; 1.7507 + msg.encoding = encoding; 1.7508 + msg.messageClass = GECKO_SMS_MESSAGE_CLASSES[messageClass]; 1.7509 + 1.7510 + if (DEBUG) this.context.debug("PDU: message encoding is " + encoding + " bit."); 1.7511 + }, 1.7512 + 1.7513 + /** 1.7514 + * Read GSM TP-Service-Centre-Time-Stamp(TP-SCTS). 1.7515 + * 1.7516 + * @see 3GPP TS 23.040 9.2.3.11 1.7517 + */ 1.7518 + readTimestamp: function() { 1.7519 + let year = this.readSwappedNibbleBcdNum(1) + PDU_TIMESTAMP_YEAR_OFFSET; 1.7520 + let month = this.readSwappedNibbleBcdNum(1) - 1; 1.7521 + let day = this.readSwappedNibbleBcdNum(1); 1.7522 + let hour = this.readSwappedNibbleBcdNum(1); 1.7523 + let minute = this.readSwappedNibbleBcdNum(1); 1.7524 + let second = this.readSwappedNibbleBcdNum(1); 1.7525 + let timestamp = Date.UTC(year, month, day, hour, minute, second); 1.7526 + 1.7527 + // If the most significant bit of the least significant nibble is 1, 1.7528 + // the timezone offset is negative (fourth bit from the right => 0x08): 1.7529 + // localtime = UTC + tzOffset 1.7530 + // therefore 1.7531 + // UTC = localtime - tzOffset 1.7532 + let tzOctet = this.readHexOctet(); 1.7533 + let tzOffset = this.octetToBCD(tzOctet & ~0x08) * 15 * 60 * 1000; 1.7534 + tzOffset = (tzOctet & 0x08) ? -tzOffset : tzOffset; 1.7535 + timestamp -= tzOffset; 1.7536 + 1.7537 + return timestamp; 1.7538 + }, 1.7539 + 1.7540 + /** 1.7541 + * Write GSM TP-Service-Centre-Time-Stamp(TP-SCTS). 1.7542 + * 1.7543 + * @see 3GPP TS 23.040 9.2.3.11 1.7544 + */ 1.7545 + writeTimestamp: function(date) { 1.7546 + this.writeSwappedNibbleBCDNum(date.getFullYear() - PDU_TIMESTAMP_YEAR_OFFSET); 1.7547 + 1.7548 + // The value returned by getMonth() is an integer between 0 and 11. 1.7549 + // 0 is corresponds to January, 1 to February, and so on. 1.7550 + this.writeSwappedNibbleBCDNum(date.getMonth() + 1); 1.7551 + this.writeSwappedNibbleBCDNum(date.getDate()); 1.7552 + this.writeSwappedNibbleBCDNum(date.getHours()); 1.7553 + this.writeSwappedNibbleBCDNum(date.getMinutes()); 1.7554 + this.writeSwappedNibbleBCDNum(date.getSeconds()); 1.7555 + 1.7556 + // the value returned by getTimezoneOffset() is the difference, 1.7557 + // in minutes, between UTC and local time. 1.7558 + // For example, if your time zone is UTC+10 (Australian Eastern Standard Time), 1.7559 + // -600 will be returned. 1.7560 + // In TS 23.040 9.2.3.11, the Time Zone field of TP-SCTS indicates 1.7561 + // the different between the local time and GMT. 1.7562 + // And expressed in quarters of an hours. (so need to divid by 15) 1.7563 + let zone = date.getTimezoneOffset() / 15; 1.7564 + let octet = this.BCDToOctet(zone); 1.7565 + 1.7566 + // the bit3 of the Time Zone field represents the algebraic sign. 1.7567 + // (0: positive, 1: negative). 1.7568 + // For example, if the time zone is -0800 GMT, 1.7569 + // 480 will be returned by getTimezoneOffset(). 1.7570 + // In this case, need to mark sign bit as 1. => 0x08 1.7571 + if (zone > 0) { 1.7572 + octet = octet | 0x08; 1.7573 + } 1.7574 + this.writeHexOctet(octet); 1.7575 + }, 1.7576 + 1.7577 + /** 1.7578 + * User data can be 7 bit (default alphabet) data, 8 bit data, or 16 bit 1.7579 + * (UCS2) data. 1.7580 + * 1.7581 + * @param msg 1.7582 + * message object for output. 1.7583 + * @param length 1.7584 + * length of user data to read in octets. 1.7585 + */ 1.7586 + readUserData: function(msg, length) { 1.7587 + if (DEBUG) { 1.7588 + this.context.debug("Reading " + length + " bytes of user data."); 1.7589 + } 1.7590 + 1.7591 + let paddingBits = 0; 1.7592 + if (msg.udhi) { 1.7593 + this.readUserDataHeader(msg); 1.7594 + 1.7595 + if (msg.encoding == PDU_DCS_MSG_CODING_7BITS_ALPHABET) { 1.7596 + let headerBits = (msg.header.length + 1) * 8; 1.7597 + let headerSeptets = Math.ceil(headerBits / 7); 1.7598 + 1.7599 + length -= headerSeptets; 1.7600 + paddingBits = headerSeptets * 7 - headerBits; 1.7601 + } else { 1.7602 + length -= (msg.header.length + 1); 1.7603 + } 1.7604 + } 1.7605 + 1.7606 + if (DEBUG) { 1.7607 + this.context.debug("After header, " + length + " septets left of user data"); 1.7608 + } 1.7609 + 1.7610 + msg.body = null; 1.7611 + msg.data = null; 1.7612 + switch (msg.encoding) { 1.7613 + case PDU_DCS_MSG_CODING_7BITS_ALPHABET: 1.7614 + // 7 bit encoding allows 140 octets, which means 160 characters 1.7615 + // ((140x8) / 7 = 160 chars) 1.7616 + if (length > PDU_MAX_USER_DATA_7BIT) { 1.7617 + if (DEBUG) { 1.7618 + this.context.debug("PDU error: user data is too long: " + length); 1.7619 + } 1.7620 + break; 1.7621 + } 1.7622 + 1.7623 + let langIndex = msg.udhi ? msg.header.langIndex : PDU_NL_IDENTIFIER_DEFAULT; 1.7624 + let langShiftIndex = msg.udhi ? msg.header.langShiftIndex : PDU_NL_IDENTIFIER_DEFAULT; 1.7625 + msg.body = this.readSeptetsToString(length, paddingBits, langIndex, 1.7626 + langShiftIndex); 1.7627 + break; 1.7628 + case PDU_DCS_MSG_CODING_8BITS_ALPHABET: 1.7629 + msg.data = this.readHexOctetArray(length); 1.7630 + break; 1.7631 + case PDU_DCS_MSG_CODING_16BITS_ALPHABET: 1.7632 + msg.body = this.readUCS2String(length); 1.7633 + break; 1.7634 + } 1.7635 + }, 1.7636 + 1.7637 + /** 1.7638 + * Read extra parameters if TP-PI is set. 1.7639 + * 1.7640 + * @param msg 1.7641 + * message object for output. 1.7642 + */ 1.7643 + readExtraParams: function(msg) { 1.7644 + // Because each PDU octet is converted to two UCS2 char2, we should always 1.7645 + // get even messageStringLength in this#_processReceivedSms(). So, we'll 1.7646 + // always need two delimitors at the end. 1.7647 + if (this.context.Buf.getReadAvailable() <= 4) { 1.7648 + return; 1.7649 + } 1.7650 + 1.7651 + // TP-Parameter-Indicator 1.7652 + let pi; 1.7653 + do { 1.7654 + // `The most significant bit in octet 1 and any other TP-PI octets which 1.7655 + // may be added later is reserved as an extension bit which when set to a 1.7656 + // 1 shall indicate that another TP-PI octet follows immediately 1.7657 + // afterwards.` ~ 3GPP TS 23.040 9.2.3.27 1.7658 + pi = this.readHexOctet(); 1.7659 + } while (pi & PDU_PI_EXTENSION); 1.7660 + 1.7661 + // `If the TP-UDL bit is set to "1" but the TP-DCS bit is set to "0" then 1.7662 + // the receiving entity shall for TP-DCS assume a value of 0x00, i.e. the 1.7663 + // 7bit default alphabet.` ~ 3GPP 23.040 9.2.3.27 1.7664 + msg.dcs = 0; 1.7665 + msg.encoding = PDU_DCS_MSG_CODING_7BITS_ALPHABET; 1.7666 + 1.7667 + // TP-Protocol-Identifier 1.7668 + if (pi & PDU_PI_PROTOCOL_IDENTIFIER) { 1.7669 + this.readProtocolIndicator(msg); 1.7670 + } 1.7671 + // TP-Data-Coding-Scheme 1.7672 + if (pi & PDU_PI_DATA_CODING_SCHEME) { 1.7673 + this.readDataCodingScheme(msg); 1.7674 + } 1.7675 + // TP-User-Data-Length 1.7676 + if (pi & PDU_PI_USER_DATA_LENGTH) { 1.7677 + let userDataLength = this.readHexOctet(); 1.7678 + this.readUserData(msg, userDataLength); 1.7679 + } 1.7680 + }, 1.7681 + 1.7682 + /** 1.7683 + * Read and decode a PDU-encoded message from the stream. 1.7684 + * 1.7685 + * TODO: add some basic sanity checks like: 1.7686 + * - do we have the minimum number of chars available 1.7687 + */ 1.7688 + readMessage: function() { 1.7689 + // An empty message object. This gets filled below and then returned. 1.7690 + let msg = { 1.7691 + // D:DELIVER, DR:DELIVER-REPORT, S:SUBMIT, SR:SUBMIT-REPORT, 1.7692 + // ST:STATUS-REPORT, C:COMMAND 1.7693 + // M:Mandatory, O:Optional, X:Unavailable 1.7694 + // D DR S SR ST C 1.7695 + SMSC: null, // M M M M M M 1.7696 + mti: null, // M M M M M M 1.7697 + udhi: null, // M M O M M M 1.7698 + sender: null, // M X X X X X 1.7699 + recipient: null, // X X M X M M 1.7700 + pid: null, // M O M O O M 1.7701 + epid: null, // M O M O O M 1.7702 + dcs: null, // M O M O O X 1.7703 + mwi: null, // O O O O O O 1.7704 + replace: false, // O O O O O O 1.7705 + header: null, // M M O M M M 1.7706 + body: null, // M O M O O O 1.7707 + data: null, // M O M O O O 1.7708 + sentTimestamp: null, // M X X X X X 1.7709 + status: null, // X X X X M X 1.7710 + scts: null, // X X X M M X 1.7711 + dt: null, // X X X X M X 1.7712 + }; 1.7713 + 1.7714 + // SMSC info 1.7715 + let smscLength = this.readHexOctet(); 1.7716 + if (smscLength > 0) { 1.7717 + let smscTypeOfAddress = this.readHexOctet(); 1.7718 + // Subtract the type-of-address octet we just read from the length. 1.7719 + msg.SMSC = this.readSwappedNibbleBcdString(smscLength - 1); 1.7720 + if ((smscTypeOfAddress >> 4) == (PDU_TOA_INTERNATIONAL >> 4)) { 1.7721 + msg.SMSC = '+' + msg.SMSC; 1.7722 + } 1.7723 + } 1.7724 + 1.7725 + // First octet of this SMS-DELIVER or SMS-SUBMIT message 1.7726 + let firstOctet = this.readHexOctet(); 1.7727 + // Message Type Indicator 1.7728 + msg.mti = firstOctet & 0x03; 1.7729 + // User data header indicator 1.7730 + msg.udhi = firstOctet & PDU_UDHI; 1.7731 + 1.7732 + switch (msg.mti) { 1.7733 + case PDU_MTI_SMS_RESERVED: 1.7734 + // `If an MS receives a TPDU with a "Reserved" value in the TP-MTI it 1.7735 + // shall process the message as if it were an "SMS-DELIVER" but store 1.7736 + // the message exactly as received.` ~ 3GPP TS 23.040 9.2.3.1 1.7737 + case PDU_MTI_SMS_DELIVER: 1.7738 + return this.readDeliverMessage(msg); 1.7739 + case PDU_MTI_SMS_STATUS_REPORT: 1.7740 + return this.readStatusReportMessage(msg); 1.7741 + default: 1.7742 + return null; 1.7743 + } 1.7744 + }, 1.7745 + 1.7746 + /** 1.7747 + * Helper for processing received SMS parcel data. 1.7748 + * 1.7749 + * @param length 1.7750 + * Length of SMS string in the incoming parcel. 1.7751 + * 1.7752 + * @return Message parsed or null for invalid message. 1.7753 + */ 1.7754 + processReceivedSms: function(length) { 1.7755 + if (!length) { 1.7756 + if (DEBUG) this.context.debug("Received empty SMS!"); 1.7757 + return [null, PDU_FCS_UNSPECIFIED]; 1.7758 + } 1.7759 + 1.7760 + let Buf = this.context.Buf; 1.7761 + 1.7762 + // An SMS is a string, but we won't read it as such, so let's read the 1.7763 + // string length and then defer to PDU parsing helper. 1.7764 + let messageStringLength = Buf.readInt32(); 1.7765 + if (DEBUG) this.context.debug("Got new SMS, length " + messageStringLength); 1.7766 + let message = this.readMessage(); 1.7767 + if (DEBUG) this.context.debug("Got new SMS: " + JSON.stringify(message)); 1.7768 + 1.7769 + // Read string delimiters. See Buf.readString(). 1.7770 + Buf.readStringDelimiter(length); 1.7771 + 1.7772 + // Determine result 1.7773 + if (!message) { 1.7774 + return [null, PDU_FCS_UNSPECIFIED]; 1.7775 + } 1.7776 + 1.7777 + if (message.epid == PDU_PID_SHORT_MESSAGE_TYPE_0) { 1.7778 + // `A short message type 0 indicates that the ME must acknowledge receipt 1.7779 + // of the short message but shall discard its contents.` ~ 3GPP TS 23.040 1.7780 + // 9.2.3.9 1.7781 + return [null, PDU_FCS_OK]; 1.7782 + } 1.7783 + 1.7784 + if (message.messageClass == GECKO_SMS_MESSAGE_CLASSES[PDU_DCS_MSG_CLASS_2]) { 1.7785 + let RIL = this.context.RIL; 1.7786 + switch (message.epid) { 1.7787 + case PDU_PID_ANSI_136_R_DATA: 1.7788 + case PDU_PID_USIM_DATA_DOWNLOAD: 1.7789 + let ICCUtilsHelper = this.context.ICCUtilsHelper; 1.7790 + if (ICCUtilsHelper.isICCServiceAvailable("DATA_DOWNLOAD_SMS_PP")) { 1.7791 + // `If the service "data download via SMS Point-to-Point" is 1.7792 + // allocated and activated in the (U)SIM Service Table, ... then the 1.7793 + // ME shall pass the message transparently to the UICC using the 1.7794 + // ENVELOPE (SMS-PP DOWNLOAD).` ~ 3GPP TS 31.111 7.1.1.1 1.7795 + RIL.dataDownloadViaSMSPP(message); 1.7796 + 1.7797 + // `the ME shall not display the message, or alert the user of a 1.7798 + // short message waiting.` ~ 3GPP TS 31.111 7.1.1.1 1.7799 + return [null, PDU_FCS_RESERVED]; 1.7800 + } 1.7801 + 1.7802 + // If the service "data download via SMS-PP" is not available in the 1.7803 + // (U)SIM Service Table, ..., then the ME shall store the message in 1.7804 + // EFsms in accordance with TS 31.102` ~ 3GPP TS 31.111 7.1.1.1 1.7805 + 1.7806 + // Fall through. 1.7807 + default: 1.7808 + RIL.writeSmsToSIM(message); 1.7809 + break; 1.7810 + } 1.7811 + } 1.7812 + 1.7813 + // TODO: Bug 739143: B2G SMS: Support SMS Storage Full event 1.7814 + if ((message.messageClass != GECKO_SMS_MESSAGE_CLASSES[PDU_DCS_MSG_CLASS_0]) && !true) { 1.7815 + // `When a mobile terminated message is class 0..., the MS shall display 1.7816 + // the message immediately and send a ACK to the SC ..., irrespective of 1.7817 + // whether there is memory available in the (U)SIM or ME.` ~ 3GPP 23.038 1.7818 + // clause 4. 1.7819 + 1.7820 + if (message.messageClass == GECKO_SMS_MESSAGE_CLASSES[PDU_DCS_MSG_CLASS_2]) { 1.7821 + // `If all the short message storage at the MS is already in use, the 1.7822 + // MS shall return "memory capacity exceeded".` ~ 3GPP 23.038 clause 4. 1.7823 + return [null, PDU_FCS_MEMORY_CAPACITY_EXCEEDED]; 1.7824 + } 1.7825 + 1.7826 + return [null, PDU_FCS_UNSPECIFIED]; 1.7827 + } 1.7828 + 1.7829 + return [message, PDU_FCS_OK]; 1.7830 + }, 1.7831 + 1.7832 + /** 1.7833 + * Read and decode a SMS-DELIVER PDU. 1.7834 + * 1.7835 + * @param msg 1.7836 + * message object for output. 1.7837 + */ 1.7838 + readDeliverMessage: function(msg) { 1.7839 + // - Sender Address info - 1.7840 + let senderAddressLength = this.readHexOctet(); 1.7841 + msg.sender = this.readAddress(senderAddressLength); 1.7842 + // - TP-Protocolo-Identifier - 1.7843 + this.readProtocolIndicator(msg); 1.7844 + // - TP-Data-Coding-Scheme - 1.7845 + this.readDataCodingScheme(msg); 1.7846 + // - TP-Service-Center-Time-Stamp - 1.7847 + msg.sentTimestamp = this.readTimestamp(); 1.7848 + // - TP-User-Data-Length - 1.7849 + let userDataLength = this.readHexOctet(); 1.7850 + 1.7851 + // - TP-User-Data - 1.7852 + if (userDataLength > 0) { 1.7853 + this.readUserData(msg, userDataLength); 1.7854 + } 1.7855 + 1.7856 + return msg; 1.7857 + }, 1.7858 + 1.7859 + /** 1.7860 + * Read and decode a SMS-STATUS-REPORT PDU. 1.7861 + * 1.7862 + * @param msg 1.7863 + * message object for output. 1.7864 + */ 1.7865 + readStatusReportMessage: function(msg) { 1.7866 + // TP-Message-Reference 1.7867 + msg.messageRef = this.readHexOctet(); 1.7868 + // TP-Recipient-Address 1.7869 + let recipientAddressLength = this.readHexOctet(); 1.7870 + msg.recipient = this.readAddress(recipientAddressLength); 1.7871 + // TP-Service-Centre-Time-Stamp 1.7872 + msg.scts = this.readTimestamp(); 1.7873 + // TP-Discharge-Time 1.7874 + msg.dt = this.readTimestamp(); 1.7875 + // TP-Status 1.7876 + msg.status = this.readHexOctet(); 1.7877 + 1.7878 + this.readExtraParams(msg); 1.7879 + 1.7880 + return msg; 1.7881 + }, 1.7882 + 1.7883 + /** 1.7884 + * Serialize a SMS-SUBMIT PDU message and write it to the output stream. 1.7885 + * 1.7886 + * This method expects that a data coding scheme has been chosen already 1.7887 + * and that the length of the user data payload in that encoding is known, 1.7888 + * too. Both go hand in hand together anyway. 1.7889 + * 1.7890 + * @param address 1.7891 + * String containing the address (number) of the SMS receiver 1.7892 + * @param userData 1.7893 + * String containing the message to be sent as user data 1.7894 + * @param dcs 1.7895 + * Data coding scheme. One of the PDU_DCS_MSG_CODING_*BITS_ALPHABET 1.7896 + * constants. 1.7897 + * @param userDataHeaderLength 1.7898 + * Length of embedded user data header, in bytes. The whole header 1.7899 + * size will be userDataHeaderLength + 1; 0 for no header. 1.7900 + * @param encodedBodyLength 1.7901 + * Length of the user data when encoded with the given DCS. For UCS2, 1.7902 + * in bytes; for 7-bit, in septets. 1.7903 + * @param langIndex 1.7904 + * Table index used for normal 7-bit encoded character lookup. 1.7905 + * @param langShiftIndex 1.7906 + * Table index used for escaped 7-bit encoded character lookup. 1.7907 + * @param requestStatusReport 1.7908 + * Request status report. 1.7909 + */ 1.7910 + writeMessage: function(options) { 1.7911 + if (DEBUG) { 1.7912 + this.context.debug("writeMessage: " + JSON.stringify(options)); 1.7913 + } 1.7914 + let Buf = this.context.Buf; 1.7915 + let address = options.number; 1.7916 + let body = options.body; 1.7917 + let dcs = options.dcs; 1.7918 + let userDataHeaderLength = options.userDataHeaderLength; 1.7919 + let encodedBodyLength = options.encodedBodyLength; 1.7920 + let langIndex = options.langIndex; 1.7921 + let langShiftIndex = options.langShiftIndex; 1.7922 + 1.7923 + // SMS-SUBMIT Format: 1.7924 + // 1.7925 + // PDU Type - 1 octet 1.7926 + // Message Reference - 1 octet 1.7927 + // DA - Destination Address - 2 to 12 octets 1.7928 + // PID - Protocol Identifier - 1 octet 1.7929 + // DCS - Data Coding Scheme - 1 octet 1.7930 + // VP - Validity Period - 0, 1 or 7 octets 1.7931 + // UDL - User Data Length - 1 octet 1.7932 + // UD - User Data - 140 octets 1.7933 + 1.7934 + let addressFormat = PDU_TOA_ISDN; // 81 1.7935 + if (address[0] == '+') { 1.7936 + addressFormat = PDU_TOA_INTERNATIONAL | PDU_TOA_ISDN; // 91 1.7937 + address = address.substring(1); 1.7938 + } 1.7939 + //TODO validity is unsupported for now 1.7940 + let validity = 0; 1.7941 + 1.7942 + let headerOctets = (userDataHeaderLength ? userDataHeaderLength + 1 : 0); 1.7943 + let paddingBits; 1.7944 + let userDataLengthInSeptets; 1.7945 + let userDataLengthInOctets; 1.7946 + if (dcs == PDU_DCS_MSG_CODING_7BITS_ALPHABET) { 1.7947 + let headerSeptets = Math.ceil(headerOctets * 8 / 7); 1.7948 + userDataLengthInSeptets = headerSeptets + encodedBodyLength; 1.7949 + userDataLengthInOctets = Math.ceil(userDataLengthInSeptets * 7 / 8); 1.7950 + paddingBits = headerSeptets * 7 - headerOctets * 8; 1.7951 + } else { 1.7952 + userDataLengthInOctets = headerOctets + encodedBodyLength; 1.7953 + paddingBits = 0; 1.7954 + } 1.7955 + 1.7956 + let pduOctetLength = 4 + // PDU Type, Message Ref, address length + format 1.7957 + Math.ceil(address.length / 2) + 1.7958 + 3 + // PID, DCS, UDL 1.7959 + userDataLengthInOctets; 1.7960 + if (validity) { 1.7961 + //TODO: add more to pduOctetLength 1.7962 + } 1.7963 + 1.7964 + // Start the string. Since octets are represented in hex, we will need 1.7965 + // twice as many characters as octets. 1.7966 + Buf.writeInt32(pduOctetLength * 2); 1.7967 + 1.7968 + // - PDU-TYPE- 1.7969 + 1.7970 + // +--------+----------+---------+---------+--------+---------+ 1.7971 + // | RP (1) | UDHI (1) | SRR (1) | VPF (2) | RD (1) | MTI (2) | 1.7972 + // +--------+----------+---------+---------+--------+---------+ 1.7973 + // RP: 0 Reply path parameter is not set 1.7974 + // 1 Reply path parameter is set 1.7975 + // UDHI: 0 The UD Field contains only the short message 1.7976 + // 1 The beginning of the UD field contains a header in addition 1.7977 + // of the short message 1.7978 + // SRR: 0 A status report is not requested 1.7979 + // 1 A status report is requested 1.7980 + // VPF: bit4 bit3 1.7981 + // 0 0 VP field is not present 1.7982 + // 0 1 Reserved 1.7983 + // 1 0 VP field present an integer represented (relative) 1.7984 + // 1 1 VP field present a semi-octet represented (absolute) 1.7985 + // RD: Instruct the SMSC to accept(0) or reject(1) an SMS-SUBMIT 1.7986 + // for a short message still held in the SMSC which has the same 1.7987 + // MR and DA as a previously submitted short message from the 1.7988 + // same OA 1.7989 + // MTI: bit1 bit0 Message Type 1.7990 + // 0 0 SMS-DELIVER (SMSC ==> MS) 1.7991 + // 0 1 SMS-SUBMIT (MS ==> SMSC) 1.7992 + 1.7993 + // PDU type. MTI is set to SMS-SUBMIT 1.7994 + let firstOctet = PDU_MTI_SMS_SUBMIT; 1.7995 + 1.7996 + // Status-Report-Request 1.7997 + if (options.requestStatusReport) { 1.7998 + firstOctet |= PDU_SRI_SRR; 1.7999 + } 1.8000 + 1.8001 + // Validity period 1.8002 + if (validity) { 1.8003 + //TODO: not supported yet, OR with one of PDU_VPF_* 1.8004 + } 1.8005 + // User data header indicator 1.8006 + if (headerOctets) { 1.8007 + firstOctet |= PDU_UDHI; 1.8008 + } 1.8009 + this.writeHexOctet(firstOctet); 1.8010 + 1.8011 + // Message reference 00 1.8012 + this.writeHexOctet(0x00); 1.8013 + 1.8014 + // - Destination Address - 1.8015 + this.writeHexOctet(address.length); 1.8016 + this.writeHexOctet(addressFormat); 1.8017 + this.writeSwappedNibbleBCD(address); 1.8018 + 1.8019 + // - Protocol Identifier - 1.8020 + this.writeHexOctet(0x00); 1.8021 + 1.8022 + // - Data coding scheme - 1.8023 + // For now it assumes bits 7..4 = 1111 except for the 16 bits use case 1.8024 + this.writeHexOctet(dcs); 1.8025 + 1.8026 + // - Validity Period - 1.8027 + if (validity) { 1.8028 + this.writeHexOctet(validity); 1.8029 + } 1.8030 + 1.8031 + // - User Data - 1.8032 + if (dcs == PDU_DCS_MSG_CODING_7BITS_ALPHABET) { 1.8033 + this.writeHexOctet(userDataLengthInSeptets); 1.8034 + } else { 1.8035 + this.writeHexOctet(userDataLengthInOctets); 1.8036 + } 1.8037 + 1.8038 + if (headerOctets) { 1.8039 + this.writeUserDataHeader(options); 1.8040 + } 1.8041 + 1.8042 + switch (dcs) { 1.8043 + case PDU_DCS_MSG_CODING_7BITS_ALPHABET: 1.8044 + this.writeStringAsSeptets(body, paddingBits, langIndex, langShiftIndex); 1.8045 + break; 1.8046 + case PDU_DCS_MSG_CODING_8BITS_ALPHABET: 1.8047 + // Unsupported. 1.8048 + break; 1.8049 + case PDU_DCS_MSG_CODING_16BITS_ALPHABET: 1.8050 + this.writeUCS2String(body); 1.8051 + break; 1.8052 + } 1.8053 + 1.8054 + // End of the string. The string length is always even by definition, so 1.8055 + // we write two \0 delimiters. 1.8056 + Buf.writeUint16(0); 1.8057 + Buf.writeUint16(0); 1.8058 + }, 1.8059 + 1.8060 + /** 1.8061 + * Read GSM CBS message serial number. 1.8062 + * 1.8063 + * @param msg 1.8064 + * message object for output. 1.8065 + * 1.8066 + * @see 3GPP TS 23.041 section 9.4.1.2.1 1.8067 + */ 1.8068 + readCbSerialNumber: function(msg) { 1.8069 + let Buf = this.context.Buf; 1.8070 + msg.serial = Buf.readUint8() << 8 | Buf.readUint8(); 1.8071 + msg.geographicalScope = (msg.serial >>> 14) & 0x03; 1.8072 + msg.messageCode = (msg.serial >>> 4) & 0x03FF; 1.8073 + msg.updateNumber = msg.serial & 0x0F; 1.8074 + }, 1.8075 + 1.8076 + /** 1.8077 + * Read GSM CBS message message identifier. 1.8078 + * 1.8079 + * @param msg 1.8080 + * message object for output. 1.8081 + * 1.8082 + * @see 3GPP TS 23.041 section 9.4.1.2.2 1.8083 + */ 1.8084 + readCbMessageIdentifier: function(msg) { 1.8085 + let Buf = this.context.Buf; 1.8086 + msg.messageId = Buf.readUint8() << 8 | Buf.readUint8(); 1.8087 + 1.8088 + if ((msg.format != CB_FORMAT_ETWS) 1.8089 + && (msg.messageId >= CB_GSM_MESSAGEID_ETWS_BEGIN) 1.8090 + && (msg.messageId <= CB_GSM_MESSAGEID_ETWS_END)) { 1.8091 + // `In the case of transmitting CBS message for ETWS, a part of 1.8092 + // Message Code can be used to command mobile terminals to activate 1.8093 + // emergency user alert and message popup in order to alert the users.` 1.8094 + msg.etws = { 1.8095 + emergencyUserAlert: msg.messageCode & 0x0200 ? true : false, 1.8096 + popup: msg.messageCode & 0x0100 ? true : false 1.8097 + }; 1.8098 + 1.8099 + let warningType = msg.messageId - CB_GSM_MESSAGEID_ETWS_BEGIN; 1.8100 + if (warningType < CB_ETWS_WARNING_TYPE_NAMES.length) { 1.8101 + msg.etws.warningType = warningType; 1.8102 + } 1.8103 + } 1.8104 + }, 1.8105 + 1.8106 + /** 1.8107 + * Read CBS Data Coding Scheme. 1.8108 + * 1.8109 + * @param msg 1.8110 + * message object for output. 1.8111 + * 1.8112 + * @see 3GPP TS 23.038 section 5. 1.8113 + */ 1.8114 + readCbDataCodingScheme: function(msg) { 1.8115 + let dcs = this.context.Buf.readUint8(); 1.8116 + if (DEBUG) this.context.debug("PDU: read CBS dcs: " + dcs); 1.8117 + 1.8118 + let language = null, hasLanguageIndicator = false; 1.8119 + // `Any reserved codings shall be assumed to be the GSM 7bit default 1.8120 + // alphabet.` 1.8121 + let encoding = PDU_DCS_MSG_CODING_7BITS_ALPHABET; 1.8122 + let messageClass = PDU_DCS_MSG_CLASS_NORMAL; 1.8123 + 1.8124 + switch (dcs & PDU_DCS_CODING_GROUP_BITS) { 1.8125 + case 0x00: // 0000 1.8126 + language = CB_DCS_LANG_GROUP_1[dcs & 0x0F]; 1.8127 + break; 1.8128 + 1.8129 + case 0x10: // 0001 1.8130 + switch (dcs & 0x0F) { 1.8131 + case 0x00: 1.8132 + hasLanguageIndicator = true; 1.8133 + break; 1.8134 + case 0x01: 1.8135 + encoding = PDU_DCS_MSG_CODING_16BITS_ALPHABET; 1.8136 + hasLanguageIndicator = true; 1.8137 + break; 1.8138 + } 1.8139 + break; 1.8140 + 1.8141 + case 0x20: // 0010 1.8142 + language = CB_DCS_LANG_GROUP_2[dcs & 0x0F]; 1.8143 + break; 1.8144 + 1.8145 + case 0x40: // 01xx 1.8146 + case 0x50: 1.8147 + //case 0x60: Text Compression, not supported 1.8148 + //case 0x70: Text Compression, not supported 1.8149 + case 0x90: // 1001 1.8150 + encoding = (dcs & 0x0C); 1.8151 + if (encoding == 0x0C) { 1.8152 + encoding = PDU_DCS_MSG_CODING_7BITS_ALPHABET; 1.8153 + } 1.8154 + messageClass = (dcs & PDU_DCS_MSG_CLASS_BITS); 1.8155 + break; 1.8156 + 1.8157 + case 0xF0: 1.8158 + encoding = (dcs & 0x04) ? PDU_DCS_MSG_CODING_8BITS_ALPHABET 1.8159 + : PDU_DCS_MSG_CODING_7BITS_ALPHABET; 1.8160 + switch(dcs & PDU_DCS_MSG_CLASS_BITS) { 1.8161 + case 0x01: messageClass = PDU_DCS_MSG_CLASS_USER_1; break; 1.8162 + case 0x02: messageClass = PDU_DCS_MSG_CLASS_USER_2; break; 1.8163 + case 0x03: messageClass = PDU_DCS_MSG_CLASS_3; break; 1.8164 + } 1.8165 + break; 1.8166 + 1.8167 + case 0x30: // 0011 (Reserved) 1.8168 + case 0x80: // 1000 (Reserved) 1.8169 + case 0xA0: // 1010..1100 (Reserved) 1.8170 + case 0xB0: 1.8171 + case 0xC0: 1.8172 + break; 1.8173 + 1.8174 + default: 1.8175 + throw new Error("Unsupported CBS data coding scheme: " + dcs); 1.8176 + } 1.8177 + 1.8178 + msg.dcs = dcs; 1.8179 + msg.encoding = encoding; 1.8180 + msg.language = language; 1.8181 + msg.messageClass = GECKO_SMS_MESSAGE_CLASSES[messageClass]; 1.8182 + msg.hasLanguageIndicator = hasLanguageIndicator; 1.8183 + }, 1.8184 + 1.8185 + /** 1.8186 + * Read GSM CBS message page parameter. 1.8187 + * 1.8188 + * @param msg 1.8189 + * message object for output. 1.8190 + * 1.8191 + * @see 3GPP TS 23.041 section 9.4.1.2.4 1.8192 + */ 1.8193 + readCbPageParameter: function(msg) { 1.8194 + let octet = this.context.Buf.readUint8(); 1.8195 + msg.pageIndex = (octet >>> 4) & 0x0F; 1.8196 + msg.numPages = octet & 0x0F; 1.8197 + if (!msg.pageIndex || !msg.numPages) { 1.8198 + // `If a mobile receives the code 0000 in either the first field or the 1.8199 + // second field then it shall treat the CBS message exactly the same as a 1.8200 + // CBS message with page parameter 0001 0001 (i.e. a single page message).` 1.8201 + msg.pageIndex = msg.numPages = 1; 1.8202 + } 1.8203 + }, 1.8204 + 1.8205 + /** 1.8206 + * Read ETWS Primary Notification message warning type. 1.8207 + * 1.8208 + * @param msg 1.8209 + * message object for output. 1.8210 + * 1.8211 + * @see 3GPP TS 23.041 section 9.3.24 1.8212 + */ 1.8213 + readCbWarningType: function(msg) { 1.8214 + let Buf = this.context.Buf; 1.8215 + let word = Buf.readUint8() << 8 | Buf.readUint8(); 1.8216 + msg.etws = { 1.8217 + warningType: (word >>> 9) & 0x7F, 1.8218 + popup: word & 0x80 ? true : false, 1.8219 + emergencyUserAlert: word & 0x100 ? true : false 1.8220 + }; 1.8221 + }, 1.8222 + 1.8223 + /** 1.8224 + * Read CBS-Message-Information-Page 1.8225 + * 1.8226 + * @param msg 1.8227 + * message object for output. 1.8228 + * @param length 1.8229 + * length of cell broadcast data to read in octets. 1.8230 + * 1.8231 + * @see 3GPP TS 23.041 section 9.3.19 1.8232 + */ 1.8233 + readGsmCbData: function(msg, length) { 1.8234 + let Buf = this.context.Buf; 1.8235 + let bufAdapter = { 1.8236 + context: this.context, 1.8237 + readHexOctet: function() { 1.8238 + return Buf.readUint8(); 1.8239 + } 1.8240 + }; 1.8241 + 1.8242 + msg.body = null; 1.8243 + msg.data = null; 1.8244 + switch (msg.encoding) { 1.8245 + case PDU_DCS_MSG_CODING_7BITS_ALPHABET: 1.8246 + msg.body = this.readSeptetsToString.call(bufAdapter, 1.8247 + (length * 8 / 7), 0, 1.8248 + PDU_NL_IDENTIFIER_DEFAULT, 1.8249 + PDU_NL_IDENTIFIER_DEFAULT); 1.8250 + if (msg.hasLanguageIndicator) { 1.8251 + msg.language = msg.body.substring(0, 2); 1.8252 + msg.body = msg.body.substring(3); 1.8253 + } 1.8254 + break; 1.8255 + 1.8256 + case PDU_DCS_MSG_CODING_8BITS_ALPHABET: 1.8257 + msg.data = Buf.readUint8Array(length); 1.8258 + break; 1.8259 + 1.8260 + case PDU_DCS_MSG_CODING_16BITS_ALPHABET: 1.8261 + if (msg.hasLanguageIndicator) { 1.8262 + msg.language = this.readSeptetsToString.call(bufAdapter, 2, 0, 1.8263 + PDU_NL_IDENTIFIER_DEFAULT, 1.8264 + PDU_NL_IDENTIFIER_DEFAULT); 1.8265 + length -= 2; 1.8266 + } 1.8267 + msg.body = this.readUCS2String.call(bufAdapter, length); 1.8268 + break; 1.8269 + } 1.8270 + }, 1.8271 + 1.8272 + /** 1.8273 + * Read Cell GSM/ETWS/UMTS Broadcast Message. 1.8274 + * 1.8275 + * @param pduLength 1.8276 + * total length of the incoming PDU in octets. 1.8277 + */ 1.8278 + readCbMessage: function(pduLength) { 1.8279 + // Validity GSM ETWS UMTS 1.8280 + let msg = { 1.8281 + // Internally used in ril_worker: 1.8282 + serial: null, // O O O 1.8283 + updateNumber: null, // O O O 1.8284 + format: null, // O O O 1.8285 + dcs: 0x0F, // O X O 1.8286 + encoding: PDU_DCS_MSG_CODING_7BITS_ALPHABET, // O X O 1.8287 + hasLanguageIndicator: false, // O X O 1.8288 + data: null, // O X O 1.8289 + body: null, // O X O 1.8290 + pageIndex: 1, // O X X 1.8291 + numPages: 1, // O X X 1.8292 + 1.8293 + // DOM attributes: 1.8294 + geographicalScope: null, // O O O 1.8295 + messageCode: null, // O O O 1.8296 + messageId: null, // O O O 1.8297 + language: null, // O X O 1.8298 + fullBody: null, // O X O 1.8299 + fullData: null, // O X O 1.8300 + messageClass: GECKO_SMS_MESSAGE_CLASSES[PDU_DCS_MSG_CLASS_NORMAL], // O x O 1.8301 + etws: null // ? O ? 1.8302 + /*{ 1.8303 + warningType: null, // X O X 1.8304 + popup: false, // X O X 1.8305 + emergencyUserAlert: false, // X O X 1.8306 + }*/ 1.8307 + }; 1.8308 + 1.8309 + if (pduLength <= CB_MESSAGE_SIZE_ETWS) { 1.8310 + msg.format = CB_FORMAT_ETWS; 1.8311 + return this.readEtwsCbMessage(msg); 1.8312 + } 1.8313 + 1.8314 + if (pduLength <= CB_MESSAGE_SIZE_GSM) { 1.8315 + msg.format = CB_FORMAT_GSM; 1.8316 + return this.readGsmCbMessage(msg, pduLength); 1.8317 + } 1.8318 + 1.8319 + return null; 1.8320 + }, 1.8321 + 1.8322 + /** 1.8323 + * Read GSM Cell Broadcast Message. 1.8324 + * 1.8325 + * @param msg 1.8326 + * message object for output. 1.8327 + * @param pduLength 1.8328 + * total length of the incomint PDU in octets. 1.8329 + * 1.8330 + * @see 3GPP TS 23.041 clause 9.4.1.2 1.8331 + */ 1.8332 + readGsmCbMessage: function(msg, pduLength) { 1.8333 + this.readCbSerialNumber(msg); 1.8334 + this.readCbMessageIdentifier(msg); 1.8335 + this.readCbDataCodingScheme(msg); 1.8336 + this.readCbPageParameter(msg); 1.8337 + 1.8338 + // GSM CB message header takes 6 octets. 1.8339 + this.readGsmCbData(msg, pduLength - 6); 1.8340 + 1.8341 + return msg; 1.8342 + }, 1.8343 + 1.8344 + /** 1.8345 + * Read ETWS Primary Notification Message. 1.8346 + * 1.8347 + * @param msg 1.8348 + * message object for output. 1.8349 + * 1.8350 + * @see 3GPP TS 23.041 clause 9.4.1.3 1.8351 + */ 1.8352 + readEtwsCbMessage: function(msg) { 1.8353 + this.readCbSerialNumber(msg); 1.8354 + this.readCbMessageIdentifier(msg); 1.8355 + this.readCbWarningType(msg); 1.8356 + 1.8357 + // Octet 7..56 is Warning Security Information. However, according to 1.8358 + // section 9.4.1.3.6, `The UE shall ignore this parameter.` So we just skip 1.8359 + // processing it here. 1.8360 + 1.8361 + return msg; 1.8362 + }, 1.8363 + 1.8364 + /** 1.8365 + * Read network name. 1.8366 + * 1.8367 + * @param len Length of the information element. 1.8368 + * @return 1.8369 + * { 1.8370 + * networkName: network name. 1.8371 + * shouldIncludeCi: Should Country's initials included in text string. 1.8372 + * } 1.8373 + * @see TS 24.008 clause 10.5.3.5a. 1.8374 + */ 1.8375 + readNetworkName: function(len) { 1.8376 + // According to TS 24.008 Sec. 10.5.3.5a, the first octet is: 1.8377 + // bit 8: must be 1. 1.8378 + // bit 5-7: Text encoding. 1.8379 + // 000 - GSM default alphabet. 1.8380 + // 001 - UCS2 (16 bit). 1.8381 + // else - reserved. 1.8382 + // bit 4: MS should add the letters for Country's Initials and a space 1.8383 + // to the text string if this bit is true. 1.8384 + // bit 1-3: number of spare bits in last octet. 1.8385 + 1.8386 + let codingInfo = this.readHexOctet(); 1.8387 + if (!(codingInfo & 0x80)) { 1.8388 + return null; 1.8389 + } 1.8390 + 1.8391 + let textEncoding = (codingInfo & 0x70) >> 4; 1.8392 + let shouldIncludeCountryInitials = !!(codingInfo & 0x08); 1.8393 + let spareBits = codingInfo & 0x07; 1.8394 + let resultString; 1.8395 + 1.8396 + switch (textEncoding) { 1.8397 + case 0: 1.8398 + // GSM Default alphabet. 1.8399 + resultString = this.readSeptetsToString( 1.8400 + ((len - 1) * 8 - spareBits) / 7, 0, 1.8401 + PDU_NL_IDENTIFIER_DEFAULT, 1.8402 + PDU_NL_IDENTIFIER_DEFAULT); 1.8403 + break; 1.8404 + case 1: 1.8405 + // UCS2 encoded. 1.8406 + resultString = this.readUCS2String(len - 1); 1.8407 + break; 1.8408 + default: 1.8409 + // Not an available text coding. 1.8410 + return null; 1.8411 + } 1.8412 + 1.8413 + // TODO - Bug 820286: According to shouldIncludeCountryInitials, add 1.8414 + // country initials to the resulting string. 1.8415 + return resultString; 1.8416 + } 1.8417 +}; 1.8418 + 1.8419 +/** 1.8420 + * Provide buffer with bitwise read/write function so make encoding/decoding easier. 1.8421 + */ 1.8422 +function BitBufferHelperObject(/* unused */aContext) { 1.8423 + this.readBuffer = []; 1.8424 + this.writeBuffer = []; 1.8425 +} 1.8426 +BitBufferHelperObject.prototype = { 1.8427 + readCache: 0, 1.8428 + readCacheSize: 0, 1.8429 + readBuffer: null, 1.8430 + readIndex: 0, 1.8431 + writeCache: 0, 1.8432 + writeCacheSize: 0, 1.8433 + writeBuffer: null, 1.8434 + 1.8435 + // Max length is 32 because we use integer as read/write cache. 1.8436 + // All read/write functions are implemented based on bitwise operation. 1.8437 + readBits: function(length) { 1.8438 + if (length <= 0 || length > 32) { 1.8439 + return null; 1.8440 + } 1.8441 + 1.8442 + if (length > this.readCacheSize) { 1.8443 + let bytesToRead = Math.ceil((length - this.readCacheSize) / 8); 1.8444 + for(let i = 0; i < bytesToRead; i++) { 1.8445 + this.readCache = (this.readCache << 8) | (this.readBuffer[this.readIndex++] & 0xFF); 1.8446 + this.readCacheSize += 8; 1.8447 + } 1.8448 + } 1.8449 + 1.8450 + let bitOffset = (this.readCacheSize - length), 1.8451 + resultMask = (1 << length) - 1, 1.8452 + result = 0; 1.8453 + 1.8454 + result = (this.readCache >> bitOffset) & resultMask; 1.8455 + this.readCacheSize -= length; 1.8456 + 1.8457 + return result; 1.8458 + }, 1.8459 + 1.8460 + backwardReadPilot: function(length) { 1.8461 + if (length <= 0) { 1.8462 + return; 1.8463 + } 1.8464 + 1.8465 + // Zero-based position. 1.8466 + let bitIndexToRead = this.readIndex * 8 - this.readCacheSize - length; 1.8467 + 1.8468 + if (bitIndexToRead < 0) { 1.8469 + return; 1.8470 + } 1.8471 + 1.8472 + // Update readIndex, readCache, readCacheSize accordingly. 1.8473 + let readBits = bitIndexToRead % 8; 1.8474 + this.readIndex = Math.floor(bitIndexToRead / 8) + ((readBits) ? 1 : 0); 1.8475 + this.readCache = (readBits) ? this.readBuffer[this.readIndex - 1] : 0; 1.8476 + this.readCacheSize = (readBits) ? (8 - readBits) : 0; 1.8477 + }, 1.8478 + 1.8479 + writeBits: function(value, length) { 1.8480 + if (length <= 0 || length > 32) { 1.8481 + return; 1.8482 + } 1.8483 + 1.8484 + let totalLength = length + this.writeCacheSize; 1.8485 + 1.8486 + // 8-byte cache not full 1.8487 + if (totalLength < 8) { 1.8488 + let valueMask = (1 << length) - 1; 1.8489 + this.writeCache = (this.writeCache << length) | (value & valueMask); 1.8490 + this.writeCacheSize += length; 1.8491 + return; 1.8492 + } 1.8493 + 1.8494 + // Deal with unaligned part 1.8495 + if (this.writeCacheSize) { 1.8496 + let mergeLength = 8 - this.writeCacheSize, 1.8497 + valueMask = (1 << mergeLength) - 1; 1.8498 + 1.8499 + this.writeCache = (this.writeCache << mergeLength) | ((value >> (length - mergeLength)) & valueMask); 1.8500 + this.writeBuffer.push(this.writeCache & 0xFF); 1.8501 + length -= mergeLength; 1.8502 + } 1.8503 + 1.8504 + // Aligned part, just copy 1.8505 + while (length >= 8) { 1.8506 + length -= 8; 1.8507 + this.writeBuffer.push((value >> length) & 0xFF); 1.8508 + } 1.8509 + 1.8510 + // Rest part is saved into cache 1.8511 + this.writeCacheSize = length; 1.8512 + this.writeCache = value & ((1 << length) - 1); 1.8513 + 1.8514 + return; 1.8515 + }, 1.8516 + 1.8517 + // Drop what still in read cache and goto next 8-byte alignment. 1.8518 + // There might be a better naming. 1.8519 + nextOctetAlign: function() { 1.8520 + this.readCache = 0; 1.8521 + this.readCacheSize = 0; 1.8522 + }, 1.8523 + 1.8524 + // Flush current write cache to Buf with padding 0s. 1.8525 + // There might be a better naming. 1.8526 + flushWithPadding: function() { 1.8527 + if (this.writeCacheSize) { 1.8528 + this.writeBuffer.push(this.writeCache << (8 - this.writeCacheSize)); 1.8529 + } 1.8530 + this.writeCache = 0; 1.8531 + this.writeCacheSize = 0; 1.8532 + }, 1.8533 + 1.8534 + startWrite: function(dataBuffer) { 1.8535 + this.writeBuffer = dataBuffer; 1.8536 + this.writeCache = 0; 1.8537 + this.writeCacheSize = 0; 1.8538 + }, 1.8539 + 1.8540 + startRead: function(dataBuffer) { 1.8541 + this.readBuffer = dataBuffer; 1.8542 + this.readCache = 0; 1.8543 + this.readCacheSize = 0; 1.8544 + this.readIndex = 0; 1.8545 + }, 1.8546 + 1.8547 + getWriteBufferSize: function() { 1.8548 + return this.writeBuffer.length; 1.8549 + }, 1.8550 + 1.8551 + overwriteWriteBuffer: function(position, data) { 1.8552 + let writeLength = data.length; 1.8553 + if (writeLength + position >= this.writeBuffer.length) { 1.8554 + writeLength = this.writeBuffer.length - position; 1.8555 + } 1.8556 + for (let i = 0; i < writeLength; i++) { 1.8557 + this.writeBuffer[i + position] = data[i]; 1.8558 + } 1.8559 + } 1.8560 +}; 1.8561 + 1.8562 +/** 1.8563 + * Helper for CDMA PDU 1.8564 + * 1.8565 + * Currently, some function are shared with GsmPDUHelper, they should be 1.8566 + * moved from GsmPDUHelper to a common object shared among GsmPDUHelper and 1.8567 + * CdmaPDUHelper. 1.8568 + */ 1.8569 +function CdmaPDUHelperObject(aContext) { 1.8570 + this.context = aContext; 1.8571 +} 1.8572 +CdmaPDUHelperObject.prototype = { 1.8573 + context: null, 1.8574 + 1.8575 + // 1..........C 1.8576 + // Only "1234567890*#" is defined in C.S0005-D v2.0 1.8577 + dtmfChars: ".1234567890*#...", 1.8578 + 1.8579 + /** 1.8580 + * Entry point for SMS encoding, the options object is made compatible 1.8581 + * with existing writeMessage() of GsmPDUHelper, but less key is used. 1.8582 + * 1.8583 + * Current used key in options: 1.8584 + * @param number 1.8585 + * String containing the address (number) of the SMS receiver 1.8586 + * @param body 1.8587 + * String containing the message to be sent, segmented part 1.8588 + * @param dcs 1.8589 + * Data coding scheme. One of the PDU_DCS_MSG_CODING_*BITS_ALPHABET 1.8590 + * constants. 1.8591 + * @param encodedBodyLength 1.8592 + * Length of the user data when encoded with the given DCS. For UCS2, 1.8593 + * in bytes; for 7-bit, in septets. 1.8594 + * @param requestStatusReport 1.8595 + * Request status report. 1.8596 + * @param segmentRef 1.8597 + * Reference number of concatenated SMS message 1.8598 + * @param segmentMaxSeq 1.8599 + * Total number of concatenated SMS message 1.8600 + * @param segmentSeq 1.8601 + * Sequence number of concatenated SMS message 1.8602 + */ 1.8603 + writeMessage: function(options) { 1.8604 + if (DEBUG) { 1.8605 + this.context.debug("cdma_writeMessage: " + JSON.stringify(options)); 1.8606 + } 1.8607 + 1.8608 + // Get encoding 1.8609 + options.encoding = this.gsmDcsToCdmaEncoding(options.dcs); 1.8610 + 1.8611 + // Common Header 1.8612 + if (options.segmentMaxSeq > 1) { 1.8613 + this.writeInt(PDU_CDMA_MSG_TELESERIVCIE_ID_WEMT); 1.8614 + } else { 1.8615 + this.writeInt(PDU_CDMA_MSG_TELESERIVCIE_ID_SMS); 1.8616 + } 1.8617 + 1.8618 + this.writeInt(0); 1.8619 + this.writeInt(PDU_CDMA_MSG_CATEGORY_UNSPEC); 1.8620 + 1.8621 + // Just fill out address info in byte, rild will encap them for us 1.8622 + let addrInfo = this.encodeAddr(options.number); 1.8623 + this.writeByte(addrInfo.digitMode); 1.8624 + this.writeByte(addrInfo.numberMode); 1.8625 + this.writeByte(addrInfo.numberType); 1.8626 + this.writeByte(addrInfo.numberPlan); 1.8627 + this.writeByte(addrInfo.address.length); 1.8628 + for (let i = 0; i < addrInfo.address.length; i++) { 1.8629 + this.writeByte(addrInfo.address[i]); 1.8630 + } 1.8631 + 1.8632 + // Subaddress, not supported 1.8633 + this.writeByte(0); // Subaddress : Type 1.8634 + this.writeByte(0); // Subaddress : Odd 1.8635 + this.writeByte(0); // Subaddress : length 1.8636 + 1.8637 + // User Data 1.8638 + let encodeResult = this.encodeUserData(options); 1.8639 + this.writeByte(encodeResult.length); 1.8640 + for (let i = 0; i < encodeResult.length; i++) { 1.8641 + this.writeByte(encodeResult[i]); 1.8642 + } 1.8643 + 1.8644 + encodeResult = null; 1.8645 + }, 1.8646 + 1.8647 + /** 1.8648 + * Data writters 1.8649 + */ 1.8650 + writeInt: function(value) { 1.8651 + this.context.Buf.writeInt32(value); 1.8652 + }, 1.8653 + 1.8654 + writeByte: function(value) { 1.8655 + this.context.Buf.writeInt32(value & 0xFF); 1.8656 + }, 1.8657 + 1.8658 + /** 1.8659 + * Transform GSM DCS to CDMA encoding. 1.8660 + */ 1.8661 + gsmDcsToCdmaEncoding: function(encoding) { 1.8662 + switch (encoding) { 1.8663 + case PDU_DCS_MSG_CODING_7BITS_ALPHABET: 1.8664 + return PDU_CDMA_MSG_CODING_7BITS_ASCII; 1.8665 + case PDU_DCS_MSG_CODING_8BITS_ALPHABET: 1.8666 + return PDU_CDMA_MSG_CODING_OCTET; 1.8667 + case PDU_DCS_MSG_CODING_16BITS_ALPHABET: 1.8668 + return PDU_CDMA_MSG_CODING_UNICODE; 1.8669 + } 1.8670 + throw new Error("gsmDcsToCdmaEncoding(): Invalid GSM SMS DCS value: " + encoding); 1.8671 + }, 1.8672 + 1.8673 + /** 1.8674 + * Encode address into CDMA address format, as a byte array. 1.8675 + * 1.8676 + * @see 3GGP2 C.S0015-B 2.0, 3.4.3.3 Address Parameters 1.8677 + * 1.8678 + * @param address 1.8679 + * String of address to be encoded 1.8680 + */ 1.8681 + encodeAddr: function(address) { 1.8682 + let result = {}; 1.8683 + 1.8684 + result.numberType = PDU_CDMA_MSG_ADDR_NUMBER_TYPE_UNKNOWN; 1.8685 + result.numberPlan = PDU_CDMA_MSG_ADDR_NUMBER_TYPE_UNKNOWN; 1.8686 + 1.8687 + if (address[0] === '+') { 1.8688 + address = address.substring(1); 1.8689 + } 1.8690 + 1.8691 + // Try encode with DTMF first 1.8692 + result.digitMode = PDU_CDMA_MSG_ADDR_DIGIT_MODE_DTMF; 1.8693 + result.numberMode = PDU_CDMA_MSG_ADDR_NUMBER_MODE_ANSI; 1.8694 + 1.8695 + result.address = []; 1.8696 + for (let i = 0; i < address.length; i++) { 1.8697 + let addrDigit = this.dtmfChars.indexOf(address.charAt(i)); 1.8698 + if (addrDigit < 0) { 1.8699 + result.digitMode = PDU_CDMA_MSG_ADDR_DIGIT_MODE_ASCII; 1.8700 + result.numberMode = PDU_CDMA_MSG_ADDR_NUMBER_MODE_ASCII; 1.8701 + result.address = []; 1.8702 + break; 1.8703 + } 1.8704 + result.address.push(addrDigit); 1.8705 + } 1.8706 + 1.8707 + // Address can't be encoded with DTMF, then use 7-bit ASCII 1.8708 + if (result.digitMode !== PDU_CDMA_MSG_ADDR_DIGIT_MODE_DTMF) { 1.8709 + if (address.indexOf("@") !== -1) { 1.8710 + result.numberType = PDU_CDMA_MSG_ADDR_NUMBER_TYPE_NATIONAL; 1.8711 + } 1.8712 + 1.8713 + for (let i = 0; i < address.length; i++) { 1.8714 + result.address.push(address.charCodeAt(i) & 0x7F); 1.8715 + } 1.8716 + } 1.8717 + 1.8718 + return result; 1.8719 + }, 1.8720 + 1.8721 + /** 1.8722 + * Encode SMS contents in options into CDMA userData field. 1.8723 + * Corresponding and required subparameters will be added automatically. 1.8724 + * 1.8725 + * @see 3GGP2 C.S0015-B 2.0, 3.4.3.7 Bearer Data 1.8726 + * 4.5 Bearer Data Parameters 1.8727 + * 1.8728 + * Current used key in options: 1.8729 + * @param body 1.8730 + * String containing the message to be sent, segmented part 1.8731 + * @param encoding 1.8732 + * Encoding method of CDMA, can be transformed from GSM DCS by function 1.8733 + * cdmaPduHelp.gsmDcsToCdmaEncoding() 1.8734 + * @param encodedBodyLength 1.8735 + * Length of the user data when encoded with the given DCS. For UCS2, 1.8736 + * in bytes; for 7-bit, in septets. 1.8737 + * @param requestStatusReport 1.8738 + * Request status report. 1.8739 + * @param segmentRef 1.8740 + * Reference number of concatenated SMS message 1.8741 + * @param segmentMaxSeq 1.8742 + * Total number of concatenated SMS message 1.8743 + * @param segmentSeq 1.8744 + * Sequence number of concatenated SMS message 1.8745 + */ 1.8746 + encodeUserData: function(options) { 1.8747 + let userDataBuffer = []; 1.8748 + this.context.BitBufferHelper.startWrite(userDataBuffer); 1.8749 + 1.8750 + // Message Identifier 1.8751 + this.encodeUserDataMsgId(options); 1.8752 + 1.8753 + // User Data 1.8754 + this.encodeUserDataMsg(options); 1.8755 + 1.8756 + // Reply Option 1.8757 + this.encodeUserDataReplyOption(options); 1.8758 + 1.8759 + return userDataBuffer; 1.8760 + }, 1.8761 + 1.8762 + /** 1.8763 + * User data subparameter encoder : Message Identifier 1.8764 + * 1.8765 + * @see 3GGP2 C.S0015-B 2.0, 4.5.1 Message Identifier 1.8766 + */ 1.8767 + encodeUserDataMsgId: function(options) { 1.8768 + let BitBufferHelper = this.context.BitBufferHelper; 1.8769 + BitBufferHelper.writeBits(PDU_CDMA_MSG_USERDATA_MSG_ID, 8); 1.8770 + BitBufferHelper.writeBits(3, 8); 1.8771 + BitBufferHelper.writeBits(PDU_CDMA_MSG_TYPE_SUBMIT, 4); 1.8772 + BitBufferHelper.writeBits(1, 16); // TODO: How to get message ID? 1.8773 + if (options.segmentMaxSeq > 1) { 1.8774 + BitBufferHelper.writeBits(1, 1); 1.8775 + } else { 1.8776 + BitBufferHelper.writeBits(0, 1); 1.8777 + } 1.8778 + 1.8779 + BitBufferHelper.flushWithPadding(); 1.8780 + }, 1.8781 + 1.8782 + /** 1.8783 + * User data subparameter encoder : User Data 1.8784 + * 1.8785 + * @see 3GGP2 C.S0015-B 2.0, 4.5.2 User Data 1.8786 + */ 1.8787 + encodeUserDataMsg: function(options) { 1.8788 + let BitBufferHelper = this.context.BitBufferHelper; 1.8789 + BitBufferHelper.writeBits(PDU_CDMA_MSG_USERDATA_BODY, 8); 1.8790 + // Reserve space for length 1.8791 + BitBufferHelper.writeBits(0, 8); 1.8792 + let lengthPosition = BitBufferHelper.getWriteBufferSize(); 1.8793 + 1.8794 + BitBufferHelper.writeBits(options.encoding, 5); 1.8795 + 1.8796 + // Add user data header for message segement 1.8797 + let msgBody = options.body, 1.8798 + msgBodySize = (options.encoding === PDU_CDMA_MSG_CODING_7BITS_ASCII ? 1.8799 + options.encodedBodyLength : 1.8800 + msgBody.length); 1.8801 + if (options.segmentMaxSeq > 1) { 1.8802 + if (options.encoding === PDU_CDMA_MSG_CODING_7BITS_ASCII) { 1.8803 + BitBufferHelper.writeBits(msgBodySize + 7, 8); // Required length for user data header, in septet(7-bit) 1.8804 + 1.8805 + BitBufferHelper.writeBits(5, 8); // total header length 5 bytes 1.8806 + BitBufferHelper.writeBits(PDU_IEI_CONCATENATED_SHORT_MESSAGES_8BIT, 8); // header id 0 1.8807 + BitBufferHelper.writeBits(3, 8); // length of element for id 0 is 3 1.8808 + BitBufferHelper.writeBits(options.segmentRef & 0xFF, 8); // Segement reference 1.8809 + BitBufferHelper.writeBits(options.segmentMaxSeq & 0xFF, 8); // Max segment 1.8810 + BitBufferHelper.writeBits(options.segmentSeq & 0xFF, 8); // Current segment 1.8811 + BitBufferHelper.writeBits(0, 1); // Padding to make header data septet(7-bit) aligned 1.8812 + } else { 1.8813 + if (options.encoding === PDU_CDMA_MSG_CODING_UNICODE) { 1.8814 + BitBufferHelper.writeBits(msgBodySize + 3, 8); // Required length for user data header, in 16-bit 1.8815 + } else { 1.8816 + BitBufferHelper.writeBits(msgBodySize + 6, 8); // Required length for user data header, in octet(8-bit) 1.8817 + } 1.8818 + 1.8819 + BitBufferHelper.writeBits(5, 8); // total header length 5 bytes 1.8820 + BitBufferHelper.writeBits(PDU_IEI_CONCATENATED_SHORT_MESSAGES_8BIT, 8); // header id 0 1.8821 + BitBufferHelper.writeBits(3, 8); // length of element for id 0 is 3 1.8822 + BitBufferHelper.writeBits(options.segmentRef & 0xFF, 8); // Segement reference 1.8823 + BitBufferHelper.writeBits(options.segmentMaxSeq & 0xFF, 8); // Max segment 1.8824 + BitBufferHelper.writeBits(options.segmentSeq & 0xFF, 8); // Current segment 1.8825 + } 1.8826 + } else { 1.8827 + BitBufferHelper.writeBits(msgBodySize, 8); 1.8828 + } 1.8829 + 1.8830 + // Encode message based on encoding method 1.8831 + const langTable = PDU_NL_LOCKING_SHIFT_TABLES[PDU_NL_IDENTIFIER_DEFAULT]; 1.8832 + const langShiftTable = PDU_NL_SINGLE_SHIFT_TABLES[PDU_NL_IDENTIFIER_DEFAULT]; 1.8833 + for (let i = 0; i < msgBody.length; i++) { 1.8834 + switch (options.encoding) { 1.8835 + case PDU_CDMA_MSG_CODING_OCTET: { 1.8836 + let msgDigit = msgBody.charCodeAt(i); 1.8837 + BitBufferHelper.writeBits(msgDigit, 8); 1.8838 + break; 1.8839 + } 1.8840 + case PDU_CDMA_MSG_CODING_7BITS_ASCII: { 1.8841 + let msgDigit = msgBody.charCodeAt(i), 1.8842 + msgDigitChar = msgBody.charAt(i); 1.8843 + 1.8844 + if (msgDigit >= 32) { 1.8845 + BitBufferHelper.writeBits(msgDigit, 7); 1.8846 + } else { 1.8847 + msgDigit = langTable.indexOf(msgDigitChar); 1.8848 + 1.8849 + if (msgDigit === PDU_NL_EXTENDED_ESCAPE) { 1.8850 + break; 1.8851 + } 1.8852 + if (msgDigit >= 0) { 1.8853 + BitBufferHelper.writeBits(msgDigit, 7); 1.8854 + } else { 1.8855 + msgDigit = langShiftTable.indexOf(msgDigitChar); 1.8856 + if (msgDigit == -1) { 1.8857 + throw new Error("'" + msgDigitChar + "' is not in 7 bit alphabet " 1.8858 + + langIndex + ":" + langShiftIndex + "!"); 1.8859 + } 1.8860 + 1.8861 + if (msgDigit === PDU_NL_RESERVED_CONTROL) { 1.8862 + break; 1.8863 + } 1.8864 + 1.8865 + BitBufferHelper.writeBits(PDU_NL_EXTENDED_ESCAPE, 7); 1.8866 + BitBufferHelper.writeBits(msgDigit, 7); 1.8867 + } 1.8868 + } 1.8869 + break; 1.8870 + } 1.8871 + case PDU_CDMA_MSG_CODING_UNICODE: { 1.8872 + let msgDigit = msgBody.charCodeAt(i); 1.8873 + BitBufferHelper.writeBits(msgDigit, 16); 1.8874 + break; 1.8875 + } 1.8876 + } 1.8877 + } 1.8878 + BitBufferHelper.flushWithPadding(); 1.8879 + 1.8880 + // Fill length 1.8881 + let currentPosition = BitBufferHelper.getWriteBufferSize(); 1.8882 + BitBufferHelper.overwriteWriteBuffer(lengthPosition - 1, [currentPosition - lengthPosition]); 1.8883 + }, 1.8884 + 1.8885 + /** 1.8886 + * User data subparameter encoder : Reply Option 1.8887 + * 1.8888 + * @see 3GGP2 C.S0015-B 2.0, 4.5.11 Reply Option 1.8889 + */ 1.8890 + encodeUserDataReplyOption: function(options) { 1.8891 + if (options.requestStatusReport) { 1.8892 + let BitBufferHelper = this.context.BitBufferHelper; 1.8893 + BitBufferHelper.writeBits(PDU_CDMA_MSG_USERDATA_REPLY_OPTION, 8); 1.8894 + BitBufferHelper.writeBits(1, 8); 1.8895 + BitBufferHelper.writeBits(0, 1); // USER_ACK_REQ 1.8896 + BitBufferHelper.writeBits(1, 1); // DAK_REQ 1.8897 + BitBufferHelper.flushWithPadding(); 1.8898 + } 1.8899 + }, 1.8900 + 1.8901 + /** 1.8902 + * Entry point for SMS decoding, the returned object is made compatible 1.8903 + * with existing readMessage() of GsmPDUHelper 1.8904 + */ 1.8905 + readMessage: function() { 1.8906 + let message = {}; 1.8907 + 1.8908 + // Teleservice Identifier 1.8909 + message.teleservice = this.readInt(); 1.8910 + 1.8911 + // Message Type 1.8912 + let isServicePresent = this.readByte(); 1.8913 + if (isServicePresent) { 1.8914 + message.messageType = PDU_CDMA_MSG_TYPE_BROADCAST; 1.8915 + } else { 1.8916 + if (message.teleservice) { 1.8917 + message.messageType = PDU_CDMA_MSG_TYPE_P2P; 1.8918 + } else { 1.8919 + message.messageType = PDU_CDMA_MSG_TYPE_ACK; 1.8920 + } 1.8921 + } 1.8922 + 1.8923 + // Service Category 1.8924 + message.service = this.readInt(); 1.8925 + 1.8926 + // Originated Address 1.8927 + let addrInfo = {}; 1.8928 + addrInfo.digitMode = (this.readInt() & 0x01); 1.8929 + addrInfo.numberMode = (this.readInt() & 0x01); 1.8930 + addrInfo.numberType = (this.readInt() & 0x01); 1.8931 + addrInfo.numberPlan = (this.readInt() & 0x01); 1.8932 + addrInfo.addrLength = this.readByte(); 1.8933 + addrInfo.address = []; 1.8934 + for (let i = 0; i < addrInfo.addrLength; i++) { 1.8935 + addrInfo.address.push(this.readByte()); 1.8936 + } 1.8937 + message.sender = this.decodeAddr(addrInfo); 1.8938 + 1.8939 + // Originated Subaddress 1.8940 + addrInfo.Type = (this.readInt() & 0x07); 1.8941 + addrInfo.Odd = (this.readByte() & 0x01); 1.8942 + addrInfo.addrLength = this.readByte(); 1.8943 + for (let i = 0; i < addrInfo.addrLength; i++) { 1.8944 + let addrDigit = this.readByte(); 1.8945 + message.sender += String.fromCharCode(addrDigit); 1.8946 + } 1.8947 + 1.8948 + // Bearer Data 1.8949 + this.decodeUserData(message); 1.8950 + 1.8951 + // Bearer Data Sub-Parameter: User Data 1.8952 + let userData = message[PDU_CDMA_MSG_USERDATA_BODY]; 1.8953 + [message.header, message.body, message.encoding, message.data] = 1.8954 + (userData) ? [userData.header, userData.body, userData.encoding, userData.data] 1.8955 + : [null, null, null, null]; 1.8956 + 1.8957 + // Bearer Data Sub-Parameter: Message Status 1.8958 + // Success Delivery (0) if both Message Status and User Data are absent. 1.8959 + // Message Status absent (-1) if only User Data is available. 1.8960 + let msgStatus = message[PDU_CDMA_MSG_USER_DATA_MSG_STATUS]; 1.8961 + [message.errorClass, message.msgStatus] = 1.8962 + (msgStatus) ? [msgStatus.errorClass, msgStatus.msgStatus] 1.8963 + : ((message.body) ? [-1, -1] : [0, 0]); 1.8964 + 1.8965 + // Transform message to GSM msg 1.8966 + let msg = { 1.8967 + SMSC: "", 1.8968 + mti: 0, 1.8969 + udhi: 0, 1.8970 + sender: message.sender, 1.8971 + recipient: null, 1.8972 + pid: PDU_PID_DEFAULT, 1.8973 + epid: PDU_PID_DEFAULT, 1.8974 + dcs: 0, 1.8975 + mwi: null, 1.8976 + replace: false, 1.8977 + header: message.header, 1.8978 + body: message.body, 1.8979 + data: message.data, 1.8980 + sentTimestamp: message[PDU_CDMA_MSG_USERDATA_TIMESTAMP], 1.8981 + language: message[PDU_CDMA_LANGUAGE_INDICATOR], 1.8982 + status: null, 1.8983 + scts: null, 1.8984 + dt: null, 1.8985 + encoding: message.encoding, 1.8986 + messageClass: GECKO_SMS_MESSAGE_CLASSES[PDU_DCS_MSG_CLASS_NORMAL], 1.8987 + messageType: message.messageType, 1.8988 + serviceCategory: message.service, 1.8989 + subMsgType: message[PDU_CDMA_MSG_USERDATA_MSG_ID].msgType, 1.8990 + msgId: message[PDU_CDMA_MSG_USERDATA_MSG_ID].msgId, 1.8991 + errorClass: message.errorClass, 1.8992 + msgStatus: message.msgStatus, 1.8993 + teleservice: message.teleservice 1.8994 + }; 1.8995 + 1.8996 + return msg; 1.8997 + }, 1.8998 + 1.8999 + /** 1.9000 + * Helper for processing received SMS parcel data. 1.9001 + * 1.9002 + * @param length 1.9003 + * Length of SMS string in the incoming parcel. 1.9004 + * 1.9005 + * @return Message parsed or null for invalid message. 1.9006 + */ 1.9007 + processReceivedSms: function(length) { 1.9008 + if (!length) { 1.9009 + if (DEBUG) this.context.debug("Received empty SMS!"); 1.9010 + return [null, PDU_FCS_UNSPECIFIED]; 1.9011 + } 1.9012 + 1.9013 + let message = this.readMessage(); 1.9014 + if (DEBUG) this.context.debug("Got new SMS: " + JSON.stringify(message)); 1.9015 + 1.9016 + // Determine result 1.9017 + if (!message) { 1.9018 + return [null, PDU_FCS_UNSPECIFIED]; 1.9019 + } 1.9020 + 1.9021 + return [message, PDU_FCS_OK]; 1.9022 + }, 1.9023 + 1.9024 + /** 1.9025 + * Data readers 1.9026 + */ 1.9027 + readInt: function() { 1.9028 + return this.context.Buf.readInt32(); 1.9029 + }, 1.9030 + 1.9031 + readByte: function() { 1.9032 + return (this.context.Buf.readInt32() & 0xFF); 1.9033 + }, 1.9034 + 1.9035 + /** 1.9036 + * Decode CDMA address data into address string 1.9037 + * 1.9038 + * @see 3GGP2 C.S0015-B 2.0, 3.4.3.3 Address Parameters 1.9039 + * 1.9040 + * Required key in addrInfo 1.9041 + * @param addrLength 1.9042 + * Length of address 1.9043 + * @param digitMode 1.9044 + * Address encoding method 1.9045 + * @param address 1.9046 + * Array of encoded address data 1.9047 + */ 1.9048 + decodeAddr: function(addrInfo) { 1.9049 + let result = ""; 1.9050 + for (let i = 0; i < addrInfo.addrLength; i++) { 1.9051 + if (addrInfo.digitMode === PDU_CDMA_MSG_ADDR_DIGIT_MODE_DTMF) { 1.9052 + result += this.dtmfChars.charAt(addrInfo.address[i]); 1.9053 + } else { 1.9054 + result += String.fromCharCode(addrInfo.address[i]); 1.9055 + } 1.9056 + } 1.9057 + return result; 1.9058 + }, 1.9059 + 1.9060 + /** 1.9061 + * Read userData in parcel buffer and decode into message object. 1.9062 + * Each subparameter will be stored in corresponding key. 1.9063 + * 1.9064 + * @see 3GGP2 C.S0015-B 2.0, 3.4.3.7 Bearer Data 1.9065 + * 4.5 Bearer Data Parameters 1.9066 + */ 1.9067 + decodeUserData: function(message) { 1.9068 + let userDataLength = this.readInt(); 1.9069 + 1.9070 + while (userDataLength > 0) { 1.9071 + let id = this.readByte(), 1.9072 + length = this.readByte(), 1.9073 + userDataBuffer = []; 1.9074 + 1.9075 + for (let i = 0; i < length; i++) { 1.9076 + userDataBuffer.push(this.readByte()); 1.9077 + } 1.9078 + 1.9079 + this.context.BitBufferHelper.startRead(userDataBuffer); 1.9080 + 1.9081 + switch (id) { 1.9082 + case PDU_CDMA_MSG_USERDATA_MSG_ID: 1.9083 + message[id] = this.decodeUserDataMsgId(); 1.9084 + break; 1.9085 + case PDU_CDMA_MSG_USERDATA_BODY: 1.9086 + message[id] = this.decodeUserDataMsg(message[PDU_CDMA_MSG_USERDATA_MSG_ID].userHeader); 1.9087 + break; 1.9088 + case PDU_CDMA_MSG_USERDATA_TIMESTAMP: 1.9089 + message[id] = this.decodeUserDataTimestamp(); 1.9090 + break; 1.9091 + case PDU_CDMA_MSG_USERDATA_REPLY_OPTION: 1.9092 + message[id] = this.decodeUserDataReplyOption(); 1.9093 + break; 1.9094 + case PDU_CDMA_LANGUAGE_INDICATOR: 1.9095 + message[id] = this.decodeLanguageIndicator(); 1.9096 + break; 1.9097 + case PDU_CDMA_MSG_USERDATA_CALLBACK_NUMBER: 1.9098 + message[id] = this.decodeUserDataCallbackNumber(); 1.9099 + break; 1.9100 + case PDU_CDMA_MSG_USER_DATA_MSG_STATUS: 1.9101 + message[id] = this.decodeUserDataMsgStatus(); 1.9102 + break; 1.9103 + } 1.9104 + 1.9105 + userDataLength -= (length + 2); 1.9106 + userDataBuffer = []; 1.9107 + } 1.9108 + }, 1.9109 + 1.9110 + /** 1.9111 + * User data subparameter decoder: Message Identifier 1.9112 + * 1.9113 + * @see 3GGP2 C.S0015-B 2.0, 4.5.1 Message Identifier 1.9114 + */ 1.9115 + decodeUserDataMsgId: function() { 1.9116 + let result = {}; 1.9117 + let BitBufferHelper = this.context.BitBufferHelper; 1.9118 + result.msgType = BitBufferHelper.readBits(4); 1.9119 + result.msgId = BitBufferHelper.readBits(16); 1.9120 + result.userHeader = BitBufferHelper.readBits(1); 1.9121 + 1.9122 + return result; 1.9123 + }, 1.9124 + 1.9125 + /** 1.9126 + * Decode user data header, we only care about segment information 1.9127 + * on CDMA. 1.9128 + * 1.9129 + * This function is mostly copied from gsmPduHelper.readUserDataHeader() but 1.9130 + * change the read function, because CDMA user header decoding is't byte-wise 1.9131 + * aligned. 1.9132 + */ 1.9133 + decodeUserDataHeader: function(encoding) { 1.9134 + let BitBufferHelper = this.context.BitBufferHelper; 1.9135 + let header = {}, 1.9136 + headerSize = BitBufferHelper.readBits(8), 1.9137 + userDataHeaderSize = headerSize + 1, 1.9138 + headerPaddingBits = 0; 1.9139 + 1.9140 + // Calculate header size 1.9141 + if (encoding === PDU_DCS_MSG_CODING_7BITS_ALPHABET) { 1.9142 + // Length is in 7-bit 1.9143 + header.length = Math.ceil(userDataHeaderSize * 8 / 7); 1.9144 + // Calulate padding length 1.9145 + headerPaddingBits = (header.length * 7) - (userDataHeaderSize * 8); 1.9146 + } else if (encoding === PDU_DCS_MSG_CODING_8BITS_ALPHABET) { 1.9147 + header.length = userDataHeaderSize; 1.9148 + } else { 1.9149 + header.length = userDataHeaderSize / 2; 1.9150 + } 1.9151 + 1.9152 + while (headerSize) { 1.9153 + let identifier = BitBufferHelper.readBits(8), 1.9154 + length = BitBufferHelper.readBits(8); 1.9155 + 1.9156 + headerSize -= (2 + length); 1.9157 + 1.9158 + switch (identifier) { 1.9159 + case PDU_IEI_CONCATENATED_SHORT_MESSAGES_8BIT: { 1.9160 + let ref = BitBufferHelper.readBits(8), 1.9161 + max = BitBufferHelper.readBits(8), 1.9162 + seq = BitBufferHelper.readBits(8); 1.9163 + if (max && seq && (seq <= max)) { 1.9164 + header.segmentRef = ref; 1.9165 + header.segmentMaxSeq = max; 1.9166 + header.segmentSeq = seq; 1.9167 + } 1.9168 + break; 1.9169 + } 1.9170 + case PDU_IEI_APPLICATION_PORT_ADDRESSING_SCHEME_8BIT: { 1.9171 + let dstp = BitBufferHelper.readBits(8), 1.9172 + orip = BitBufferHelper.readBits(8); 1.9173 + if ((dstp < PDU_APA_RESERVED_8BIT_PORTS) 1.9174 + || (orip < PDU_APA_RESERVED_8BIT_PORTS)) { 1.9175 + // 3GPP TS 23.040 clause 9.2.3.24.3: "A receiving entity shall 1.9176 + // ignore any information element where the value of the 1.9177 + // Information-Element-Data is Reserved or not supported" 1.9178 + break; 1.9179 + } 1.9180 + header.destinationPort = dstp; 1.9181 + header.originatorPort = orip; 1.9182 + break; 1.9183 + } 1.9184 + case PDU_IEI_APPLICATION_PORT_ADDRESSING_SCHEME_16BIT: { 1.9185 + let dstp = (BitBufferHelper.readBits(8) << 8) | BitBufferHelper.readBits(8), 1.9186 + orip = (BitBufferHelper.readBits(8) << 8) | BitBufferHelper.readBits(8); 1.9187 + // 3GPP TS 23.040 clause 9.2.3.24.4: "A receiving entity shall 1.9188 + // ignore any information element where the value of the 1.9189 + // Information-Element-Data is Reserved or not supported" 1.9190 + if ((dstp < PDU_APA_VALID_16BIT_PORTS) 1.9191 + && (orip < PDU_APA_VALID_16BIT_PORTS)) { 1.9192 + header.destinationPort = dstp; 1.9193 + header.originatorPort = orip; 1.9194 + } 1.9195 + break; 1.9196 + } 1.9197 + case PDU_IEI_CONCATENATED_SHORT_MESSAGES_16BIT: { 1.9198 + let ref = (BitBufferHelper.readBits(8) << 8) | BitBufferHelper.readBits(8), 1.9199 + max = BitBufferHelper.readBits(8), 1.9200 + seq = BitBufferHelper.readBits(8); 1.9201 + if (max && seq && (seq <= max)) { 1.9202 + header.segmentRef = ref; 1.9203 + header.segmentMaxSeq = max; 1.9204 + header.segmentSeq = seq; 1.9205 + } 1.9206 + break; 1.9207 + } 1.9208 + case PDU_IEI_NATIONAL_LANGUAGE_SINGLE_SHIFT: { 1.9209 + let langShiftIndex = BitBufferHelper.readBits(8); 1.9210 + if (langShiftIndex < PDU_NL_SINGLE_SHIFT_TABLES.length) { 1.9211 + header.langShiftIndex = langShiftIndex; 1.9212 + } 1.9213 + break; 1.9214 + } 1.9215 + case PDU_IEI_NATIONAL_LANGUAGE_LOCKING_SHIFT: { 1.9216 + let langIndex = BitBufferHelper.readBits(8); 1.9217 + if (langIndex < PDU_NL_LOCKING_SHIFT_TABLES.length) { 1.9218 + header.langIndex = langIndex; 1.9219 + } 1.9220 + break; 1.9221 + } 1.9222 + case PDU_IEI_SPECIAL_SMS_MESSAGE_INDICATION: { 1.9223 + let msgInd = BitBufferHelper.readBits(8) & 0xFF, 1.9224 + msgCount = BitBufferHelper.readBits(8); 1.9225 + /* 1.9226 + * TS 23.040 V6.8.1 Sec 9.2.3.24.2 1.9227 + * bits 1 0 : basic message indication type 1.9228 + * bits 4 3 2 : extended message indication type 1.9229 + * bits 6 5 : Profile id 1.9230 + * bit 7 : storage type 1.9231 + */ 1.9232 + let storeType = msgInd & PDU_MWI_STORE_TYPE_BIT; 1.9233 + header.mwi = {}; 1.9234 + mwi = header.mwi; 1.9235 + 1.9236 + if (storeType == PDU_MWI_STORE_TYPE_STORE) { 1.9237 + // Store message because TP_UDH indicates so, note this may override 1.9238 + // the setting in DCS, but that is expected 1.9239 + mwi.discard = false; 1.9240 + } else if (mwi.discard === undefined) { 1.9241 + // storeType == PDU_MWI_STORE_TYPE_DISCARD 1.9242 + // only override mwi.discard here if it hasn't already been set 1.9243 + mwi.discard = true; 1.9244 + } 1.9245 + 1.9246 + mwi.msgCount = msgCount & 0xFF; 1.9247 + mwi.active = mwi.msgCount > 0; 1.9248 + 1.9249 + if (DEBUG) { 1.9250 + this.context.debug("MWI in TP_UDH received: " + JSON.stringify(mwi)); 1.9251 + } 1.9252 + break; 1.9253 + } 1.9254 + default: 1.9255 + // Drop unsupported id 1.9256 + for (let i = 0; i < length; i++) { 1.9257 + BitBufferHelper.readBits(8); 1.9258 + } 1.9259 + } 1.9260 + } 1.9261 + 1.9262 + // Consume padding bits 1.9263 + if (headerPaddingBits) { 1.9264 + BitBufferHelper.readBits(headerPaddingBits); 1.9265 + } 1.9266 + 1.9267 + return header; 1.9268 + }, 1.9269 + 1.9270 + getCdmaMsgEncoding: function(encoding) { 1.9271 + // Determine encoding method 1.9272 + switch (encoding) { 1.9273 + case PDU_CDMA_MSG_CODING_7BITS_ASCII: 1.9274 + case PDU_CDMA_MSG_CODING_IA5: 1.9275 + case PDU_CDMA_MSG_CODING_7BITS_GSM: 1.9276 + return PDU_DCS_MSG_CODING_7BITS_ALPHABET; 1.9277 + case PDU_CDMA_MSG_CODING_OCTET: 1.9278 + case PDU_CDMA_MSG_CODING_IS_91: 1.9279 + case PDU_CDMA_MSG_CODING_LATIN_HEBREW: 1.9280 + case PDU_CDMA_MSG_CODING_LATIN: 1.9281 + return PDU_DCS_MSG_CODING_8BITS_ALPHABET; 1.9282 + case PDU_CDMA_MSG_CODING_UNICODE: 1.9283 + case PDU_CDMA_MSG_CODING_SHIFT_JIS: 1.9284 + case PDU_CDMA_MSG_CODING_KOREAN: 1.9285 + return PDU_DCS_MSG_CODING_16BITS_ALPHABET; 1.9286 + } 1.9287 + return null; 1.9288 + }, 1.9289 + 1.9290 + decodeCdmaPDUMsg: function(encoding, msgType, msgBodySize) { 1.9291 + const langTable = PDU_NL_LOCKING_SHIFT_TABLES[PDU_NL_IDENTIFIER_DEFAULT]; 1.9292 + const langShiftTable = PDU_NL_SINGLE_SHIFT_TABLES[PDU_NL_IDENTIFIER_DEFAULT]; 1.9293 + let BitBufferHelper = this.context.BitBufferHelper; 1.9294 + let result = ""; 1.9295 + let msgDigit; 1.9296 + switch (encoding) { 1.9297 + case PDU_CDMA_MSG_CODING_OCTET: // TODO : Require Test 1.9298 + while(msgBodySize > 0) { 1.9299 + msgDigit = String.fromCharCode(BitBufferHelper.readBits(8)); 1.9300 + result += msgDigit; 1.9301 + msgBodySize--; 1.9302 + } 1.9303 + break; 1.9304 + case PDU_CDMA_MSG_CODING_IS_91: // TODO : Require Test 1.9305 + // Referenced from android code 1.9306 + switch (msgType) { 1.9307 + case PDU_CDMA_MSG_CODING_IS_91_TYPE_SMS: 1.9308 + case PDU_CDMA_MSG_CODING_IS_91_TYPE_SMS_FULL: 1.9309 + case PDU_CDMA_MSG_CODING_IS_91_TYPE_VOICEMAIL_STATUS: 1.9310 + while(msgBodySize > 0) { 1.9311 + msgDigit = String.fromCharCode(BitBufferHelper.readBits(6) + 0x20); 1.9312 + result += msgDigit; 1.9313 + msgBodySize--; 1.9314 + } 1.9315 + break; 1.9316 + case PDU_CDMA_MSG_CODING_IS_91_TYPE_CLI: 1.9317 + let addrInfo = {}; 1.9318 + addrInfo.digitMode = PDU_CDMA_MSG_ADDR_DIGIT_MODE_DTMF; 1.9319 + addrInfo.numberMode = PDU_CDMA_MSG_ADDR_NUMBER_MODE_ANSI; 1.9320 + addrInfo.numberType = PDU_CDMA_MSG_ADDR_NUMBER_TYPE_UNKNOWN; 1.9321 + addrInfo.numberPlan = PDU_CDMA_MSG_ADDR_NUMBER_PLAN_UNKNOWN; 1.9322 + addrInfo.addrLength = msgBodySize; 1.9323 + addrInfo.address = []; 1.9324 + for (let i = 0; i < addrInfo.addrLength; i++) { 1.9325 + addrInfo.address.push(BitBufferHelper.readBits(4)); 1.9326 + } 1.9327 + result = this.decodeAddr(addrInfo); 1.9328 + break; 1.9329 + } 1.9330 + // Fall through. 1.9331 + case PDU_CDMA_MSG_CODING_7BITS_ASCII: 1.9332 + case PDU_CDMA_MSG_CODING_IA5: // TODO : Require Test 1.9333 + while(msgBodySize > 0) { 1.9334 + msgDigit = BitBufferHelper.readBits(7); 1.9335 + if (msgDigit >= 32) { 1.9336 + msgDigit = String.fromCharCode(msgDigit); 1.9337 + } else { 1.9338 + if (msgDigit !== PDU_NL_EXTENDED_ESCAPE) { 1.9339 + msgDigit = langTable[msgDigit]; 1.9340 + } else { 1.9341 + msgDigit = BitBufferHelper.readBits(7); 1.9342 + msgBodySize--; 1.9343 + msgDigit = langShiftTable[msgDigit]; 1.9344 + } 1.9345 + } 1.9346 + result += msgDigit; 1.9347 + msgBodySize--; 1.9348 + } 1.9349 + break; 1.9350 + case PDU_CDMA_MSG_CODING_UNICODE: 1.9351 + while(msgBodySize > 0) { 1.9352 + msgDigit = String.fromCharCode(BitBufferHelper.readBits(16)); 1.9353 + result += msgDigit; 1.9354 + msgBodySize--; 1.9355 + } 1.9356 + break; 1.9357 + case PDU_CDMA_MSG_CODING_7BITS_GSM: // TODO : Require Test 1.9358 + while(msgBodySize > 0) { 1.9359 + msgDigit = BitBufferHelper.readBits(7); 1.9360 + if (msgDigit !== PDU_NL_EXTENDED_ESCAPE) { 1.9361 + msgDigit = langTable[msgDigit]; 1.9362 + } else { 1.9363 + msgDigit = BitBufferHelper.readBits(7); 1.9364 + msgBodySize--; 1.9365 + msgDigit = langShiftTable[msgDigit]; 1.9366 + } 1.9367 + result += msgDigit; 1.9368 + msgBodySize--; 1.9369 + } 1.9370 + break; 1.9371 + case PDU_CDMA_MSG_CODING_LATIN: // TODO : Require Test 1.9372 + // Reference : http://en.wikipedia.org/wiki/ISO/IEC_8859-1 1.9373 + while(msgBodySize > 0) { 1.9374 + msgDigit = String.fromCharCode(BitBufferHelper.readBits(8)); 1.9375 + result += msgDigit; 1.9376 + msgBodySize--; 1.9377 + } 1.9378 + break; 1.9379 + case PDU_CDMA_MSG_CODING_LATIN_HEBREW: // TODO : Require Test 1.9380 + // Reference : http://en.wikipedia.org/wiki/ISO/IEC_8859-8 1.9381 + while(msgBodySize > 0) { 1.9382 + msgDigit = BitBufferHelper.readBits(8); 1.9383 + if (msgDigit === 0xDF) { 1.9384 + msgDigit = String.fromCharCode(0x2017); 1.9385 + } else if (msgDigit === 0xFD) { 1.9386 + msgDigit = String.fromCharCode(0x200E); 1.9387 + } else if (msgDigit === 0xFE) { 1.9388 + msgDigit = String.fromCharCode(0x200F); 1.9389 + } else if (msgDigit >= 0xE0 && msgDigit <= 0xFA) { 1.9390 + msgDigit = String.fromCharCode(0x4F0 + msgDigit); 1.9391 + } else { 1.9392 + msgDigit = String.fromCharCode(msgDigit); 1.9393 + } 1.9394 + result += msgDigit; 1.9395 + msgBodySize--; 1.9396 + } 1.9397 + break; 1.9398 + case PDU_CDMA_MSG_CODING_SHIFT_JIS: 1.9399 + // Reference : http://msdn.microsoft.com/en-US/goglobal/cc305152.aspx 1.9400 + // http://demo.icu-project.org/icu-bin/convexp?conv=Shift_JIS 1.9401 + let shift_jis_message = []; 1.9402 + 1.9403 + while (msgBodySize > 0) { 1.9404 + shift_jis_message.push(BitBufferHelper.readBits(8)); 1.9405 + msgBodySize--; 1.9406 + } 1.9407 + 1.9408 + let decoder = new TextDecoder("shift_jis"); 1.9409 + result = decoder.decode(new Uint8Array(shift_jis_message)); 1.9410 + break; 1.9411 + case PDU_CDMA_MSG_CODING_KOREAN: 1.9412 + case PDU_CDMA_MSG_CODING_GSM_DCS: 1.9413 + // Fall through. 1.9414 + default: 1.9415 + break; 1.9416 + } 1.9417 + return result; 1.9418 + }, 1.9419 + 1.9420 + /** 1.9421 + * User data subparameter decoder : User Data 1.9422 + * 1.9423 + * @see 3GGP2 C.S0015-B 2.0, 4.5.2 User Data 1.9424 + */ 1.9425 + decodeUserDataMsg: function(hasUserHeader) { 1.9426 + let BitBufferHelper = this.context.BitBufferHelper; 1.9427 + let result = {}, 1.9428 + encoding = BitBufferHelper.readBits(5), 1.9429 + msgType; 1.9430 + 1.9431 + if (encoding === PDU_CDMA_MSG_CODING_IS_91) { 1.9432 + msgType = BitBufferHelper.readBits(8); 1.9433 + } 1.9434 + result.encoding = this.getCdmaMsgEncoding(encoding); 1.9435 + 1.9436 + let msgBodySize = BitBufferHelper.readBits(8); 1.9437 + 1.9438 + // For segmented SMS, a user header is included before sms content 1.9439 + if (hasUserHeader) { 1.9440 + result.header = this.decodeUserDataHeader(result.encoding); 1.9441 + // header size is included in body size, they are decoded 1.9442 + msgBodySize -= result.header.length; 1.9443 + } 1.9444 + 1.9445 + // Store original payload if enconding is OCTET for further handling of WAP Push, etc. 1.9446 + if (encoding === PDU_CDMA_MSG_CODING_OCTET && msgBodySize > 0) { 1.9447 + result.data = new Uint8Array(msgBodySize); 1.9448 + for (let i = 0; i < msgBodySize; i++) { 1.9449 + result.data[i] = BitBufferHelper.readBits(8); 1.9450 + } 1.9451 + BitBufferHelper.backwardReadPilot(8 * msgBodySize); 1.9452 + } 1.9453 + 1.9454 + // Decode sms content 1.9455 + result.body = this.decodeCdmaPDUMsg(encoding, msgType, msgBodySize); 1.9456 + 1.9457 + return result; 1.9458 + }, 1.9459 + 1.9460 + decodeBcd: function(value) { 1.9461 + return ((value >> 4) & 0xF) * 10 + (value & 0x0F); 1.9462 + }, 1.9463 + 1.9464 + /** 1.9465 + * User data subparameter decoder : Time Stamp 1.9466 + * 1.9467 + * @see 3GGP2 C.S0015-B 2.0, 4.5.4 Message Center Time Stamp 1.9468 + */ 1.9469 + decodeUserDataTimestamp: function() { 1.9470 + let BitBufferHelper = this.context.BitBufferHelper; 1.9471 + let year = this.decodeBcd(BitBufferHelper.readBits(8)), 1.9472 + month = this.decodeBcd(BitBufferHelper.readBits(8)) - 1, 1.9473 + date = this.decodeBcd(BitBufferHelper.readBits(8)), 1.9474 + hour = this.decodeBcd(BitBufferHelper.readBits(8)), 1.9475 + min = this.decodeBcd(BitBufferHelper.readBits(8)), 1.9476 + sec = this.decodeBcd(BitBufferHelper.readBits(8)); 1.9477 + 1.9478 + if (year >= 96 && year <= 99) { 1.9479 + year += 1900; 1.9480 + } else { 1.9481 + year += 2000; 1.9482 + } 1.9483 + 1.9484 + let result = (new Date(year, month, date, hour, min, sec, 0)).valueOf(); 1.9485 + 1.9486 + return result; 1.9487 + }, 1.9488 + 1.9489 + /** 1.9490 + * User data subparameter decoder : Reply Option 1.9491 + * 1.9492 + * @see 3GGP2 C.S0015-B 2.0, 4.5.11 Reply Option 1.9493 + */ 1.9494 + decodeUserDataReplyOption: function() { 1.9495 + let replyAction = this.context.BitBufferHelper.readBits(4), 1.9496 + result = { userAck: (replyAction & 0x8) ? true : false, 1.9497 + deliverAck: (replyAction & 0x4) ? true : false, 1.9498 + readAck: (replyAction & 0x2) ? true : false, 1.9499 + report: (replyAction & 0x1) ? true : false 1.9500 + }; 1.9501 + 1.9502 + return result; 1.9503 + }, 1.9504 + 1.9505 + /** 1.9506 + * User data subparameter decoder : Language Indicator 1.9507 + * 1.9508 + * @see 3GGP2 C.S0015-B 2.0, 4.5.14 Language Indicator 1.9509 + */ 1.9510 + decodeLanguageIndicator: function() { 1.9511 + let language = this.context.BitBufferHelper.readBits(8); 1.9512 + let result = CB_CDMA_LANG_GROUP[language]; 1.9513 + return result; 1.9514 + }, 1.9515 + 1.9516 + /** 1.9517 + * User data subparameter decoder : Call-Back Number 1.9518 + * 1.9519 + * @see 3GGP2 C.S0015-B 2.0, 4.5.15 Call-Back Number 1.9520 + */ 1.9521 + decodeUserDataCallbackNumber: function() { 1.9522 + let BitBufferHelper = this.context.BitBufferHelper; 1.9523 + let digitMode = BitBufferHelper.readBits(1); 1.9524 + if (digitMode) { 1.9525 + let numberType = BitBufferHelper.readBits(3), 1.9526 + numberPlan = BitBufferHelper.readBits(4); 1.9527 + } 1.9528 + let numberFields = BitBufferHelper.readBits(8), 1.9529 + result = ""; 1.9530 + for (let i = 0; i < numberFields; i++) { 1.9531 + if (digitMode === PDU_CDMA_MSG_ADDR_DIGIT_MODE_DTMF) { 1.9532 + let addrDigit = BitBufferHelper.readBits(4); 1.9533 + result += this.dtmfChars.charAt(addrDigit); 1.9534 + } else { 1.9535 + let addrDigit = BitBufferHelper.readBits(8); 1.9536 + result += String.fromCharCode(addrDigit); 1.9537 + } 1.9538 + } 1.9539 + 1.9540 + return result; 1.9541 + }, 1.9542 + 1.9543 + /** 1.9544 + * User data subparameter decoder : Message Status 1.9545 + * 1.9546 + * @see 3GGP2 C.S0015-B 2.0, 4.5.21 Message Status 1.9547 + */ 1.9548 + decodeUserDataMsgStatus: function() { 1.9549 + let BitBufferHelper = this.context.BitBufferHelper; 1.9550 + let result = { 1.9551 + errorClass: BitBufferHelper.readBits(2), 1.9552 + msgStatus: BitBufferHelper.readBits(6) 1.9553 + }; 1.9554 + 1.9555 + return result; 1.9556 + }, 1.9557 + 1.9558 + /** 1.9559 + * Decode information record parcel. 1.9560 + */ 1.9561 + decodeInformationRecord: function() { 1.9562 + let Buf = this.context.Buf; 1.9563 + let record = {}; 1.9564 + let numOfRecords = Buf.readInt32(); 1.9565 + 1.9566 + let type; 1.9567 + for (let i = 0; i < numOfRecords; i++) { 1.9568 + type = Buf.readInt32(); 1.9569 + 1.9570 + switch (type) { 1.9571 + /* 1.9572 + * Every type is encaped by ril, except extended display 1.9573 + */ 1.9574 + case PDU_CDMA_INFO_REC_TYPE_DISPLAY: 1.9575 + record.display = Buf.readString(); 1.9576 + break; 1.9577 + case PDU_CDMA_INFO_REC_TYPE_CALLED_PARTY_NUMBER: 1.9578 + record.calledNumber = {}; 1.9579 + record.calledNumber.number = Buf.readString(); 1.9580 + record.calledNumber.type = Buf.readInt32(); 1.9581 + record.calledNumber.plan = Buf.readInt32(); 1.9582 + record.calledNumber.pi = Buf.readInt32(); 1.9583 + record.calledNumber.si = Buf.readInt32(); 1.9584 + break; 1.9585 + case PDU_CDMA_INFO_REC_TYPE_CALLING_PARTY_NUMBER: 1.9586 + record.callingNumber = {}; 1.9587 + record.callingNumber.number = Buf.readString(); 1.9588 + record.callingNumber.type = Buf.readInt32(); 1.9589 + record.callingNumber.plan = Buf.readInt32(); 1.9590 + record.callingNumber.pi = Buf.readInt32(); 1.9591 + record.callingNumber.si = Buf.readInt32(); 1.9592 + break; 1.9593 + case PDU_CDMA_INFO_REC_TYPE_CONNECTED_NUMBER: 1.9594 + record.connectedNumber = {}; 1.9595 + record.connectedNumber.number = Buf.readString(); 1.9596 + record.connectedNumber.type = Buf.readInt32(); 1.9597 + record.connectedNumber.plan = Buf.readInt32(); 1.9598 + record.connectedNumber.pi = Buf.readInt32(); 1.9599 + record.connectedNumber.si = Buf.readInt32(); 1.9600 + break; 1.9601 + case PDU_CDMA_INFO_REC_TYPE_SIGNAL: 1.9602 + record.signal = {}; 1.9603 + record.signal.present = Buf.readInt32(); 1.9604 + record.signal.type = Buf.readInt32(); 1.9605 + record.signal.alertPitch = Buf.readInt32(); 1.9606 + record.signal.signal = Buf.readInt32(); 1.9607 + break; 1.9608 + case PDU_CDMA_INFO_REC_TYPE_REDIRECTING_NUMBER: 1.9609 + record.redirect = {}; 1.9610 + record.redirect.number = Buf.readString(); 1.9611 + record.redirect.type = Buf.readInt32(); 1.9612 + record.redirect.plan = Buf.readInt32(); 1.9613 + record.redirect.pi = Buf.readInt32(); 1.9614 + record.redirect.si = Buf.readInt32(); 1.9615 + record.redirect.reason = Buf.readInt32(); 1.9616 + break; 1.9617 + case PDU_CDMA_INFO_REC_TYPE_LINE_CONTROL: 1.9618 + record.lineControl = {}; 1.9619 + record.lineControl.polarityIncluded = Buf.readInt32(); 1.9620 + record.lineControl.toggle = Buf.readInt32(); 1.9621 + record.lineControl.recerse = Buf.readInt32(); 1.9622 + record.lineControl.powerDenial = Buf.readInt32(); 1.9623 + break; 1.9624 + case PDU_CDMA_INFO_REC_TYPE_EXTENDED_DISPLAY: 1.9625 + let length = Buf.readInt32(); 1.9626 + /* 1.9627 + * Extended display is still in format defined in 1.9628 + * C.S0005-F v1.0, 3.7.5.16 1.9629 + */ 1.9630 + record.extendedDisplay = {}; 1.9631 + 1.9632 + let headerByte = Buf.readInt32(); 1.9633 + length--; 1.9634 + // Based on spec, headerByte must be 0x80 now 1.9635 + record.extendedDisplay.indicator = (headerByte >> 7); 1.9636 + record.extendedDisplay.type = (headerByte & 0x7F); 1.9637 + record.extendedDisplay.records = []; 1.9638 + 1.9639 + while (length > 0) { 1.9640 + let display = {}; 1.9641 + 1.9642 + display.tag = Buf.readInt32(); 1.9643 + length--; 1.9644 + if (display.tag !== INFO_REC_EXTENDED_DISPLAY_BLANK && 1.9645 + display.tag !== INFO_REC_EXTENDED_DISPLAY_SKIP) { 1.9646 + display.content = Buf.readString(); 1.9647 + length -= (display.content.length + 1); 1.9648 + } 1.9649 + 1.9650 + record.extendedDisplay.records.push(display); 1.9651 + } 1.9652 + break; 1.9653 + case PDU_CDMA_INFO_REC_TYPE_T53_CLIR: 1.9654 + record.cause = Buf.readInt32(); 1.9655 + break; 1.9656 + case PDU_CDMA_INFO_REC_TYPE_T53_AUDIO_CONTROL: 1.9657 + record.audioControl = {}; 1.9658 + record.audioControl.upLink = Buf.readInt32(); 1.9659 + record.audioControl.downLink = Buf.readInt32(); 1.9660 + break; 1.9661 + case PDU_CDMA_INFO_REC_TYPE_T53_RELEASE: 1.9662 + // Fall through 1.9663 + default: 1.9664 + throw new Error("UNSOLICITED_CDMA_INFO_REC(), Unsupported information record type " + record.type + "\n"); 1.9665 + } 1.9666 + } 1.9667 + 1.9668 + return record; 1.9669 + } 1.9670 +}; 1.9671 + 1.9672 +/** 1.9673 + * Helper for processing ICC PDUs. 1.9674 + */ 1.9675 +function ICCPDUHelperObject(aContext) { 1.9676 + this.context = aContext; 1.9677 +} 1.9678 +ICCPDUHelperObject.prototype = { 1.9679 + context: null, 1.9680 + 1.9681 + /** 1.9682 + * Read GSM 8-bit unpacked octets, 1.9683 + * which are default 7-bit alphabets with bit 8 set to 0. 1.9684 + * 1.9685 + * @param numOctets 1.9686 + * Number of octets to be read. 1.9687 + */ 1.9688 + read8BitUnpackedToString: function(numOctets) { 1.9689 + let GsmPDUHelper = this.context.GsmPDUHelper; 1.9690 + 1.9691 + let ret = ""; 1.9692 + let escapeFound = false; 1.9693 + let i; 1.9694 + const langTable = PDU_NL_LOCKING_SHIFT_TABLES[PDU_NL_IDENTIFIER_DEFAULT]; 1.9695 + const langShiftTable = PDU_NL_SINGLE_SHIFT_TABLES[PDU_NL_IDENTIFIER_DEFAULT]; 1.9696 + 1.9697 + for(i = 0; i < numOctets; i++) { 1.9698 + let octet = GsmPDUHelper.readHexOctet(); 1.9699 + if (octet == 0xff) { 1.9700 + i++; 1.9701 + break; 1.9702 + } 1.9703 + 1.9704 + if (escapeFound) { 1.9705 + escapeFound = false; 1.9706 + if (octet == PDU_NL_EXTENDED_ESCAPE) { 1.9707 + // According to 3GPP TS 23.038, section 6.2.1.1, NOTE 1, "On 1.9708 + // receipt of this code, a receiving entity shall display a space 1.9709 + // until another extensiion table is defined." 1.9710 + ret += " "; 1.9711 + } else if (octet == PDU_NL_RESERVED_CONTROL) { 1.9712 + // According to 3GPP TS 23.038 B.2, "This code represents a control 1.9713 + // character and therefore must not be used for language specific 1.9714 + // characters." 1.9715 + ret += " "; 1.9716 + } else { 1.9717 + ret += langShiftTable[octet]; 1.9718 + } 1.9719 + } else if (octet == PDU_NL_EXTENDED_ESCAPE) { 1.9720 + escapeFound = true; 1.9721 + } else { 1.9722 + ret += langTable[octet]; 1.9723 + } 1.9724 + } 1.9725 + 1.9726 + let Buf = this.context.Buf; 1.9727 + Buf.seekIncoming((numOctets - i) * Buf.PDU_HEX_OCTET_SIZE); 1.9728 + return ret; 1.9729 + }, 1.9730 + 1.9731 + /** 1.9732 + * Write GSM 8-bit unpacked octets. 1.9733 + * 1.9734 + * @param numOctets Number of total octets to be writen, including trailing 1.9735 + * 0xff. 1.9736 + * @param str String to be written. Could be null. 1.9737 + */ 1.9738 + writeStringTo8BitUnpacked: function(numOctets, str) { 1.9739 + const langTable = PDU_NL_LOCKING_SHIFT_TABLES[PDU_NL_IDENTIFIER_DEFAULT]; 1.9740 + const langShiftTable = PDU_NL_SINGLE_SHIFT_TABLES[PDU_NL_IDENTIFIER_DEFAULT]; 1.9741 + 1.9742 + let GsmPDUHelper = this.context.GsmPDUHelper; 1.9743 + 1.9744 + // If the character is GSM extended alphabet, two octets will be written. 1.9745 + // So we need to keep track of number of octets to be written. 1.9746 + let i, j; 1.9747 + let len = str ? str.length : 0; 1.9748 + for (i = 0, j = 0; i < len && j < numOctets; i++) { 1.9749 + let c = str.charAt(i); 1.9750 + let octet = langTable.indexOf(c); 1.9751 + 1.9752 + if (octet == -1) { 1.9753 + // Make sure we still have enough space to write two octets. 1.9754 + if (j + 2 > numOctets) { 1.9755 + break; 1.9756 + } 1.9757 + 1.9758 + octet = langShiftTable.indexOf(c); 1.9759 + if (octet == -1) { 1.9760 + // Fallback to ASCII space. 1.9761 + octet = langTable.indexOf(' '); 1.9762 + } 1.9763 + GsmPDUHelper.writeHexOctet(PDU_NL_EXTENDED_ESCAPE); 1.9764 + j++; 1.9765 + } 1.9766 + GsmPDUHelper.writeHexOctet(octet); 1.9767 + j++; 1.9768 + } 1.9769 + 1.9770 + // trailing 0xff 1.9771 + while (j++ < numOctets) { 1.9772 + GsmPDUHelper.writeHexOctet(0xff); 1.9773 + } 1.9774 + }, 1.9775 + 1.9776 + /** 1.9777 + * Read UCS2 String on UICC. 1.9778 + * 1.9779 + * @see TS 101.221, Annex A. 1.9780 + * @param scheme 1.9781 + * Coding scheme for UCS2 on UICC. One of 0x80, 0x81 or 0x82. 1.9782 + * @param numOctets 1.9783 + * Number of octets to be read as UCS2 string. 1.9784 + */ 1.9785 + readICCUCS2String: function(scheme, numOctets) { 1.9786 + let Buf = this.context.Buf; 1.9787 + let GsmPDUHelper = this.context.GsmPDUHelper; 1.9788 + 1.9789 + let str = ""; 1.9790 + switch (scheme) { 1.9791 + /** 1.9792 + * +------+---------+---------+---------+---------+------+------+ 1.9793 + * | 0x80 | Ch1_msb | Ch1_lsb | Ch2_msb | Ch2_lsb | 0xff | 0xff | 1.9794 + * +------+---------+---------+---------+---------+------+------+ 1.9795 + */ 1.9796 + case 0x80: 1.9797 + let isOdd = numOctets % 2; 1.9798 + let i; 1.9799 + for (i = 0; i < numOctets - isOdd; i += 2) { 1.9800 + let code = (GsmPDUHelper.readHexOctet() << 8) | GsmPDUHelper.readHexOctet(); 1.9801 + if (code == 0xffff) { 1.9802 + i += 2; 1.9803 + break; 1.9804 + } 1.9805 + str += String.fromCharCode(code); 1.9806 + } 1.9807 + 1.9808 + // Skip trailing 0xff 1.9809 + Buf.seekIncoming((numOctets - i) * Buf.PDU_HEX_OCTET_SIZE); 1.9810 + break; 1.9811 + case 0x81: // Fall through 1.9812 + case 0x82: 1.9813 + /** 1.9814 + * +------+-----+--------+-----+-----+-----+--------+------+ 1.9815 + * | 0x81 | len | offset | Ch1 | Ch2 | ... | Ch_len | 0xff | 1.9816 + * +------+-----+--------+-----+-----+-----+--------+------+ 1.9817 + * 1.9818 + * len : The length of characters. 1.9819 + * offset : 0hhh hhhh h000 0000 1.9820 + * Ch_n: bit 8 = 0 1.9821 + * GSM default alphabets 1.9822 + * bit 8 = 1 1.9823 + * UCS2 character whose char code is (Ch_n & 0x7f) + offset 1.9824 + * 1.9825 + * +------+-----+------------+------------+-----+-----+-----+--------+ 1.9826 + * | 0x82 | len | offset_msb | offset_lsb | Ch1 | Ch2 | ... | Ch_len | 1.9827 + * +------+-----+------------+------------+-----+-----+-----+--------+ 1.9828 + * 1.9829 + * len : The length of characters. 1.9830 + * offset_msb, offset_lsn: offset 1.9831 + * Ch_n: bit 8 = 0 1.9832 + * GSM default alphabets 1.9833 + * bit 8 = 1 1.9834 + * UCS2 character whose char code is (Ch_n & 0x7f) + offset 1.9835 + */ 1.9836 + let len = GsmPDUHelper.readHexOctet(); 1.9837 + let offset, headerLen; 1.9838 + if (scheme == 0x81) { 1.9839 + offset = GsmPDUHelper.readHexOctet() << 7; 1.9840 + headerLen = 2; 1.9841 + } else { 1.9842 + offset = (GsmPDUHelper.readHexOctet() << 8) | GsmPDUHelper.readHexOctet(); 1.9843 + headerLen = 3; 1.9844 + } 1.9845 + 1.9846 + for (let i = 0; i < len; i++) { 1.9847 + let ch = GsmPDUHelper.readHexOctet(); 1.9848 + if (ch & 0x80) { 1.9849 + // UCS2 1.9850 + str += String.fromCharCode((ch & 0x7f) + offset); 1.9851 + } else { 1.9852 + // GSM 8bit 1.9853 + let count = 0, gotUCS2 = 0; 1.9854 + while ((i + count + 1 < len)) { 1.9855 + count++; 1.9856 + if (GsmPDUHelper.readHexOctet() & 0x80) { 1.9857 + gotUCS2 = 1; 1.9858 + break; 1.9859 + } 1.9860 + } 1.9861 + // Unread. 1.9862 + // +1 for the GSM alphabet indexed at i, 1.9863 + Buf.seekIncoming(-1 * (count + 1) * Buf.PDU_HEX_OCTET_SIZE); 1.9864 + str += this.read8BitUnpackedToString(count + 1 - gotUCS2); 1.9865 + i += count - gotUCS2; 1.9866 + } 1.9867 + } 1.9868 + 1.9869 + // Skipping trailing 0xff 1.9870 + Buf.seekIncoming((numOctets - len - headerLen) * Buf.PDU_HEX_OCTET_SIZE); 1.9871 + break; 1.9872 + } 1.9873 + return str; 1.9874 + }, 1.9875 + 1.9876 + /** 1.9877 + * Read Alpha Id and Dialling number from TS TS 151.011 clause 10.5.1 1.9878 + * 1.9879 + * @param recordSize The size of linear fixed record. 1.9880 + */ 1.9881 + readAlphaIdDiallingNumber: function(recordSize) { 1.9882 + let Buf = this.context.Buf; 1.9883 + let length = Buf.readInt32(); 1.9884 + 1.9885 + let alphaLen = recordSize - ADN_FOOTER_SIZE_BYTES; 1.9886 + let alphaId = this.readAlphaIdentifier(alphaLen); 1.9887 + 1.9888 + let number = this.readNumberWithLength(); 1.9889 + 1.9890 + // Skip 2 unused octets, CCP and EXT1. 1.9891 + Buf.seekIncoming(2 * Buf.PDU_HEX_OCTET_SIZE); 1.9892 + Buf.readStringDelimiter(length); 1.9893 + 1.9894 + let contact = null; 1.9895 + if (alphaId || number) { 1.9896 + contact = {alphaId: alphaId, 1.9897 + number: number}; 1.9898 + } 1.9899 + return contact; 1.9900 + }, 1.9901 + 1.9902 + /** 1.9903 + * Write Alpha Identifier and Dialling number from TS 151.011 clause 10.5.1 1.9904 + * 1.9905 + * @param recordSize The size of linear fixed record. 1.9906 + * @param alphaId Alpha Identifier to be written. 1.9907 + * @param number Dialling Number to be written. 1.9908 + */ 1.9909 + writeAlphaIdDiallingNumber: function(recordSize, alphaId, number) { 1.9910 + let Buf = this.context.Buf; 1.9911 + let GsmPDUHelper = this.context.GsmPDUHelper; 1.9912 + 1.9913 + // Write String length 1.9914 + let strLen = recordSize * 2; 1.9915 + Buf.writeInt32(strLen); 1.9916 + 1.9917 + let alphaLen = recordSize - ADN_FOOTER_SIZE_BYTES; 1.9918 + this.writeAlphaIdentifier(alphaLen, alphaId); 1.9919 + this.writeNumberWithLength(number); 1.9920 + 1.9921 + // Write unused octets 0xff, CCP and EXT1. 1.9922 + GsmPDUHelper.writeHexOctet(0xff); 1.9923 + GsmPDUHelper.writeHexOctet(0xff); 1.9924 + Buf.writeStringDelimiter(strLen); 1.9925 + }, 1.9926 + 1.9927 + /** 1.9928 + * Read Alpha Identifier. 1.9929 + * 1.9930 + * @see TS 131.102 1.9931 + * 1.9932 + * @param numOctets 1.9933 + * Number of octets to be read. 1.9934 + * 1.9935 + * It uses either 1.9936 + * 1. SMS default 7-bit alphabet with bit 8 set to 0. 1.9937 + * 2. UCS2 string. 1.9938 + * 1.9939 + * Unused bytes should be set to 0xff. 1.9940 + */ 1.9941 + readAlphaIdentifier: function(numOctets) { 1.9942 + if (numOctets === 0) { 1.9943 + return ""; 1.9944 + } 1.9945 + 1.9946 + let temp; 1.9947 + // Read the 1st octet to determine the encoding. 1.9948 + if ((temp = this.context.GsmPDUHelper.readHexOctet()) == 0x80 || 1.9949 + temp == 0x81 || 1.9950 + temp == 0x82) { 1.9951 + numOctets--; 1.9952 + return this.readICCUCS2String(temp, numOctets); 1.9953 + } else { 1.9954 + let Buf = this.context.Buf; 1.9955 + Buf.seekIncoming(-1 * Buf.PDU_HEX_OCTET_SIZE); 1.9956 + return this.read8BitUnpackedToString(numOctets); 1.9957 + } 1.9958 + }, 1.9959 + 1.9960 + /** 1.9961 + * Write Alpha Identifier. 1.9962 + * 1.9963 + * @param numOctets 1.9964 + * Total number of octets to be written. This includes the length of 1.9965 + * alphaId and the length of trailing unused octets(0xff). 1.9966 + * @param alphaId 1.9967 + * Alpha Identifier to be written. 1.9968 + * 1.9969 + * Unused octets will be written as 0xff. 1.9970 + */ 1.9971 + writeAlphaIdentifier: function(numOctets, alphaId) { 1.9972 + if (numOctets === 0) { 1.9973 + return; 1.9974 + } 1.9975 + 1.9976 + // If alphaId is empty or it's of GSM 8 bit. 1.9977 + if (!alphaId || this.context.ICCUtilsHelper.isGsm8BitAlphabet(alphaId)) { 1.9978 + this.writeStringTo8BitUnpacked(numOctets, alphaId); 1.9979 + } else { 1.9980 + let GsmPDUHelper = this.context.GsmPDUHelper; 1.9981 + 1.9982 + // Currently only support UCS2 coding scheme 0x80. 1.9983 + GsmPDUHelper.writeHexOctet(0x80); 1.9984 + numOctets--; 1.9985 + // Now the alphaId is UCS2 string, each character will take 2 octets. 1.9986 + if (alphaId.length * 2 > numOctets) { 1.9987 + alphaId = alphaId.substring(0, Math.floor(numOctets / 2)); 1.9988 + } 1.9989 + GsmPDUHelper.writeUCS2String(alphaId); 1.9990 + for (let i = alphaId.length * 2; i < numOctets; i++) { 1.9991 + GsmPDUHelper.writeHexOctet(0xff); 1.9992 + } 1.9993 + } 1.9994 + }, 1.9995 + 1.9996 + /** 1.9997 + * Read Dialling number. 1.9998 + * 1.9999 + * @see TS 131.102 1.10000 + * 1.10001 + * @param len 1.10002 + * The Length of BCD number. 1.10003 + * 1.10004 + * From TS 131.102, in EF_ADN, EF_FDN, the field 'Length of BCD number' 1.10005 + * means the total bytes should be allocated to store the TON/NPI and 1.10006 + * the dialing number. 1.10007 + * For example, if the dialing number is 1234567890, 1.10008 + * and the TON/NPI is 0x81, 1.10009 + * The field 'Length of BCD number' should be 06, which is 1.10010 + * 1 byte to store the TON/NPI, 0x81 1.10011 + * 5 bytes to store the BCD number 2143658709. 1.10012 + * 1.10013 + * Here the definition of the length is different from SMS spec, 1.10014 + * TS 23.040 9.1.2.5, which the length means 1.10015 + * "number of useful semi-octets within the Address-Value field". 1.10016 + */ 1.10017 + readDiallingNumber: function(len) { 1.10018 + if (DEBUG) this.context.debug("PDU: Going to read Dialling number: " + len); 1.10019 + if (len === 0) { 1.10020 + return ""; 1.10021 + } 1.10022 + 1.10023 + let GsmPDUHelper = this.context.GsmPDUHelper; 1.10024 + 1.10025 + // TOA = TON + NPI 1.10026 + let toa = GsmPDUHelper.readHexOctet(); 1.10027 + 1.10028 + let number = GsmPDUHelper.readSwappedNibbleBcdString(len - 1); 1.10029 + if (number.length <= 0) { 1.10030 + if (DEBUG) this.context.debug("No number provided"); 1.10031 + return ""; 1.10032 + } 1.10033 + if ((toa >> 4) == (PDU_TOA_INTERNATIONAL >> 4)) { 1.10034 + number = '+' + number; 1.10035 + } 1.10036 + return number; 1.10037 + }, 1.10038 + 1.10039 + /** 1.10040 + * Write Dialling Number. 1.10041 + * 1.10042 + * @param number The Dialling number 1.10043 + */ 1.10044 + writeDiallingNumber: function(number) { 1.10045 + let GsmPDUHelper = this.context.GsmPDUHelper; 1.10046 + 1.10047 + let toa = PDU_TOA_ISDN; // 81 1.10048 + if (number[0] == '+') { 1.10049 + toa = PDU_TOA_INTERNATIONAL | PDU_TOA_ISDN; // 91 1.10050 + number = number.substring(1); 1.10051 + } 1.10052 + GsmPDUHelper.writeHexOctet(toa); 1.10053 + GsmPDUHelper.writeSwappedNibbleBCD(number); 1.10054 + }, 1.10055 + 1.10056 + readNumberWithLength: function() { 1.10057 + let Buf = this.context.Buf; 1.10058 + let number; 1.10059 + let numLen = this.context.GsmPDUHelper.readHexOctet(); 1.10060 + if (numLen != 0xff) { 1.10061 + if (numLen > ADN_MAX_BCD_NUMBER_BYTES) { 1.10062 + throw new Error("invalid length of BCD number/SSC contents - " + numLen); 1.10063 + } 1.10064 + 1.10065 + number = this.readDiallingNumber(numLen); 1.10066 + Buf.seekIncoming((ADN_MAX_BCD_NUMBER_BYTES - numLen) * Buf.PDU_HEX_OCTET_SIZE); 1.10067 + } else { 1.10068 + Buf.seekIncoming(ADN_MAX_BCD_NUMBER_BYTES * Buf.PDU_HEX_OCTET_SIZE); 1.10069 + } 1.10070 + 1.10071 + return number; 1.10072 + }, 1.10073 + 1.10074 + writeNumberWithLength: function(number) { 1.10075 + let GsmPDUHelper = this.context.GsmPDUHelper; 1.10076 + 1.10077 + if (number) { 1.10078 + let numStart = number[0] == "+" ? 1 : 0; 1.10079 + number = number.substring(0, numStart) + 1.10080 + number.substring(numStart) 1.10081 + .replace(/[^0-9*#,]/g, "") 1.10082 + .replace(/\*/g, "a") 1.10083 + .replace(/\#/g, "b") 1.10084 + .replace(/\,/g, "c"); 1.10085 + 1.10086 + let numDigits = number.length - numStart; 1.10087 + if (numDigits > ADN_MAX_NUMBER_DIGITS) { 1.10088 + number = number.substring(0, ADN_MAX_NUMBER_DIGITS + numStart); 1.10089 + numDigits = number.length - numStart; 1.10090 + } 1.10091 + 1.10092 + // +1 for TON/NPI 1.10093 + let numLen = Math.ceil(numDigits / 2) + 1; 1.10094 + GsmPDUHelper.writeHexOctet(numLen); 1.10095 + this.writeDiallingNumber(number); 1.10096 + // Write trailing 0xff of Dialling Number. 1.10097 + for (let i = 0; i < ADN_MAX_BCD_NUMBER_BYTES - numLen; i++) { 1.10098 + GsmPDUHelper.writeHexOctet(0xff); 1.10099 + } 1.10100 + } else { 1.10101 + // +1 for numLen 1.10102 + for (let i = 0; i < ADN_MAX_BCD_NUMBER_BYTES + 1; i++) { 1.10103 + GsmPDUHelper.writeHexOctet(0xff); 1.10104 + } 1.10105 + } 1.10106 + } 1.10107 +}; 1.10108 + 1.10109 +function StkCommandParamsFactoryObject(aContext) { 1.10110 + this.context = aContext; 1.10111 +} 1.10112 +StkCommandParamsFactoryObject.prototype = { 1.10113 + context: null, 1.10114 + 1.10115 + createParam: function(cmdDetails, ctlvs) { 1.10116 + let method = this[cmdDetails.typeOfCommand]; 1.10117 + if (typeof method != "function") { 1.10118 + if (DEBUG) { 1.10119 + this.context.debug("Unknown proactive command " + 1.10120 + cmdDetails.typeOfCommand.toString(16)); 1.10121 + } 1.10122 + return null; 1.10123 + } 1.10124 + return method.call(this, cmdDetails, ctlvs); 1.10125 + }, 1.10126 + 1.10127 + /** 1.10128 + * Construct a param for Refresh. 1.10129 + * 1.10130 + * @param cmdDetails 1.10131 + * The value object of CommandDetails TLV. 1.10132 + * @param ctlvs 1.10133 + * The all TLVs in this proactive command. 1.10134 + */ 1.10135 + processRefresh: function(cmdDetails, ctlvs) { 1.10136 + let refreshType = cmdDetails.commandQualifier; 1.10137 + switch (refreshType) { 1.10138 + case STK_REFRESH_FILE_CHANGE: 1.10139 + case STK_REFRESH_NAA_INIT_AND_FILE_CHANGE: 1.10140 + let ctlv = this.context.StkProactiveCmdHelper.searchForTag( 1.10141 + COMPREHENSIONTLV_TAG_FILE_LIST, ctlvs); 1.10142 + if (ctlv) { 1.10143 + let list = ctlv.value.fileList; 1.10144 + if (DEBUG) { 1.10145 + this.context.debug("Refresh, list = " + list); 1.10146 + } 1.10147 + this.context.ICCRecordHelper.fetchICCRecords(); 1.10148 + } 1.10149 + break; 1.10150 + } 1.10151 + return null; 1.10152 + }, 1.10153 + 1.10154 + /** 1.10155 + * Construct a param for Poll Interval. 1.10156 + * 1.10157 + * @param cmdDetails 1.10158 + * The value object of CommandDetails TLV. 1.10159 + * @param ctlvs 1.10160 + * The all TLVs in this proactive command. 1.10161 + */ 1.10162 + processPollInterval: function(cmdDetails, ctlvs) { 1.10163 + let ctlv = this.context.StkProactiveCmdHelper.searchForTag( 1.10164 + COMPREHENSIONTLV_TAG_DURATION, ctlvs); 1.10165 + if (!ctlv) { 1.10166 + this.context.RIL.sendStkTerminalResponse({ 1.10167 + command: cmdDetails, 1.10168 + resultCode: STK_RESULT_REQUIRED_VALUES_MISSING}); 1.10169 + throw new Error("Stk Poll Interval: Required value missing : Duration"); 1.10170 + } 1.10171 + 1.10172 + return ctlv.value; 1.10173 + }, 1.10174 + 1.10175 + /** 1.10176 + * Construct a param for Poll Off. 1.10177 + * 1.10178 + * @param cmdDetails 1.10179 + * The value object of CommandDetails TLV. 1.10180 + * @param ctlvs 1.10181 + * The all TLVs in this proactive command. 1.10182 + */ 1.10183 + processPollOff: function(cmdDetails, ctlvs) { 1.10184 + return null; 1.10185 + }, 1.10186 + 1.10187 + /** 1.10188 + * Construct a param for Set Up Event list. 1.10189 + * 1.10190 + * @param cmdDetails 1.10191 + * The value object of CommandDetails TLV. 1.10192 + * @param ctlvs 1.10193 + * The all TLVs in this proactive command. 1.10194 + */ 1.10195 + processSetUpEventList: function(cmdDetails, ctlvs) { 1.10196 + let ctlv = this.context.StkProactiveCmdHelper.searchForTag( 1.10197 + COMPREHENSIONTLV_TAG_EVENT_LIST, ctlvs); 1.10198 + if (!ctlv) { 1.10199 + this.context.RIL.sendStkTerminalResponse({ 1.10200 + command: cmdDetails, 1.10201 + resultCode: STK_RESULT_REQUIRED_VALUES_MISSING}); 1.10202 + throw new Error("Stk Event List: Required value missing : Event List"); 1.10203 + } 1.10204 + 1.10205 + return ctlv.value || {eventList: null}; 1.10206 + }, 1.10207 + 1.10208 + /** 1.10209 + * Construct a param for Select Item. 1.10210 + * 1.10211 + * @param cmdDetails 1.10212 + * The value object of CommandDetails TLV. 1.10213 + * @param ctlvs 1.10214 + * The all TLVs in this proactive command. 1.10215 + */ 1.10216 + processSelectItem: function(cmdDetails, ctlvs) { 1.10217 + let StkProactiveCmdHelper = this.context.StkProactiveCmdHelper; 1.10218 + let menu = {}; 1.10219 + 1.10220 + let ctlv = StkProactiveCmdHelper.searchForTag(COMPREHENSIONTLV_TAG_ALPHA_ID, ctlvs); 1.10221 + if (ctlv) { 1.10222 + menu.title = ctlv.value.identifier; 1.10223 + } 1.10224 + 1.10225 + menu.items = []; 1.10226 + for (let i = 0; i < ctlvs.length; i++) { 1.10227 + let ctlv = ctlvs[i]; 1.10228 + if (ctlv.tag == COMPREHENSIONTLV_TAG_ITEM) { 1.10229 + menu.items.push(ctlv.value); 1.10230 + } 1.10231 + } 1.10232 + 1.10233 + if (menu.items.length === 0) { 1.10234 + this.context.RIL.sendStkTerminalResponse({ 1.10235 + command: cmdDetails, 1.10236 + resultCode: STK_RESULT_REQUIRED_VALUES_MISSING}); 1.10237 + throw new Error("Stk Menu: Required value missing : items"); 1.10238 + } 1.10239 + 1.10240 + ctlv = StkProactiveCmdHelper.searchForTag(COMPREHENSIONTLV_TAG_ITEM_ID, ctlvs); 1.10241 + if (ctlv) { 1.10242 + menu.defaultItem = ctlv.value.identifier - 1; 1.10243 + } 1.10244 + 1.10245 + // The 1st bit and 2nd bit determines the presentation type. 1.10246 + menu.presentationType = cmdDetails.commandQualifier & 0x03; 1.10247 + 1.10248 + // Help information available. 1.10249 + if (cmdDetails.commandQualifier & 0x80) { 1.10250 + menu.isHelpAvailable = true; 1.10251 + } 1.10252 + 1.10253 + ctlv = StkProactiveCmdHelper.searchForTag(COMPREHENSIONTLV_TAG_NEXT_ACTION_IND, ctlvs); 1.10254 + if (ctlv) { 1.10255 + menu.nextActionList = ctlv.value; 1.10256 + } 1.10257 + 1.10258 + return menu; 1.10259 + }, 1.10260 + 1.10261 + processDisplayText: function(cmdDetails, ctlvs) { 1.10262 + let StkProactiveCmdHelper = this.context.StkProactiveCmdHelper; 1.10263 + let textMsg = {}; 1.10264 + 1.10265 + let ctlv = StkProactiveCmdHelper.searchForTag(COMPREHENSIONTLV_TAG_TEXT_STRING, ctlvs); 1.10266 + if (!ctlv) { 1.10267 + this.context.RIL.sendStkTerminalResponse({ 1.10268 + command: cmdDetails, 1.10269 + resultCode: STK_RESULT_REQUIRED_VALUES_MISSING}); 1.10270 + throw new Error("Stk Display Text: Required value missing : Text String"); 1.10271 + } 1.10272 + textMsg.text = ctlv.value.textString; 1.10273 + 1.10274 + ctlv = StkProactiveCmdHelper.searchForTag(COMPREHENSIONTLV_TAG_IMMEDIATE_RESPONSE, ctlvs); 1.10275 + if (ctlv) { 1.10276 + textMsg.responseNeeded = true; 1.10277 + } 1.10278 + 1.10279 + ctlv = StkProactiveCmdHelper.searchForTag(COMPREHENSIONTLV_TAG_DURATION, ctlvs); 1.10280 + if (ctlv) { 1.10281 + textMsg.duration = ctlv.value; 1.10282 + } 1.10283 + 1.10284 + // High priority. 1.10285 + if (cmdDetails.commandQualifier & 0x01) { 1.10286 + textMsg.isHighPriority = true; 1.10287 + } 1.10288 + 1.10289 + // User clear. 1.10290 + if (cmdDetails.commandQualifier & 0x80) { 1.10291 + textMsg.userClear = true; 1.10292 + } 1.10293 + 1.10294 + return textMsg; 1.10295 + }, 1.10296 + 1.10297 + processSetUpIdleModeText: function(cmdDetails, ctlvs) { 1.10298 + let textMsg = {}; 1.10299 + 1.10300 + let ctlv = this.context.StkProactiveCmdHelper.searchForTag( 1.10301 + COMPREHENSIONTLV_TAG_TEXT_STRING, ctlvs); 1.10302 + if (!ctlv) { 1.10303 + this.context.RIL.sendStkTerminalResponse({ 1.10304 + command: cmdDetails, 1.10305 + resultCode: STK_RESULT_REQUIRED_VALUES_MISSING}); 1.10306 + throw new Error("Stk Set Up Idle Text: Required value missing : Text String"); 1.10307 + } 1.10308 + textMsg.text = ctlv.value.textString; 1.10309 + 1.10310 + return textMsg; 1.10311 + }, 1.10312 + 1.10313 + processGetInkey: function(cmdDetails, ctlvs) { 1.10314 + let StkProactiveCmdHelper = this.context.StkProactiveCmdHelper; 1.10315 + let input = {}; 1.10316 + 1.10317 + let ctlv = StkProactiveCmdHelper.searchForTag(COMPREHENSIONTLV_TAG_TEXT_STRING, ctlvs); 1.10318 + if (!ctlv) { 1.10319 + this.context.RIL.sendStkTerminalResponse({ 1.10320 + command: cmdDetails, 1.10321 + resultCode: STK_RESULT_REQUIRED_VALUES_MISSING}); 1.10322 + throw new Error("Stk Get InKey: Required value missing : Text String"); 1.10323 + } 1.10324 + input.text = ctlv.value.textString; 1.10325 + 1.10326 + // duration 1.10327 + ctlv = StkProactiveCmdHelper.searchForTag( 1.10328 + COMPREHENSIONTLV_TAG_DURATION, ctlvs); 1.10329 + if (ctlv) { 1.10330 + input.duration = ctlv.value; 1.10331 + } 1.10332 + 1.10333 + input.minLength = 1; 1.10334 + input.maxLength = 1; 1.10335 + 1.10336 + // isAlphabet 1.10337 + if (cmdDetails.commandQualifier & 0x01) { 1.10338 + input.isAlphabet = true; 1.10339 + } 1.10340 + 1.10341 + // UCS2 1.10342 + if (cmdDetails.commandQualifier & 0x02) { 1.10343 + input.isUCS2 = true; 1.10344 + } 1.10345 + 1.10346 + // Character sets defined in bit 1 and bit 2 are disable and 1.10347 + // the YES/NO reponse is required. 1.10348 + if (cmdDetails.commandQualifier & 0x04) { 1.10349 + input.isYesNoRequested = true; 1.10350 + } 1.10351 + 1.10352 + // Help information available. 1.10353 + if (cmdDetails.commandQualifier & 0x80) { 1.10354 + input.isHelpAvailable = true; 1.10355 + } 1.10356 + 1.10357 + return input; 1.10358 + }, 1.10359 + 1.10360 + processGetInput: function(cmdDetails, ctlvs) { 1.10361 + let StkProactiveCmdHelper = this.context.StkProactiveCmdHelper; 1.10362 + let input = {}; 1.10363 + 1.10364 + let ctlv = StkProactiveCmdHelper.searchForTag(COMPREHENSIONTLV_TAG_TEXT_STRING, ctlvs); 1.10365 + if (!ctlv) { 1.10366 + this.context.RIL.sendStkTerminalResponse({ 1.10367 + command: cmdDetails, 1.10368 + resultCode: STK_RESULT_REQUIRED_VALUES_MISSING}); 1.10369 + throw new Error("Stk Get Input: Required value missing : Text String"); 1.10370 + } 1.10371 + input.text = ctlv.value.textString; 1.10372 + 1.10373 + ctlv = StkProactiveCmdHelper.searchForTag(COMPREHENSIONTLV_TAG_RESPONSE_LENGTH, ctlvs); 1.10374 + if (ctlv) { 1.10375 + input.minLength = ctlv.value.minLength; 1.10376 + input.maxLength = ctlv.value.maxLength; 1.10377 + } 1.10378 + 1.10379 + ctlv = StkProactiveCmdHelper.searchForTag(COMPREHENSIONTLV_TAG_DEFAULT_TEXT, ctlvs); 1.10380 + if (ctlv) { 1.10381 + input.defaultText = ctlv.value.textString; 1.10382 + } 1.10383 + 1.10384 + // Alphabet only 1.10385 + if (cmdDetails.commandQualifier & 0x01) { 1.10386 + input.isAlphabet = true; 1.10387 + } 1.10388 + 1.10389 + // UCS2 1.10390 + if (cmdDetails.commandQualifier & 0x02) { 1.10391 + input.isUCS2 = true; 1.10392 + } 1.10393 + 1.10394 + // User input shall not be revealed 1.10395 + if (cmdDetails.commandQualifier & 0x04) { 1.10396 + input.hideInput = true; 1.10397 + } 1.10398 + 1.10399 + // User input in SMS packed format 1.10400 + if (cmdDetails.commandQualifier & 0x08) { 1.10401 + input.isPacked = true; 1.10402 + } 1.10403 + 1.10404 + // Help information available. 1.10405 + if (cmdDetails.commandQualifier & 0x80) { 1.10406 + input.isHelpAvailable = true; 1.10407 + } 1.10408 + 1.10409 + return input; 1.10410 + }, 1.10411 + 1.10412 + processEventNotify: function(cmdDetails, ctlvs) { 1.10413 + let textMsg = {}; 1.10414 + 1.10415 + let ctlv = this.context.StkProactiveCmdHelper.searchForTag( 1.10416 + COMPREHENSIONTLV_TAG_ALPHA_ID, ctlvs); 1.10417 + if (!ctlv) { 1.10418 + this.context.RIL.sendStkTerminalResponse({ 1.10419 + command: cmdDetails, 1.10420 + resultCode: STK_RESULT_REQUIRED_VALUES_MISSING}); 1.10421 + throw new Error("Stk Event Notfiy: Required value missing : Alpha ID"); 1.10422 + } 1.10423 + textMsg.text = ctlv.value.identifier; 1.10424 + 1.10425 + return textMsg; 1.10426 + }, 1.10427 + 1.10428 + processSetupCall: function(cmdDetails, ctlvs) { 1.10429 + let StkProactiveCmdHelper = this.context.StkProactiveCmdHelper; 1.10430 + let call = {}; 1.10431 + let iter = Iterator(ctlvs); 1.10432 + 1.10433 + let ctlv = StkProactiveCmdHelper.searchForNextTag(COMPREHENSIONTLV_TAG_ALPHA_ID, iter); 1.10434 + if (ctlv) { 1.10435 + call.confirmMessage = ctlv.value.identifier; 1.10436 + } 1.10437 + 1.10438 + ctlv = StkProactiveCmdHelper.searchForNextTag(COMPREHENSIONTLV_TAG_ALPHA_ID, iter); 1.10439 + if (ctlv) { 1.10440 + call.callMessage = ctlv.value.identifier; 1.10441 + } 1.10442 + 1.10443 + ctlv = StkProactiveCmdHelper.searchForTag(COMPREHENSIONTLV_TAG_ADDRESS, ctlvs); 1.10444 + if (!ctlv) { 1.10445 + this.context.RIL.sendStkTerminalResponse({ 1.10446 + command: cmdDetails, 1.10447 + resultCode: STK_RESULT_REQUIRED_VALUES_MISSING}); 1.10448 + throw new Error("Stk Set Up Call: Required value missing : Adress"); 1.10449 + } 1.10450 + call.address = ctlv.value.number; 1.10451 + 1.10452 + // see 3GPP TS 31.111 section 6.4.13 1.10453 + ctlv = StkProactiveCmdHelper.searchForTag(COMPREHENSIONTLV_TAG_DURATION, ctlvs); 1.10454 + if (ctlv) { 1.10455 + call.duration = ctlv.value; 1.10456 + } 1.10457 + 1.10458 + return call; 1.10459 + }, 1.10460 + 1.10461 + processLaunchBrowser: function(cmdDetails, ctlvs) { 1.10462 + let StkProactiveCmdHelper = this.context.StkProactiveCmdHelper; 1.10463 + let browser = {}; 1.10464 + 1.10465 + let ctlv = StkProactiveCmdHelper.searchForTag(COMPREHENSIONTLV_TAG_URL, ctlvs); 1.10466 + if (!ctlv) { 1.10467 + this.context.RIL.sendStkTerminalResponse({ 1.10468 + command: cmdDetails, 1.10469 + resultCode: STK_RESULT_REQUIRED_VALUES_MISSING}); 1.10470 + throw new Error("Stk Launch Browser: Required value missing : URL"); 1.10471 + } 1.10472 + browser.url = ctlv.value.url; 1.10473 + 1.10474 + ctlv = StkProactiveCmdHelper.searchForTag(COMPREHENSIONTLV_TAG_ALPHA_ID, ctlvs); 1.10475 + if (ctlv) { 1.10476 + browser.confirmMessage = ctlv.value.identifier; 1.10477 + } 1.10478 + 1.10479 + browser.mode = cmdDetails.commandQualifier & 0x03; 1.10480 + 1.10481 + return browser; 1.10482 + }, 1.10483 + 1.10484 + processPlayTone: function(cmdDetails, ctlvs) { 1.10485 + let StkProactiveCmdHelper = this.context.StkProactiveCmdHelper; 1.10486 + let playTone = {}; 1.10487 + 1.10488 + let ctlv = StkProactiveCmdHelper.searchForTag( 1.10489 + COMPREHENSIONTLV_TAG_ALPHA_ID, ctlvs); 1.10490 + if (ctlv) { 1.10491 + playTone.text = ctlv.value.identifier; 1.10492 + } 1.10493 + 1.10494 + ctlv = StkProactiveCmdHelper.searchForTag(COMPREHENSIONTLV_TAG_TONE, ctlvs); 1.10495 + if (ctlv) { 1.10496 + playTone.tone = ctlv.value.tone; 1.10497 + } 1.10498 + 1.10499 + ctlv = StkProactiveCmdHelper.searchForTag( 1.10500 + COMPREHENSIONTLV_TAG_DURATION, ctlvs); 1.10501 + if (ctlv) { 1.10502 + playTone.duration = ctlv.value; 1.10503 + } 1.10504 + 1.10505 + // vibrate is only defined in TS 102.223 1.10506 + playTone.isVibrate = (cmdDetails.commandQualifier & 0x01) !== 0x00; 1.10507 + 1.10508 + return playTone; 1.10509 + }, 1.10510 + 1.10511 + /** 1.10512 + * Construct a param for Provide Local Information 1.10513 + * 1.10514 + * @param cmdDetails 1.10515 + * The value object of CommandDetails TLV. 1.10516 + * @param ctlvs 1.10517 + * The all TLVs in this proactive command. 1.10518 + */ 1.10519 + processProvideLocalInfo: function(cmdDetails, ctlvs) { 1.10520 + let provideLocalInfo = { 1.10521 + localInfoType: cmdDetails.commandQualifier 1.10522 + }; 1.10523 + return provideLocalInfo; 1.10524 + }, 1.10525 + 1.10526 + processTimerManagement: function(cmdDetails, ctlvs) { 1.10527 + let StkProactiveCmdHelper = this.context.StkProactiveCmdHelper; 1.10528 + let timer = { 1.10529 + timerAction: cmdDetails.commandQualifier 1.10530 + }; 1.10531 + 1.10532 + let ctlv = StkProactiveCmdHelper.searchForTag( 1.10533 + COMPREHENSIONTLV_TAG_TIMER_IDENTIFIER, ctlvs); 1.10534 + if (ctlv) { 1.10535 + timer.timerId = ctlv.value.timerId; 1.10536 + } 1.10537 + 1.10538 + ctlv = StkProactiveCmdHelper.searchForTag( 1.10539 + COMPREHENSIONTLV_TAG_TIMER_VALUE, ctlvs); 1.10540 + if (ctlv) { 1.10541 + timer.timerValue = ctlv.value.timerValue; 1.10542 + } 1.10543 + 1.10544 + return timer; 1.10545 + }, 1.10546 + 1.10547 + /** 1.10548 + * Construct a param for BIP commands. 1.10549 + * 1.10550 + * @param cmdDetails 1.10551 + * The value object of CommandDetails TLV. 1.10552 + * @param ctlvs 1.10553 + * The all TLVs in this proactive command. 1.10554 + */ 1.10555 + processBipMessage: function(cmdDetails, ctlvs) { 1.10556 + let bipMsg = {}; 1.10557 + 1.10558 + let ctlv = this.context.StkProactiveCmdHelper.searchForTag( 1.10559 + COMPREHENSIONTLV_TAG_ALPHA_ID, ctlvs); 1.10560 + if (ctlv) { 1.10561 + bipMsg.text = ctlv.value.identifier; 1.10562 + } 1.10563 + 1.10564 + return bipMsg; 1.10565 + } 1.10566 +}; 1.10567 +StkCommandParamsFactoryObject.prototype[STK_CMD_REFRESH] = function STK_CMD_REFRESH(cmdDetails, ctlvs) { 1.10568 + return this.processRefresh(cmdDetails, ctlvs); 1.10569 +}; 1.10570 +StkCommandParamsFactoryObject.prototype[STK_CMD_POLL_INTERVAL] = function STK_CMD_POLL_INTERVAL(cmdDetails, ctlvs) { 1.10571 + return this.processPollInterval(cmdDetails, ctlvs); 1.10572 +}; 1.10573 +StkCommandParamsFactoryObject.prototype[STK_CMD_POLL_OFF] = function STK_CMD_POLL_OFF(cmdDetails, ctlvs) { 1.10574 + return this.processPollOff(cmdDetails, ctlvs); 1.10575 +}; 1.10576 +StkCommandParamsFactoryObject.prototype[STK_CMD_PROVIDE_LOCAL_INFO] = function STK_CMD_PROVIDE_LOCAL_INFO(cmdDetails, ctlvs) { 1.10577 + return this.processProvideLocalInfo(cmdDetails, ctlvs); 1.10578 +}; 1.10579 +StkCommandParamsFactoryObject.prototype[STK_CMD_SET_UP_EVENT_LIST] = function STK_CMD_SET_UP_EVENT_LIST(cmdDetails, ctlvs) { 1.10580 + return this.processSetUpEventList(cmdDetails, ctlvs); 1.10581 +}; 1.10582 +StkCommandParamsFactoryObject.prototype[STK_CMD_SET_UP_MENU] = function STK_CMD_SET_UP_MENU(cmdDetails, ctlvs) { 1.10583 + return this.processSelectItem(cmdDetails, ctlvs); 1.10584 +}; 1.10585 +StkCommandParamsFactoryObject.prototype[STK_CMD_SELECT_ITEM] = function STK_CMD_SELECT_ITEM(cmdDetails, ctlvs) { 1.10586 + return this.processSelectItem(cmdDetails, ctlvs); 1.10587 +}; 1.10588 +StkCommandParamsFactoryObject.prototype[STK_CMD_DISPLAY_TEXT] = function STK_CMD_DISPLAY_TEXT(cmdDetails, ctlvs) { 1.10589 + return this.processDisplayText(cmdDetails, ctlvs); 1.10590 +}; 1.10591 +StkCommandParamsFactoryObject.prototype[STK_CMD_SET_UP_IDLE_MODE_TEXT] = function STK_CMD_SET_UP_IDLE_MODE_TEXT(cmdDetails, ctlvs) { 1.10592 + return this.processSetUpIdleModeText(cmdDetails, ctlvs); 1.10593 +}; 1.10594 +StkCommandParamsFactoryObject.prototype[STK_CMD_GET_INKEY] = function STK_CMD_GET_INKEY(cmdDetails, ctlvs) { 1.10595 + return this.processGetInkey(cmdDetails, ctlvs); 1.10596 +}; 1.10597 +StkCommandParamsFactoryObject.prototype[STK_CMD_GET_INPUT] = function STK_CMD_GET_INPUT(cmdDetails, ctlvs) { 1.10598 + return this.processGetInput(cmdDetails, ctlvs); 1.10599 +}; 1.10600 +StkCommandParamsFactoryObject.prototype[STK_CMD_SEND_SS] = function STK_CMD_SEND_SS(cmdDetails, ctlvs) { 1.10601 + return this.processEventNotify(cmdDetails, ctlvs); 1.10602 +}; 1.10603 +StkCommandParamsFactoryObject.prototype[STK_CMD_SEND_USSD] = function STK_CMD_SEND_USSD(cmdDetails, ctlvs) { 1.10604 + return this.processEventNotify(cmdDetails, ctlvs); 1.10605 +}; 1.10606 +StkCommandParamsFactoryObject.prototype[STK_CMD_SEND_SMS] = function STK_CMD_SEND_SMS(cmdDetails, ctlvs) { 1.10607 + return this.processEventNotify(cmdDetails, ctlvs); 1.10608 +}; 1.10609 +StkCommandParamsFactoryObject.prototype[STK_CMD_SEND_DTMF] = function STK_CMD_SEND_DTMF(cmdDetails, ctlvs) { 1.10610 + return this.processEventNotify(cmdDetails, ctlvs); 1.10611 +}; 1.10612 +StkCommandParamsFactoryObject.prototype[STK_CMD_SET_UP_CALL] = function STK_CMD_SET_UP_CALL(cmdDetails, ctlvs) { 1.10613 + return this.processSetupCall(cmdDetails, ctlvs); 1.10614 +}; 1.10615 +StkCommandParamsFactoryObject.prototype[STK_CMD_LAUNCH_BROWSER] = function STK_CMD_LAUNCH_BROWSER(cmdDetails, ctlvs) { 1.10616 + return this.processLaunchBrowser(cmdDetails, ctlvs); 1.10617 +}; 1.10618 +StkCommandParamsFactoryObject.prototype[STK_CMD_PLAY_TONE] = function STK_CMD_PLAY_TONE(cmdDetails, ctlvs) { 1.10619 + return this.processPlayTone(cmdDetails, ctlvs); 1.10620 +}; 1.10621 +StkCommandParamsFactoryObject.prototype[STK_CMD_TIMER_MANAGEMENT] = function STK_CMD_TIMER_MANAGEMENT(cmdDetails, ctlvs) { 1.10622 + return this.processTimerManagement(cmdDetails, ctlvs); 1.10623 +}; 1.10624 +StkCommandParamsFactoryObject.prototype[STK_CMD_OPEN_CHANNEL] = function STK_CMD_OPEN_CHANNEL(cmdDetails, ctlvs) { 1.10625 + return this.processBipMessage(cmdDetails, ctlvs); 1.10626 +}; 1.10627 +StkCommandParamsFactoryObject.prototype[STK_CMD_CLOSE_CHANNEL] = function STK_CMD_CLOSE_CHANNEL(cmdDetails, ctlvs) { 1.10628 + return this.processBipMessage(cmdDetails, ctlvs); 1.10629 +}; 1.10630 +StkCommandParamsFactoryObject.prototype[STK_CMD_RECEIVE_DATA] = function STK_CMD_RECEIVE_DATA(cmdDetails, ctlvs) { 1.10631 + return this.processBipMessage(cmdDetails, ctlvs); 1.10632 +}; 1.10633 +StkCommandParamsFactoryObject.prototype[STK_CMD_SEND_DATA] = function STK_CMD_SEND_DATA(cmdDetails, ctlvs) { 1.10634 + return this.processBipMessage(cmdDetails, ctlvs); 1.10635 +}; 1.10636 + 1.10637 +function StkProactiveCmdHelperObject(aContext) { 1.10638 + this.context = aContext; 1.10639 +} 1.10640 +StkProactiveCmdHelperObject.prototype = { 1.10641 + context: null, 1.10642 + 1.10643 + retrieve: function(tag, length) { 1.10644 + let method = this[tag]; 1.10645 + if (typeof method != "function") { 1.10646 + if (DEBUG) { 1.10647 + this.context.debug("Unknown comprehension tag " + tag.toString(16)); 1.10648 + } 1.10649 + let Buf = this.context.Buf; 1.10650 + Buf.seekIncoming(length * Buf.PDU_HEX_OCTET_SIZE); 1.10651 + return null; 1.10652 + } 1.10653 + return method.call(this, length); 1.10654 + }, 1.10655 + 1.10656 + /** 1.10657 + * Command Details. 1.10658 + * 1.10659 + * | Byte | Description | Length | 1.10660 + * | 1 | Command details Tag | 1 | 1.10661 + * | 2 | Length = 03 | 1 | 1.10662 + * | 3 | Command number | 1 | 1.10663 + * | 4 | Type of Command | 1 | 1.10664 + * | 5 | Command Qualifier | 1 | 1.10665 + */ 1.10666 + retrieveCommandDetails: function(length) { 1.10667 + let GsmPDUHelper = this.context.GsmPDUHelper; 1.10668 + let cmdDetails = { 1.10669 + commandNumber: GsmPDUHelper.readHexOctet(), 1.10670 + typeOfCommand: GsmPDUHelper.readHexOctet(), 1.10671 + commandQualifier: GsmPDUHelper.readHexOctet() 1.10672 + }; 1.10673 + return cmdDetails; 1.10674 + }, 1.10675 + 1.10676 + /** 1.10677 + * Device Identities. 1.10678 + * 1.10679 + * | Byte | Description | Length | 1.10680 + * | 1 | Device Identity Tag | 1 | 1.10681 + * | 2 | Length = 02 | 1 | 1.10682 + * | 3 | Source device Identity | 1 | 1.10683 + * | 4 | Destination device Id | 1 | 1.10684 + */ 1.10685 + retrieveDeviceId: function(length) { 1.10686 + let GsmPDUHelper = this.context.GsmPDUHelper; 1.10687 + let deviceId = { 1.10688 + sourceId: GsmPDUHelper.readHexOctet(), 1.10689 + destinationId: GsmPDUHelper.readHexOctet() 1.10690 + }; 1.10691 + return deviceId; 1.10692 + }, 1.10693 + 1.10694 + /** 1.10695 + * Alpha Identifier. 1.10696 + * 1.10697 + * | Byte | Description | Length | 1.10698 + * | 1 | Alpha Identifier Tag | 1 | 1.10699 + * | 2 ~ (Y-1)+2 | Length (X) | Y | 1.10700 + * | (Y-1)+3 ~ | Alpha identfier | X | 1.10701 + * | (Y-1)+X+2 | | | 1.10702 + */ 1.10703 + retrieveAlphaId: function(length) { 1.10704 + let alphaId = { 1.10705 + identifier: this.context.ICCPDUHelper.readAlphaIdentifier(length) 1.10706 + }; 1.10707 + return alphaId; 1.10708 + }, 1.10709 + 1.10710 + /** 1.10711 + * Duration. 1.10712 + * 1.10713 + * | Byte | Description | Length | 1.10714 + * | 1 | Response Length Tag | 1 | 1.10715 + * | 2 | Lenth = 02 | 1 | 1.10716 + * | 3 | Time unit | 1 | 1.10717 + * | 4 | Time interval | 1 | 1.10718 + */ 1.10719 + retrieveDuration: function(length) { 1.10720 + let GsmPDUHelper = this.context.GsmPDUHelper; 1.10721 + let duration = { 1.10722 + timeUnit: GsmPDUHelper.readHexOctet(), 1.10723 + timeInterval: GsmPDUHelper.readHexOctet(), 1.10724 + }; 1.10725 + return duration; 1.10726 + }, 1.10727 + 1.10728 + /** 1.10729 + * Address. 1.10730 + * 1.10731 + * | Byte | Description | Length | 1.10732 + * | 1 | Alpha Identifier Tag | 1 | 1.10733 + * | 2 ~ (Y-1)+2 | Length (X) | Y | 1.10734 + * | (Y-1)+3 | TON and NPI | 1 | 1.10735 + * | (Y-1)+4 ~ | Dialling number | X | 1.10736 + * | (Y-1)+X+2 | | | 1.10737 + */ 1.10738 + retrieveAddress: function(length) { 1.10739 + let address = { 1.10740 + number : this.context.ICCPDUHelper.readDiallingNumber(length) 1.10741 + }; 1.10742 + return address; 1.10743 + }, 1.10744 + 1.10745 + /** 1.10746 + * Text String. 1.10747 + * 1.10748 + * | Byte | Description | Length | 1.10749 + * | 1 | Text String Tag | 1 | 1.10750 + * | 2 ~ (Y-1)+2 | Length (X) | Y | 1.10751 + * | (Y-1)+3 | Data coding scheme | 1 | 1.10752 + * | (Y-1)+4~ | Text String | X | 1.10753 + * | (Y-1)+X+2 | | | 1.10754 + */ 1.10755 + retrieveTextString: function(length) { 1.10756 + if (!length) { 1.10757 + // null string. 1.10758 + return {textString: null}; 1.10759 + } 1.10760 + 1.10761 + let GsmPDUHelper = this.context.GsmPDUHelper; 1.10762 + let text = { 1.10763 + codingScheme: GsmPDUHelper.readHexOctet() 1.10764 + }; 1.10765 + 1.10766 + length--; // -1 for the codingScheme. 1.10767 + switch (text.codingScheme & 0x0f) { 1.10768 + case STK_TEXT_CODING_GSM_7BIT_PACKED: 1.10769 + text.textString = GsmPDUHelper.readSeptetsToString(length * 8 / 7, 0, 0, 0); 1.10770 + break; 1.10771 + case STK_TEXT_CODING_GSM_8BIT: 1.10772 + text.textString = 1.10773 + this.context.ICCPDUHelper.read8BitUnpackedToString(length); 1.10774 + break; 1.10775 + case STK_TEXT_CODING_UCS2: 1.10776 + text.textString = GsmPDUHelper.readUCS2String(length); 1.10777 + break; 1.10778 + } 1.10779 + return text; 1.10780 + }, 1.10781 + 1.10782 + /** 1.10783 + * Tone. 1.10784 + * 1.10785 + * | Byte | Description | Length | 1.10786 + * | 1 | Tone Tag | 1 | 1.10787 + * | 2 | Lenth = 01 | 1 | 1.10788 + * | 3 | Tone | 1 | 1.10789 + */ 1.10790 + retrieveTone: function(length) { 1.10791 + let tone = { 1.10792 + tone: this.context.GsmPDUHelper.readHexOctet(), 1.10793 + }; 1.10794 + return tone; 1.10795 + }, 1.10796 + 1.10797 + /** 1.10798 + * Item. 1.10799 + * 1.10800 + * | Byte | Description | Length | 1.10801 + * | 1 | Item Tag | 1 | 1.10802 + * | 2 ~ (Y-1)+2 | Length (X) | Y | 1.10803 + * | (Y-1)+3 | Identifier of item | 1 | 1.10804 + * | (Y-1)+4 ~ | Text string of item | X | 1.10805 + * | (Y-1)+X+2 | | | 1.10806 + */ 1.10807 + retrieveItem: function(length) { 1.10808 + // TS 102.223 ,clause 6.6.7 SET-UP MENU 1.10809 + // If the "Item data object for item 1" is a null data object 1.10810 + // (i.e. length = '00' and no value part), this is an indication to the ME 1.10811 + // to remove the existing menu from the menu system in the ME. 1.10812 + if (!length) { 1.10813 + return null; 1.10814 + } 1.10815 + let item = { 1.10816 + identifier: this.context.GsmPDUHelper.readHexOctet(), 1.10817 + text: this.context.ICCPDUHelper.readAlphaIdentifier(length - 1) 1.10818 + }; 1.10819 + return item; 1.10820 + }, 1.10821 + 1.10822 + /** 1.10823 + * Item Identifier. 1.10824 + * 1.10825 + * | Byte | Description | Length | 1.10826 + * | 1 | Item Identifier Tag | 1 | 1.10827 + * | 2 | Lenth = 01 | 1 | 1.10828 + * | 3 | Identifier of Item chosen | 1 | 1.10829 + */ 1.10830 + retrieveItemId: function(length) { 1.10831 + let itemId = { 1.10832 + identifier: this.context.GsmPDUHelper.readHexOctet() 1.10833 + }; 1.10834 + return itemId; 1.10835 + }, 1.10836 + 1.10837 + /** 1.10838 + * Response Length. 1.10839 + * 1.10840 + * | Byte | Description | Length | 1.10841 + * | 1 | Response Length Tag | 1 | 1.10842 + * | 2 | Lenth = 02 | 1 | 1.10843 + * | 3 | Minimum length of response | 1 | 1.10844 + * | 4 | Maximum length of response | 1 | 1.10845 + */ 1.10846 + retrieveResponseLength: function(length) { 1.10847 + let GsmPDUHelper = this.context.GsmPDUHelper; 1.10848 + let rspLength = { 1.10849 + minLength : GsmPDUHelper.readHexOctet(), 1.10850 + maxLength : GsmPDUHelper.readHexOctet() 1.10851 + }; 1.10852 + return rspLength; 1.10853 + }, 1.10854 + 1.10855 + /** 1.10856 + * File List. 1.10857 + * 1.10858 + * | Byte | Description | Length | 1.10859 + * | 1 | File List Tag | 1 | 1.10860 + * | 2 ~ (Y-1)+2 | Length (X) | Y | 1.10861 + * | (Y-1)+3 | Number of files | 1 | 1.10862 + * | (Y-1)+4 ~ | Files | X | 1.10863 + * | (Y-1)+X+2 | | | 1.10864 + */ 1.10865 + retrieveFileList: function(length) { 1.10866 + let num = this.context.GsmPDUHelper.readHexOctet(); 1.10867 + let fileList = ""; 1.10868 + length--; // -1 for the num octet. 1.10869 + for (let i = 0; i < 2 * length; i++) { 1.10870 + // Didn't use readHexOctet here, 1.10871 + // otherwise 0x00 will be "0", not "00" 1.10872 + fileList += String.fromCharCode(this.context.Buf.readUint16()); 1.10873 + } 1.10874 + return { 1.10875 + fileList: fileList 1.10876 + }; 1.10877 + }, 1.10878 + 1.10879 + /** 1.10880 + * Default Text. 1.10881 + * 1.10882 + * Same as Text String. 1.10883 + */ 1.10884 + retrieveDefaultText: function(length) { 1.10885 + return this.retrieveTextString(length); 1.10886 + }, 1.10887 + 1.10888 + /** 1.10889 + * Event List. 1.10890 + */ 1.10891 + retrieveEventList: function(length) { 1.10892 + if (!length) { 1.10893 + // null means an indication to ME to remove the existing list of events 1.10894 + // in ME. 1.10895 + return null; 1.10896 + } 1.10897 + 1.10898 + let GsmPDUHelper = this.context.GsmPDUHelper; 1.10899 + let eventList = []; 1.10900 + for (let i = 0; i < length; i++) { 1.10901 + eventList.push(GsmPDUHelper.readHexOctet()); 1.10902 + } 1.10903 + return { 1.10904 + eventList: eventList 1.10905 + }; 1.10906 + }, 1.10907 + 1.10908 + /** 1.10909 + * Timer Identifier. 1.10910 + * 1.10911 + * | Byte | Description | Length | 1.10912 + * | 1 | Timer Identifier Tag | 1 | 1.10913 + * | 2 | Length = 01 | 1 | 1.10914 + * | 3 | Timer Identifier | 1 | 1.10915 + */ 1.10916 + retrieveTimerId: function(length) { 1.10917 + let id = { 1.10918 + timerId: this.context.GsmPDUHelper.readHexOctet() 1.10919 + }; 1.10920 + return id; 1.10921 + }, 1.10922 + 1.10923 + /** 1.10924 + * Timer Value. 1.10925 + * 1.10926 + * | Byte | Description | Length | 1.10927 + * | 1 | Timer Value Tag | 1 | 1.10928 + * | 2 | Length = 03 | 1 | 1.10929 + * | 3 | Hour | 1 | 1.10930 + * | 4 | Minute | 1 | 1.10931 + * | 5 | Second | 1 | 1.10932 + */ 1.10933 + retrieveTimerValue: function(length) { 1.10934 + let GsmPDUHelper = this.context.GsmPDUHelper; 1.10935 + let value = { 1.10936 + timerValue: (GsmPDUHelper.readSwappedNibbleBcdNum(1) * 60 * 60) + 1.10937 + (GsmPDUHelper.readSwappedNibbleBcdNum(1) * 60) + 1.10938 + (GsmPDUHelper.readSwappedNibbleBcdNum(1)) 1.10939 + }; 1.10940 + return value; 1.10941 + }, 1.10942 + 1.10943 + /** 1.10944 + * Immediate Response. 1.10945 + * 1.10946 + * | Byte | Description | Length | 1.10947 + * | 1 | Immediate Response Tag | 1 | 1.10948 + * | 2 | Length = 00 | 1 | 1.10949 + */ 1.10950 + retrieveImmediaResponse: function(length) { 1.10951 + return {}; 1.10952 + }, 1.10953 + 1.10954 + /** 1.10955 + * URL 1.10956 + * 1.10957 + * | Byte | Description | Length | 1.10958 + * | 1 | URL Tag | 1 | 1.10959 + * | 2 ~ (Y+1) | Length(X) | Y | 1.10960 + * | (Y+2) ~ | URL | X | 1.10961 + * | (Y+1+X) | | | 1.10962 + */ 1.10963 + retrieveUrl: function(length) { 1.10964 + let GsmPDUHelper = this.context.GsmPDUHelper; 1.10965 + let s = ""; 1.10966 + for (let i = 0; i < length; i++) { 1.10967 + s += String.fromCharCode(GsmPDUHelper.readHexOctet()); 1.10968 + } 1.10969 + return {url: s}; 1.10970 + }, 1.10971 + 1.10972 + /** 1.10973 + * Next Action Indicator List. 1.10974 + * 1.10975 + * | Byte | Description | Length | 1.10976 + * | 1 | Next Action tag | 1 | 1.10977 + * | 1 | Length(X) | 1 | 1.10978 + * | 3~ | Next Action List | X | 1.10979 + * | 3+X-1 | | | 1.10980 + */ 1.10981 + retrieveNextActionList: function(length) { 1.10982 + let GsmPDUHelper = this.context.GsmPDUHelper; 1.10983 + let nextActionList = []; 1.10984 + for (let i = 0; i < length; i++) { 1.10985 + nextActionList.push(GsmPDUHelper.readHexOctet()); 1.10986 + } 1.10987 + return nextActionList; 1.10988 + }, 1.10989 + 1.10990 + searchForTag: function(tag, ctlvs) { 1.10991 + let iter = Iterator(ctlvs); 1.10992 + return this.searchForNextTag(tag, iter); 1.10993 + }, 1.10994 + 1.10995 + searchForNextTag: function(tag, iter) { 1.10996 + for (let [index, ctlv] in iter) { 1.10997 + if ((ctlv.tag & ~COMPREHENSIONTLV_FLAG_CR) == tag) { 1.10998 + return ctlv; 1.10999 + } 1.11000 + } 1.11001 + return null; 1.11002 + }, 1.11003 +}; 1.11004 +StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_COMMAND_DETAILS] = function COMPREHENSIONTLV_TAG_COMMAND_DETAILS(length) { 1.11005 + return this.retrieveCommandDetails(length); 1.11006 +}; 1.11007 +StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_DEVICE_ID] = function COMPREHENSIONTLV_TAG_DEVICE_ID(length) { 1.11008 + return this.retrieveDeviceId(length); 1.11009 +}; 1.11010 +StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_ALPHA_ID] = function COMPREHENSIONTLV_TAG_ALPHA_ID(length) { 1.11011 + return this.retrieveAlphaId(length); 1.11012 +}; 1.11013 +StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_DURATION] = function COMPREHENSIONTLV_TAG_DURATION(length) { 1.11014 + return this.retrieveDuration(length); 1.11015 +}; 1.11016 +StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_ADDRESS] = function COMPREHENSIONTLV_TAG_ADDRESS(length) { 1.11017 + return this.retrieveAddress(length); 1.11018 +}; 1.11019 +StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_TEXT_STRING] = function COMPREHENSIONTLV_TAG_TEXT_STRING(length) { 1.11020 + return this.retrieveTextString(length); 1.11021 +}; 1.11022 +StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_TONE] = function COMPREHENSIONTLV_TAG_TONE(length) { 1.11023 + return this.retrieveTone(length); 1.11024 +}; 1.11025 +StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_ITEM] = function COMPREHENSIONTLV_TAG_ITEM(length) { 1.11026 + return this.retrieveItem(length); 1.11027 +}; 1.11028 +StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_ITEM_ID] = function COMPREHENSIONTLV_TAG_ITEM_ID(length) { 1.11029 + return this.retrieveItemId(length); 1.11030 +}; 1.11031 +StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_RESPONSE_LENGTH] = function COMPREHENSIONTLV_TAG_RESPONSE_LENGTH(length) { 1.11032 + return this.retrieveResponseLength(length); 1.11033 +}; 1.11034 +StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_FILE_LIST] = function COMPREHENSIONTLV_TAG_FILE_LIST(length) { 1.11035 + return this.retrieveFileList(length); 1.11036 +}; 1.11037 +StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_DEFAULT_TEXT] = function COMPREHENSIONTLV_TAG_DEFAULT_TEXT(length) { 1.11038 + return this.retrieveDefaultText(length); 1.11039 +}; 1.11040 +StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_EVENT_LIST] = function COMPREHENSIONTLV_TAG_EVENT_LIST(length) { 1.11041 + return this.retrieveEventList(length); 1.11042 +}; 1.11043 +StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_TIMER_IDENTIFIER] = function COMPREHENSIONTLV_TAG_TIMER_IDENTIFIER(length) { 1.11044 + return this.retrieveTimerId(length); 1.11045 +}; 1.11046 +StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_TIMER_VALUE] = function COMPREHENSIONTLV_TAG_TIMER_VALUE(length) { 1.11047 + return this.retrieveTimerValue(length); 1.11048 +}; 1.11049 +StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_IMMEDIATE_RESPONSE] = function COMPREHENSIONTLV_TAG_IMMEDIATE_RESPONSE(length) { 1.11050 + return this.retrieveImmediaResponse(length); 1.11051 +}; 1.11052 +StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_URL] = function COMPREHENSIONTLV_TAG_URL(length) { 1.11053 + return this.retrieveUrl(length); 1.11054 +}; 1.11055 +StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_NEXT_ACTION_IND] = function COMPREHENSIONTLV_TAG_NEXT_ACTION_IND(length) { 1.11056 + return this.retrieveNextActionList(length); 1.11057 +}; 1.11058 + 1.11059 +function ComprehensionTlvHelperObject(aContext) { 1.11060 + this.context = aContext; 1.11061 +} 1.11062 +ComprehensionTlvHelperObject.prototype = { 1.11063 + context: null, 1.11064 + 1.11065 + /** 1.11066 + * Decode raw data to a Comprehension-TLV. 1.11067 + */ 1.11068 + decode: function() { 1.11069 + let GsmPDUHelper = this.context.GsmPDUHelper; 1.11070 + 1.11071 + let hlen = 0; // For header(tag field + length field) length. 1.11072 + let temp = GsmPDUHelper.readHexOctet(); 1.11073 + hlen++; 1.11074 + 1.11075 + // TS 101.220, clause 7.1.1 1.11076 + let tag, cr; 1.11077 + switch (temp) { 1.11078 + // TS 101.220, clause 7.1.1 1.11079 + case 0x0: // Not used. 1.11080 + case 0xff: // Not used. 1.11081 + case 0x80: // Reserved for future use. 1.11082 + throw new Error("Invalid octet when parsing Comprehension TLV :" + temp); 1.11083 + case 0x7f: // Tag is three byte format. 1.11084 + // TS 101.220 clause 7.1.1.2. 1.11085 + // | Byte 1 | Byte 2 | Byte 3 | 1.11086 + // | | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | | 1.11087 + // | 0x7f |CR | Tag Value | 1.11088 + tag = (GsmPDUHelper.readHexOctet() << 8) | GsmPDUHelper.readHexOctet(); 1.11089 + hlen += 2; 1.11090 + cr = (tag & 0x8000) !== 0; 1.11091 + tag &= ~0x8000; 1.11092 + break; 1.11093 + default: // Tag is single byte format. 1.11094 + tag = temp; 1.11095 + // TS 101.220 clause 7.1.1.1. 1.11096 + // | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 1.11097 + // |CR | Tag Value | 1.11098 + cr = (tag & 0x80) !== 0; 1.11099 + tag &= ~0x80; 1.11100 + } 1.11101 + 1.11102 + // TS 101.220 clause 7.1.2, Length Encoding. 1.11103 + // Length | Byte 1 | Byte 2 | Byte 3 | Byte 4 | 1.11104 + // 0 - 127 | 00 - 7f | N/A | N/A | N/A | 1.11105 + // 128-255 | 81 | 80 - ff| N/A | N/A | 1.11106 + // 256-65535| 82 | 0100 - ffff | N/A | 1.11107 + // 65536- | 83 | 010000 - ffffff | 1.11108 + // 16777215 1.11109 + // 1.11110 + // Length errors: TS 11.14, clause 6.10.6 1.11111 + 1.11112 + let length; // Data length. 1.11113 + temp = GsmPDUHelper.readHexOctet(); 1.11114 + hlen++; 1.11115 + if (temp < 0x80) { 1.11116 + length = temp; 1.11117 + } else if (temp == 0x81) { 1.11118 + length = GsmPDUHelper.readHexOctet(); 1.11119 + hlen++; 1.11120 + if (length < 0x80) { 1.11121 + throw new Error("Invalid length in Comprehension TLV :" + length); 1.11122 + } 1.11123 + } else if (temp == 0x82) { 1.11124 + length = (GsmPDUHelper.readHexOctet() << 8) | GsmPDUHelper.readHexOctet(); 1.11125 + hlen += 2; 1.11126 + if (lenth < 0x0100) { 1.11127 + throw new Error("Invalid length in 3-byte Comprehension TLV :" + length); 1.11128 + } 1.11129 + } else if (temp == 0x83) { 1.11130 + length = (GsmPDUHelper.readHexOctet() << 16) | 1.11131 + (GsmPDUHelper.readHexOctet() << 8) | 1.11132 + GsmPDUHelper.readHexOctet(); 1.11133 + hlen += 3; 1.11134 + if (length < 0x010000) { 1.11135 + throw new Error("Invalid length in 4-byte Comprehension TLV :" + length); 1.11136 + } 1.11137 + } else { 1.11138 + throw new Error("Invalid octet in Comprehension TLV :" + temp); 1.11139 + } 1.11140 + 1.11141 + let ctlv = { 1.11142 + tag: tag, 1.11143 + length: length, 1.11144 + value: this.context.StkProactiveCmdHelper.retrieve(tag, length), 1.11145 + cr: cr, 1.11146 + hlen: hlen 1.11147 + }; 1.11148 + return ctlv; 1.11149 + }, 1.11150 + 1.11151 + decodeChunks: function(length) { 1.11152 + let chunks = []; 1.11153 + let index = 0; 1.11154 + while (index < length) { 1.11155 + let tlv = this.decode(); 1.11156 + chunks.push(tlv); 1.11157 + index += tlv.length; 1.11158 + index += tlv.hlen; 1.11159 + } 1.11160 + return chunks; 1.11161 + }, 1.11162 + 1.11163 + /** 1.11164 + * Write Location Info Comprehension TLV. 1.11165 + * 1.11166 + * @param loc location Information. 1.11167 + */ 1.11168 + writeLocationInfoTlv: function(loc) { 1.11169 + let GsmPDUHelper = this.context.GsmPDUHelper; 1.11170 + 1.11171 + GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_LOCATION_INFO | 1.11172 + COMPREHENSIONTLV_FLAG_CR); 1.11173 + GsmPDUHelper.writeHexOctet(loc.gsmCellId > 0xffff ? 9 : 7); 1.11174 + // From TS 11.14, clause 12.19 1.11175 + // "The mobile country code (MCC), the mobile network code (MNC), 1.11176 + // the location area code (LAC) and the cell ID are 1.11177 + // coded as in TS 04.08." 1.11178 + // And from TS 04.08 and TS 24.008, 1.11179 + // the format is as follows: 1.11180 + // 1.11181 + // MCC = MCC_digit_1 + MCC_digit_2 + MCC_digit_3 1.11182 + // 1.11183 + // 8 7 6 5 4 3 2 1 1.11184 + // +-------------+-------------+ 1.11185 + // | MCC digit 2 | MCC digit 1 | octet 2 1.11186 + // | MNC digit 3 | MCC digit 3 | octet 3 1.11187 + // | MNC digit 2 | MNC digit 1 | octet 4 1.11188 + // +-------------+-------------+ 1.11189 + // 1.11190 + // Also in TS 24.008 1.11191 + // "However a network operator may decide to 1.11192 + // use only two digits in the MNC in the LAI over the 1.11193 + // radio interface. In this case, bits 5 to 8 of octet 3 1.11194 + // shall be coded as '1111'". 1.11195 + 1.11196 + // MCC & MNC, 3 octets 1.11197 + let mcc = loc.mcc, mnc; 1.11198 + if (loc.mnc.length == 2) { 1.11199 + mnc = "F" + loc.mnc; 1.11200 + } else { 1.11201 + mnc = loc.mnc[2] + loc.mnc[0] + loc.mnc[1]; 1.11202 + } 1.11203 + GsmPDUHelper.writeSwappedNibbleBCD(mcc + mnc); 1.11204 + 1.11205 + // LAC, 2 octets 1.11206 + GsmPDUHelper.writeHexOctet((loc.gsmLocationAreaCode >> 8) & 0xff); 1.11207 + GsmPDUHelper.writeHexOctet(loc.gsmLocationAreaCode & 0xff); 1.11208 + 1.11209 + // Cell Id 1.11210 + if (loc.gsmCellId > 0xffff) { 1.11211 + // UMTS/WCDMA, gsmCellId is 28 bits. 1.11212 + GsmPDUHelper.writeHexOctet((loc.gsmCellId >> 24) & 0xff); 1.11213 + GsmPDUHelper.writeHexOctet((loc.gsmCellId >> 16) & 0xff); 1.11214 + GsmPDUHelper.writeHexOctet((loc.gsmCellId >> 8) & 0xff); 1.11215 + GsmPDUHelper.writeHexOctet(loc.gsmCellId & 0xff); 1.11216 + } else { 1.11217 + // GSM, gsmCellId is 16 bits. 1.11218 + GsmPDUHelper.writeHexOctet((loc.gsmCellId >> 8) & 0xff); 1.11219 + GsmPDUHelper.writeHexOctet(loc.gsmCellId & 0xff); 1.11220 + } 1.11221 + }, 1.11222 + 1.11223 + /** 1.11224 + * Given a geckoError string, this function translates it into cause value 1.11225 + * and write the value into buffer. 1.11226 + * 1.11227 + * @param geckoError Error string that is passed to gecko. 1.11228 + */ 1.11229 + writeCauseTlv: function(geckoError) { 1.11230 + let GsmPDUHelper = this.context.GsmPDUHelper; 1.11231 + 1.11232 + let cause = -1; 1.11233 + for (let errorNo in RIL_ERROR_TO_GECKO_ERROR) { 1.11234 + if (geckoError == RIL_ERROR_TO_GECKO_ERROR[errorNo]) { 1.11235 + cause = errorNo; 1.11236 + break; 1.11237 + } 1.11238 + } 1.11239 + cause = (cause == -1) ? ERROR_SUCCESS : cause; 1.11240 + 1.11241 + GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_CAUSE | 1.11242 + COMPREHENSIONTLV_FLAG_CR); 1.11243 + GsmPDUHelper.writeHexOctet(2); // For single cause value. 1.11244 + 1.11245 + // TS 04.08, clause 10.5.4.11: National standard code + user location. 1.11246 + GsmPDUHelper.writeHexOctet(0x60); 1.11247 + 1.11248 + // TS 04.08, clause 10.5.4.11: ext bit = 1 + 7 bits for cause. 1.11249 + // +-----------------+----------------------------------+ 1.11250 + // | Ext = 1 (1 bit) | Cause (7 bits) | 1.11251 + // +-----------------+----------------------------------+ 1.11252 + GsmPDUHelper.writeHexOctet(0x80 | cause); 1.11253 + }, 1.11254 + 1.11255 + writeDateTimeZoneTlv: function(date) { 1.11256 + let GsmPDUHelper = this.context.GsmPDUHelper; 1.11257 + 1.11258 + GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_DATE_TIME_ZONE); 1.11259 + GsmPDUHelper.writeHexOctet(7); 1.11260 + GsmPDUHelper.writeTimestamp(date); 1.11261 + }, 1.11262 + 1.11263 + writeLanguageTlv: function(language) { 1.11264 + let GsmPDUHelper = this.context.GsmPDUHelper; 1.11265 + 1.11266 + GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_LANGUAGE); 1.11267 + GsmPDUHelper.writeHexOctet(2); 1.11268 + 1.11269 + // ISO 639-1, Alpha-2 code 1.11270 + // TS 123.038, clause 6.2.1, GSM 7 bit Default Alphabet 1.11271 + GsmPDUHelper.writeHexOctet( 1.11272 + PDU_NL_LOCKING_SHIFT_TABLES[PDU_NL_IDENTIFIER_DEFAULT].indexOf(language[0])); 1.11273 + GsmPDUHelper.writeHexOctet( 1.11274 + PDU_NL_LOCKING_SHIFT_TABLES[PDU_NL_IDENTIFIER_DEFAULT].indexOf(language[1])); 1.11275 + }, 1.11276 + 1.11277 + /** 1.11278 + * Write Timer Value Comprehension TLV. 1.11279 + * 1.11280 + * @param seconds length of time during of the timer. 1.11281 + * @param cr Comprehension Required or not 1.11282 + */ 1.11283 + writeTimerValueTlv: function(seconds, cr) { 1.11284 + let GsmPDUHelper = this.context.GsmPDUHelper; 1.11285 + 1.11286 + GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_TIMER_VALUE | 1.11287 + (cr ? COMPREHENSIONTLV_FLAG_CR : 0)); 1.11288 + GsmPDUHelper.writeHexOctet(3); 1.11289 + 1.11290 + // TS 102.223, clause 8.38 1.11291 + // +----------------+------------------+-------------------+ 1.11292 + // | hours (1 byte) | minutes (1 btye) | secounds (1 byte) | 1.11293 + // +----------------+------------------+-------------------+ 1.11294 + GsmPDUHelper.writeSwappedNibbleBCDNum(Math.floor(seconds / 60 / 60)); 1.11295 + GsmPDUHelper.writeSwappedNibbleBCDNum(Math.floor(seconds / 60) % 60); 1.11296 + GsmPDUHelper.writeSwappedNibbleBCDNum(seconds % 60); 1.11297 + }, 1.11298 + 1.11299 + getSizeOfLengthOctets: function(length) { 1.11300 + if (length >= 0x10000) { 1.11301 + return 4; // 0x83, len_1, len_2, len_3 1.11302 + } else if (length >= 0x100) { 1.11303 + return 3; // 0x82, len_1, len_2 1.11304 + } else if (length >= 0x80) { 1.11305 + return 2; // 0x81, len 1.11306 + } else { 1.11307 + return 1; // len 1.11308 + } 1.11309 + }, 1.11310 + 1.11311 + writeLength: function(length) { 1.11312 + let GsmPDUHelper = this.context.GsmPDUHelper; 1.11313 + 1.11314 + // TS 101.220 clause 7.1.2, Length Encoding. 1.11315 + // Length | Byte 1 | Byte 2 | Byte 3 | Byte 4 | 1.11316 + // 0 - 127 | 00 - 7f | N/A | N/A | N/A | 1.11317 + // 128-255 | 81 | 80 - ff| N/A | N/A | 1.11318 + // 256-65535| 82 | 0100 - ffff | N/A | 1.11319 + // 65536- | 83 | 010000 - ffffff | 1.11320 + // 16777215 1.11321 + if (length < 0x80) { 1.11322 + GsmPDUHelper.writeHexOctet(length); 1.11323 + } else if (0x80 <= length && length < 0x100) { 1.11324 + GsmPDUHelper.writeHexOctet(0x81); 1.11325 + GsmPDUHelper.writeHexOctet(length); 1.11326 + } else if (0x100 <= length && length < 0x10000) { 1.11327 + GsmPDUHelper.writeHexOctet(0x82); 1.11328 + GsmPDUHelper.writeHexOctet((length >> 8) & 0xff); 1.11329 + GsmPDUHelper.writeHexOctet(length & 0xff); 1.11330 + } else if (0x10000 <= length && length < 0x1000000) { 1.11331 + GsmPDUHelper.writeHexOctet(0x83); 1.11332 + GsmPDUHelper.writeHexOctet((length >> 16) & 0xff); 1.11333 + GsmPDUHelper.writeHexOctet((length >> 8) & 0xff); 1.11334 + GsmPDUHelper.writeHexOctet(length & 0xff); 1.11335 + } else { 1.11336 + throw new Error("Invalid length value :" + length); 1.11337 + } 1.11338 + }, 1.11339 +}; 1.11340 + 1.11341 +function BerTlvHelperObject(aContext) { 1.11342 + this.context = aContext; 1.11343 +} 1.11344 +BerTlvHelperObject.prototype = { 1.11345 + context: null, 1.11346 + 1.11347 + /** 1.11348 + * Decode Ber TLV. 1.11349 + * 1.11350 + * @param dataLen 1.11351 + * The length of data in bytes. 1.11352 + */ 1.11353 + decode: function(dataLen) { 1.11354 + let GsmPDUHelper = this.context.GsmPDUHelper; 1.11355 + 1.11356 + let hlen = 0; 1.11357 + let tag = GsmPDUHelper.readHexOctet(); 1.11358 + hlen++; 1.11359 + 1.11360 + // The length is coded onto 1 or 2 bytes. 1.11361 + // Length | Byte 1 | Byte 2 1.11362 + // 0 - 127 | length ('00' to '7f') | N/A 1.11363 + // 128 - 255 | '81' | length ('80' to 'ff') 1.11364 + let length; 1.11365 + let temp = GsmPDUHelper.readHexOctet(); 1.11366 + hlen++; 1.11367 + if (temp < 0x80) { 1.11368 + length = temp; 1.11369 + } else if (temp === 0x81) { 1.11370 + length = GsmPDUHelper.readHexOctet(); 1.11371 + hlen++; 1.11372 + if (length < 0x80) { 1.11373 + throw new Error("Invalid length " + length); 1.11374 + } 1.11375 + } else { 1.11376 + throw new Error("Invalid length octet " + temp); 1.11377 + } 1.11378 + 1.11379 + // Header + body length check. 1.11380 + if (dataLen - hlen !== length) { 1.11381 + throw new Error("Unexpected BerTlvHelper value length!!"); 1.11382 + } 1.11383 + 1.11384 + let method = this[tag]; 1.11385 + if (typeof method != "function") { 1.11386 + throw new Error("Unknown Ber tag 0x" + tag.toString(16)); 1.11387 + } 1.11388 + 1.11389 + let value = method.call(this, length); 1.11390 + 1.11391 + return { 1.11392 + tag: tag, 1.11393 + length: length, 1.11394 + value: value 1.11395 + }; 1.11396 + }, 1.11397 + 1.11398 + /** 1.11399 + * Process the value part for FCP template TLV. 1.11400 + * 1.11401 + * @param length 1.11402 + * The length of data in bytes. 1.11403 + */ 1.11404 + processFcpTemplate: function(length) { 1.11405 + let tlvs = this.decodeChunks(length); 1.11406 + return tlvs; 1.11407 + }, 1.11408 + 1.11409 + /** 1.11410 + * Process the value part for proactive command TLV. 1.11411 + * 1.11412 + * @param length 1.11413 + * The length of data in bytes. 1.11414 + */ 1.11415 + processProactiveCommand: function(length) { 1.11416 + let ctlvs = this.context.ComprehensionTlvHelper.decodeChunks(length); 1.11417 + return ctlvs; 1.11418 + }, 1.11419 + 1.11420 + /** 1.11421 + * Decode raw data to a Ber-TLV. 1.11422 + */ 1.11423 + decodeInnerTlv: function() { 1.11424 + let GsmPDUHelper = this.context.GsmPDUHelper; 1.11425 + let tag = GsmPDUHelper.readHexOctet(); 1.11426 + let length = GsmPDUHelper.readHexOctet(); 1.11427 + return { 1.11428 + tag: tag, 1.11429 + length: length, 1.11430 + value: this.retrieve(tag, length) 1.11431 + }; 1.11432 + }, 1.11433 + 1.11434 + decodeChunks: function(length) { 1.11435 + let chunks = []; 1.11436 + let index = 0; 1.11437 + while (index < length) { 1.11438 + let tlv = this.decodeInnerTlv(); 1.11439 + if (tlv.value) { 1.11440 + chunks.push(tlv); 1.11441 + } 1.11442 + index += tlv.length; 1.11443 + // tag + length fields consume 2 bytes. 1.11444 + index += 2; 1.11445 + } 1.11446 + return chunks; 1.11447 + }, 1.11448 + 1.11449 + retrieve: function(tag, length) { 1.11450 + let method = this[tag]; 1.11451 + if (typeof method != "function") { 1.11452 + if (DEBUG) { 1.11453 + this.context.debug("Unknown Ber tag : 0x" + tag.toString(16)); 1.11454 + } 1.11455 + let Buf = this.context.Buf; 1.11456 + Buf.seekIncoming(length * Buf.PDU_HEX_OCTET_SIZE); 1.11457 + return null; 1.11458 + } 1.11459 + return method.call(this, length); 1.11460 + }, 1.11461 + 1.11462 + /** 1.11463 + * File Size Data. 1.11464 + * 1.11465 + * | Byte | Description | Length | 1.11466 + * | 1 | Tag | 1 | 1.11467 + * | 2 | Length | 1 | 1.11468 + * | 3 to X+24 | Number of allocated data bytes in the file | X | 1.11469 + * | | , excluding structural information | | 1.11470 + */ 1.11471 + retrieveFileSizeData: function(length) { 1.11472 + let GsmPDUHelper = this.context.GsmPDUHelper; 1.11473 + let fileSizeData = 0; 1.11474 + for (let i = 0; i < length; i++) { 1.11475 + fileSizeData = fileSizeData << 8; 1.11476 + fileSizeData += GsmPDUHelper.readHexOctet(); 1.11477 + } 1.11478 + 1.11479 + return {fileSizeData: fileSizeData}; 1.11480 + }, 1.11481 + 1.11482 + /** 1.11483 + * File Descriptor. 1.11484 + * 1.11485 + * | Byte | Description | Length | 1.11486 + * | 1 | Tag | 1 | 1.11487 + * | 2 | Length | 1 | 1.11488 + * | 3 | File descriptor byte | 1 | 1.11489 + * | 4 | Data coding byte | 1 | 1.11490 + * | 5 ~ 6 | Record length | 2 | 1.11491 + * | 7 | Number of records | 1 | 1.11492 + */ 1.11493 + retrieveFileDescriptor: function(length) { 1.11494 + let GsmPDUHelper = this.context.GsmPDUHelper; 1.11495 + let fileDescriptorByte = GsmPDUHelper.readHexOctet(); 1.11496 + let dataCodingByte = GsmPDUHelper.readHexOctet(); 1.11497 + // See TS 102 221 Table 11.5, we only care the least 3 bits for the 1.11498 + // structure of file. 1.11499 + let fileStructure = fileDescriptorByte & 0x07; 1.11500 + 1.11501 + let fileDescriptor = { 1.11502 + fileStructure: fileStructure 1.11503 + }; 1.11504 + // byte 5 ~ 7 are mandatory for linear fixed and cyclic files, otherwise 1.11505 + // they are not applicable. 1.11506 + if (fileStructure === UICC_EF_STRUCTURE[EF_TYPE_LINEAR_FIXED] || 1.11507 + fileStructure === UICC_EF_STRUCTURE[EF_TYPE_CYCLIC]) { 1.11508 + fileDescriptor.recordLength = (GsmPDUHelper.readHexOctet() << 8) + 1.11509 + GsmPDUHelper.readHexOctet(); 1.11510 + fileDescriptor.numOfRecords = GsmPDUHelper.readHexOctet(); 1.11511 + } 1.11512 + 1.11513 + return fileDescriptor; 1.11514 + }, 1.11515 + 1.11516 + /** 1.11517 + * File identifier. 1.11518 + * 1.11519 + * | Byte | Description | Length | 1.11520 + * | 1 | Tag | 1 | 1.11521 + * | 2 | Length | 1 | 1.11522 + * | 3 ~ 4 | File identifier | 2 | 1.11523 + */ 1.11524 + retrieveFileIdentifier: function(length) { 1.11525 + let GsmPDUHelper = this.context.GsmPDUHelper; 1.11526 + return {fileId : (GsmPDUHelper.readHexOctet() << 8) + 1.11527 + GsmPDUHelper.readHexOctet()}; 1.11528 + }, 1.11529 + 1.11530 + searchForNextTag: function(tag, iter) { 1.11531 + for (let [index, tlv] in iter) { 1.11532 + if (tlv.tag === tag) { 1.11533 + return tlv; 1.11534 + } 1.11535 + } 1.11536 + return null; 1.11537 + } 1.11538 +}; 1.11539 +BerTlvHelperObject.prototype[BER_FCP_TEMPLATE_TAG] = function BER_FCP_TEMPLATE_TAG(length) { 1.11540 + return this.processFcpTemplate(length); 1.11541 +}; 1.11542 +BerTlvHelperObject.prototype[BER_PROACTIVE_COMMAND_TAG] = function BER_PROACTIVE_COMMAND_TAG(length) { 1.11543 + return this.processProactiveCommand(length); 1.11544 +}; 1.11545 +BerTlvHelperObject.prototype[BER_FCP_FILE_SIZE_DATA_TAG] = function BER_FCP_FILE_SIZE_DATA_TAG(length) { 1.11546 + return this.retrieveFileSizeData(length); 1.11547 +}; 1.11548 +BerTlvHelperObject.prototype[BER_FCP_FILE_DESCRIPTOR_TAG] = function BER_FCP_FILE_DESCRIPTOR_TAG(length) { 1.11549 + return this.retrieveFileDescriptor(length); 1.11550 +}; 1.11551 +BerTlvHelperObject.prototype[BER_FCP_FILE_IDENTIFIER_TAG] = function BER_FCP_FILE_IDENTIFIER_TAG(length) { 1.11552 + return this.retrieveFileIdentifier(length); 1.11553 +}; 1.11554 + 1.11555 +/** 1.11556 + * ICC Helper for getting EF path. 1.11557 + */ 1.11558 +function ICCFileHelperObject(aContext) { 1.11559 + this.context = aContext; 1.11560 +} 1.11561 +ICCFileHelperObject.prototype = { 1.11562 + context: null, 1.11563 + 1.11564 + /** 1.11565 + * This function handles only EFs that are common to RUIM, SIM, USIM 1.11566 + * and other types of ICC cards. 1.11567 + */ 1.11568 + getCommonEFPath: function(fileId) { 1.11569 + switch (fileId) { 1.11570 + case ICC_EF_ICCID: 1.11571 + return EF_PATH_MF_SIM; 1.11572 + case ICC_EF_ADN: 1.11573 + return EF_PATH_MF_SIM + EF_PATH_DF_TELECOM; 1.11574 + case ICC_EF_PBR: 1.11575 + return EF_PATH_MF_SIM + EF_PATH_DF_TELECOM + EF_PATH_DF_PHONEBOOK; 1.11576 + } 1.11577 + return null; 1.11578 + }, 1.11579 + 1.11580 + /** 1.11581 + * This function handles EFs for SIM. 1.11582 + */ 1.11583 + getSimEFPath: function(fileId) { 1.11584 + switch (fileId) { 1.11585 + case ICC_EF_FDN: 1.11586 + case ICC_EF_MSISDN: 1.11587 + case ICC_EF_SMS: 1.11588 + return EF_PATH_MF_SIM + EF_PATH_DF_TELECOM; 1.11589 + case ICC_EF_AD: 1.11590 + case ICC_EF_MBDN: 1.11591 + case ICC_EF_MWIS: 1.11592 + case ICC_EF_PLMNsel: 1.11593 + case ICC_EF_SPN: 1.11594 + case ICC_EF_SPDI: 1.11595 + case ICC_EF_SST: 1.11596 + case ICC_EF_PHASE: 1.11597 + case ICC_EF_CBMI: 1.11598 + case ICC_EF_CBMID: 1.11599 + case ICC_EF_CBMIR: 1.11600 + case ICC_EF_OPL: 1.11601 + case ICC_EF_PNN: 1.11602 + return EF_PATH_MF_SIM + EF_PATH_DF_GSM; 1.11603 + default: 1.11604 + return null; 1.11605 + } 1.11606 + }, 1.11607 + 1.11608 + /** 1.11609 + * This function handles EFs for USIM. 1.11610 + */ 1.11611 + getUSimEFPath: function(fileId) { 1.11612 + switch (fileId) { 1.11613 + case ICC_EF_AD: 1.11614 + case ICC_EF_FDN: 1.11615 + case ICC_EF_MBDN: 1.11616 + case ICC_EF_MWIS: 1.11617 + case ICC_EF_UST: 1.11618 + case ICC_EF_MSISDN: 1.11619 + case ICC_EF_SPN: 1.11620 + case ICC_EF_SPDI: 1.11621 + case ICC_EF_CBMI: 1.11622 + case ICC_EF_CBMID: 1.11623 + case ICC_EF_CBMIR: 1.11624 + case ICC_EF_OPL: 1.11625 + case ICC_EF_PNN: 1.11626 + case ICC_EF_SMS: 1.11627 + return EF_PATH_MF_SIM + EF_PATH_ADF_USIM; 1.11628 + default: 1.11629 + // The file ids in USIM phone book entries are decided by the 1.11630 + // card manufacturer. So if we don't match any of the cases 1.11631 + // above and if its a USIM return the phone book path. 1.11632 + return EF_PATH_MF_SIM + EF_PATH_DF_TELECOM + EF_PATH_DF_PHONEBOOK; 1.11633 + } 1.11634 + }, 1.11635 + 1.11636 + /** 1.11637 + * This function handles EFs for RUIM 1.11638 + */ 1.11639 + getRuimEFPath: function(fileId) { 1.11640 + switch(fileId) { 1.11641 + case ICC_EF_CSIM_IMSI_M: 1.11642 + case ICC_EF_CSIM_CDMAHOME: 1.11643 + case ICC_EF_CSIM_CST: 1.11644 + case ICC_EF_CSIM_SPN: 1.11645 + return EF_PATH_MF_SIM + EF_PATH_DF_CDMA; 1.11646 + case ICC_EF_FDN: 1.11647 + return EF_PATH_MF_SIM + EF_PATH_DF_TELECOM; 1.11648 + default: 1.11649 + return null; 1.11650 + } 1.11651 + }, 1.11652 + 1.11653 + /** 1.11654 + * Helper function for getting the pathId for the specific ICC record 1.11655 + * depeding on which type of ICC card we are using. 1.11656 + * 1.11657 + * @param fileId 1.11658 + * File id. 1.11659 + * @return The pathId or null in case of an error or invalid input. 1.11660 + */ 1.11661 + getEFPath: function(fileId) { 1.11662 + let appType = this.context.RIL.appType; 1.11663 + if (appType == null) { 1.11664 + return null; 1.11665 + } 1.11666 + 1.11667 + let path = this.getCommonEFPath(fileId); 1.11668 + if (path) { 1.11669 + return path; 1.11670 + } 1.11671 + 1.11672 + switch (appType) { 1.11673 + case CARD_APPTYPE_SIM: 1.11674 + return this.getSimEFPath(fileId); 1.11675 + case CARD_APPTYPE_USIM: 1.11676 + return this.getUSimEFPath(fileId); 1.11677 + case CARD_APPTYPE_RUIM: 1.11678 + return this.getRuimEFPath(fileId); 1.11679 + default: 1.11680 + return null; 1.11681 + } 1.11682 + } 1.11683 +}; 1.11684 + 1.11685 +/** 1.11686 + * Helper for ICC IO functionalities. 1.11687 + */ 1.11688 +function ICCIOHelperObject(aContext) { 1.11689 + this.context = aContext; 1.11690 +} 1.11691 +ICCIOHelperObject.prototype = { 1.11692 + context: null, 1.11693 + 1.11694 + /** 1.11695 + * Load EF with type 'Linear Fixed'. 1.11696 + * 1.11697 + * @param fileId 1.11698 + * The file to operate on, one of the ICC_EF_* constants. 1.11699 + * @param recordNumber [optional] 1.11700 + * The number of the record shall be loaded. 1.11701 + * @param recordSize [optional] 1.11702 + * The size of the record. 1.11703 + * @param callback [optional] 1.11704 + * The callback function shall be called when the record(s) is read. 1.11705 + * @param onerror [optional] 1.11706 + * The callback function shall be called when failure. 1.11707 + */ 1.11708 + loadLinearFixedEF: function(options) { 1.11709 + let cb; 1.11710 + let readRecord = (function(options) { 1.11711 + options.command = ICC_COMMAND_READ_RECORD; 1.11712 + options.p1 = options.recordNumber || 1; // Record number 1.11713 + options.p2 = READ_RECORD_ABSOLUTE_MODE; 1.11714 + options.p3 = options.recordSize; 1.11715 + options.callback = cb || options.callback; 1.11716 + this.context.RIL.iccIO(options); 1.11717 + }).bind(this); 1.11718 + 1.11719 + options.type = EF_TYPE_LINEAR_FIXED; 1.11720 + options.pathId = this.context.ICCFileHelper.getEFPath(options.fileId); 1.11721 + if (options.recordSize) { 1.11722 + readRecord(options); 1.11723 + return; 1.11724 + } 1.11725 + 1.11726 + cb = options.callback; 1.11727 + options.callback = readRecord; 1.11728 + this.getResponse(options); 1.11729 + }, 1.11730 + 1.11731 + /** 1.11732 + * Load next record from current record Id. 1.11733 + */ 1.11734 + loadNextRecord: function(options) { 1.11735 + options.p1++; 1.11736 + this.context.RIL.iccIO(options); 1.11737 + }, 1.11738 + 1.11739 + /** 1.11740 + * Update EF with type 'Linear Fixed'. 1.11741 + * 1.11742 + * @param fileId 1.11743 + * The file to operate on, one of the ICC_EF_* constants. 1.11744 + * @param recordNumber 1.11745 + * The number of the record shall be updated. 1.11746 + * @param dataWriter [optional] 1.11747 + * The function for writing string parameter for the ICC_COMMAND_UPDATE_RECORD. 1.11748 + * @param pin2 [optional] 1.11749 + * PIN2 is required when updating ICC_EF_FDN. 1.11750 + * @param callback [optional] 1.11751 + * The callback function shall be called when the record is updated. 1.11752 + * @param onerror [optional] 1.11753 + * The callback function shall be called when failure. 1.11754 + */ 1.11755 + updateLinearFixedEF: function(options) { 1.11756 + if (!options.fileId || !options.recordNumber) { 1.11757 + throw new Error("Unexpected fileId " + options.fileId + 1.11758 + " or recordNumber " + options.recordNumber); 1.11759 + } 1.11760 + 1.11761 + options.type = EF_TYPE_LINEAR_FIXED; 1.11762 + options.pathId = this.context.ICCFileHelper.getEFPath(options.fileId); 1.11763 + let cb = options.callback; 1.11764 + options.callback = function callback(options) { 1.11765 + options.callback = cb; 1.11766 + options.command = ICC_COMMAND_UPDATE_RECORD; 1.11767 + options.p1 = options.recordNumber; 1.11768 + options.p2 = READ_RECORD_ABSOLUTE_MODE; 1.11769 + options.p3 = options.recordSize; 1.11770 + this.context.RIL.iccIO(options); 1.11771 + }.bind(this); 1.11772 + this.getResponse(options); 1.11773 + }, 1.11774 + 1.11775 + /** 1.11776 + * Load EF with type 'Transparent'. 1.11777 + * 1.11778 + * @param fileId 1.11779 + * The file to operate on, one of the ICC_EF_* constants. 1.11780 + * @param callback [optional] 1.11781 + * The callback function shall be called when the record(s) is read. 1.11782 + * @param onerror [optional] 1.11783 + * The callback function shall be called when failure. 1.11784 + */ 1.11785 + loadTransparentEF: function(options) { 1.11786 + options.type = EF_TYPE_TRANSPARENT; 1.11787 + let cb = options.callback; 1.11788 + options.callback = function callback(options) { 1.11789 + options.callback = cb; 1.11790 + options.command = ICC_COMMAND_READ_BINARY; 1.11791 + options.p3 = options.fileSize; 1.11792 + this.context.RIL.iccIO(options); 1.11793 + }.bind(this); 1.11794 + this.getResponse(options); 1.11795 + }, 1.11796 + 1.11797 + /** 1.11798 + * Use ICC_COMMAND_GET_RESPONSE to query the EF. 1.11799 + * 1.11800 + * @param fileId 1.11801 + * The file to operate on, one of the ICC_EF_* constants. 1.11802 + */ 1.11803 + getResponse: function(options) { 1.11804 + options.command = ICC_COMMAND_GET_RESPONSE; 1.11805 + options.pathId = options.pathId || 1.11806 + this.context.ICCFileHelper.getEFPath(options.fileId); 1.11807 + if (!options.pathId) { 1.11808 + throw new Error("Unknown pathId for " + options.fileId.toString(16)); 1.11809 + } 1.11810 + options.p1 = 0; // For GET_RESPONSE, p1 = 0 1.11811 + options.p2 = 0; // For GET_RESPONSE, p2 = 0 1.11812 + options.p3 = GET_RESPONSE_EF_SIZE_BYTES; 1.11813 + this.context.RIL.iccIO(options); 1.11814 + }, 1.11815 + 1.11816 + /** 1.11817 + * Process ICC I/O response. 1.11818 + */ 1.11819 + processICCIO: function(options) { 1.11820 + let func = this[options.command]; 1.11821 + func.call(this, options); 1.11822 + }, 1.11823 + 1.11824 + /** 1.11825 + * Process a ICC_COMMAND_GET_RESPONSE type command for REQUEST_SIM_IO. 1.11826 + */ 1.11827 + processICCIOGetResponse: function(options) { 1.11828 + let Buf = this.context.Buf; 1.11829 + let strLen = Buf.readInt32(); 1.11830 + 1.11831 + let peek = this.context.GsmPDUHelper.readHexOctet(); 1.11832 + Buf.seekIncoming(-1 * Buf.PDU_HEX_OCTET_SIZE); 1.11833 + if (peek === BER_FCP_TEMPLATE_TAG) { 1.11834 + this.processUSimGetResponse(options, strLen / 2); 1.11835 + } else { 1.11836 + this.processSimGetResponse(options); 1.11837 + } 1.11838 + Buf.readStringDelimiter(strLen); 1.11839 + 1.11840 + if (options.callback) { 1.11841 + options.callback(options); 1.11842 + } 1.11843 + }, 1.11844 + 1.11845 + /** 1.11846 + * Helper function for processing USIM get response. 1.11847 + */ 1.11848 + processUSimGetResponse: function(options, octetLen) { 1.11849 + let BerTlvHelper = this.context.BerTlvHelper; 1.11850 + 1.11851 + let berTlv = BerTlvHelper.decode(octetLen); 1.11852 + // See TS 102 221 Table 11.4 for the content order of getResponse. 1.11853 + let iter = Iterator(berTlv.value); 1.11854 + let tlv = BerTlvHelper.searchForNextTag(BER_FCP_FILE_DESCRIPTOR_TAG, 1.11855 + iter); 1.11856 + if (!tlv || (tlv.value.fileStructure !== UICC_EF_STRUCTURE[options.type])) { 1.11857 + throw new Error("Expected EF type " + UICC_EF_STRUCTURE[options.type] + 1.11858 + " but read " + tlv.value.fileStructure); 1.11859 + } 1.11860 + 1.11861 + if (tlv.value.fileStructure === UICC_EF_STRUCTURE[EF_TYPE_LINEAR_FIXED] || 1.11862 + tlv.value.fileStructure === UICC_EF_STRUCTURE[EF_TYPE_CYCLIC]) { 1.11863 + options.recordSize = tlv.value.recordLength; 1.11864 + options.totalRecords = tlv.value.numOfRecords; 1.11865 + } 1.11866 + 1.11867 + tlv = BerTlvHelper.searchForNextTag(BER_FCP_FILE_IDENTIFIER_TAG, iter); 1.11868 + if (!tlv || (tlv.value.fileId !== options.fileId)) { 1.11869 + throw new Error("Expected file ID " + options.fileId.toString(16) + 1.11870 + " but read " + fileId.toString(16)); 1.11871 + } 1.11872 + 1.11873 + tlv = BerTlvHelper.searchForNextTag(BER_FCP_FILE_SIZE_DATA_TAG, iter); 1.11874 + if (!tlv) { 1.11875 + throw new Error("Unexpected file size data"); 1.11876 + } 1.11877 + options.fileSize = tlv.value.fileSizeData; 1.11878 + }, 1.11879 + 1.11880 + /** 1.11881 + * Helper function for processing SIM get response. 1.11882 + */ 1.11883 + processSimGetResponse: function(options) { 1.11884 + let Buf = this.context.Buf; 1.11885 + let GsmPDUHelper = this.context.GsmPDUHelper; 1.11886 + 1.11887 + // The format is from TS 51.011, clause 9.2.1 1.11888 + 1.11889 + // Skip RFU, data[0] data[1]. 1.11890 + Buf.seekIncoming(2 * Buf.PDU_HEX_OCTET_SIZE); 1.11891 + 1.11892 + // File size, data[2], data[3] 1.11893 + options.fileSize = (GsmPDUHelper.readHexOctet() << 8) | 1.11894 + GsmPDUHelper.readHexOctet(); 1.11895 + 1.11896 + // 2 bytes File id. data[4], data[5] 1.11897 + let fileId = (GsmPDUHelper.readHexOctet() << 8) | 1.11898 + GsmPDUHelper.readHexOctet(); 1.11899 + if (fileId != options.fileId) { 1.11900 + throw new Error("Expected file ID " + options.fileId.toString(16) + 1.11901 + " but read " + fileId.toString(16)); 1.11902 + } 1.11903 + 1.11904 + // Type of file, data[6] 1.11905 + let fileType = GsmPDUHelper.readHexOctet(); 1.11906 + if (fileType != TYPE_EF) { 1.11907 + throw new Error("Unexpected file type " + fileType); 1.11908 + } 1.11909 + 1.11910 + // Skip 1 byte RFU, data[7], 1.11911 + // 3 bytes Access conditions, data[8] data[9] data[10], 1.11912 + // 1 byte File status, data[11], 1.11913 + // 1 byte Length of the following data, data[12]. 1.11914 + Buf.seekIncoming(((RESPONSE_DATA_STRUCTURE - RESPONSE_DATA_FILE_TYPE - 1) * 1.11915 + Buf.PDU_HEX_OCTET_SIZE)); 1.11916 + 1.11917 + // Read Structure of EF, data[13] 1.11918 + let efType = GsmPDUHelper.readHexOctet(); 1.11919 + if (efType != options.type) { 1.11920 + throw new Error("Expected EF type " + options.type + " but read " + efType); 1.11921 + } 1.11922 + 1.11923 + // TODO: Bug 952025. 1.11924 + // Length of a record, data[14]. 1.11925 + // Only available for LINEAR_FIXED and CYCLIC. 1.11926 + if (efType == EF_TYPE_LINEAR_FIXED || efType == EF_TYPE_CYCLIC) { 1.11927 + options.recordSize = GsmPDUHelper.readHexOctet(); 1.11928 + options.totalRecords = options.fileSize / options.recordSize; 1.11929 + } else { 1.11930 + Buf.seekIncoming(1 * Buf.PDU_HEX_OCTET_SIZE); 1.11931 + } 1.11932 + }, 1.11933 + 1.11934 + /** 1.11935 + * Process a ICC_COMMAND_READ_RECORD type command for REQUEST_SIM_IO. 1.11936 + */ 1.11937 + processICCIOReadRecord: function(options) { 1.11938 + if (options.callback) { 1.11939 + options.callback(options); 1.11940 + } 1.11941 + }, 1.11942 + 1.11943 + /** 1.11944 + * Process a ICC_COMMAND_READ_BINARY type command for REQUEST_SIM_IO. 1.11945 + */ 1.11946 + processICCIOReadBinary: function(options) { 1.11947 + if (options.callback) { 1.11948 + options.callback(options); 1.11949 + } 1.11950 + }, 1.11951 + 1.11952 + /** 1.11953 + * Process a ICC_COMMAND_UPDATE_RECORD type command for REQUEST_SIM_IO. 1.11954 + */ 1.11955 + processICCIOUpdateRecord: function(options) { 1.11956 + if (options.callback) { 1.11957 + options.callback(options); 1.11958 + } 1.11959 + }, 1.11960 + 1.11961 + /** 1.11962 + * Process ICC IO error. 1.11963 + */ 1.11964 + processICCIOError: function(options) { 1.11965 + let requestError = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError]; 1.11966 + if (DEBUG) { 1.11967 + // See GSM11.11, TS 51.011 clause 9.4, and ISO 7816-4 for the error 1.11968 + // description. 1.11969 + let errorMsg = "ICC I/O Error code " + requestError + 1.11970 + " EF id = " + options.fileId.toString(16) + 1.11971 + " command = " + options.command.toString(16); 1.11972 + if (options.sw1 && options.sw2) { 1.11973 + errorMsg += "(" + options.sw1.toString(16) + 1.11974 + "/" + options.sw2.toString(16) + ")"; 1.11975 + } 1.11976 + this.context.debug(errorMsg); 1.11977 + } 1.11978 + if (options.onerror) { 1.11979 + options.onerror(requestError); 1.11980 + } 1.11981 + }, 1.11982 +}; 1.11983 +ICCIOHelperObject.prototype[ICC_COMMAND_SEEK] = null; 1.11984 +ICCIOHelperObject.prototype[ICC_COMMAND_READ_BINARY] = function ICC_COMMAND_READ_BINARY(options) { 1.11985 + this.processICCIOReadBinary(options); 1.11986 +}; 1.11987 +ICCIOHelperObject.prototype[ICC_COMMAND_READ_RECORD] = function ICC_COMMAND_READ_RECORD(options) { 1.11988 + this.processICCIOReadRecord(options); 1.11989 +}; 1.11990 +ICCIOHelperObject.prototype[ICC_COMMAND_GET_RESPONSE] = function ICC_COMMAND_GET_RESPONSE(options) { 1.11991 + this.processICCIOGetResponse(options); 1.11992 +}; 1.11993 +ICCIOHelperObject.prototype[ICC_COMMAND_UPDATE_BINARY] = null; 1.11994 +ICCIOHelperObject.prototype[ICC_COMMAND_UPDATE_RECORD] = function ICC_COMMAND_UPDATE_RECORD(options) { 1.11995 + this.processICCIOUpdateRecord(options); 1.11996 +}; 1.11997 + 1.11998 +/** 1.11999 + * Helper for ICC records. 1.12000 + */ 1.12001 +function ICCRecordHelperObject(aContext) { 1.12002 + this.context = aContext; 1.12003 +} 1.12004 +ICCRecordHelperObject.prototype = { 1.12005 + context: null, 1.12006 + 1.12007 + /** 1.12008 + * Fetch ICC records. 1.12009 + */ 1.12010 + fetchICCRecords: function() { 1.12011 + switch (this.context.RIL.appType) { 1.12012 + case CARD_APPTYPE_SIM: 1.12013 + case CARD_APPTYPE_USIM: 1.12014 + this.context.SimRecordHelper.fetchSimRecords(); 1.12015 + break; 1.12016 + case CARD_APPTYPE_RUIM: 1.12017 + this.context.RuimRecordHelper.fetchRuimRecords(); 1.12018 + break; 1.12019 + } 1.12020 + }, 1.12021 + 1.12022 + /** 1.12023 + * Read the ICCID. 1.12024 + */ 1.12025 + readICCID: function() { 1.12026 + function callback() { 1.12027 + let Buf = this.context.Buf; 1.12028 + let RIL = this.context.RIL; 1.12029 + 1.12030 + let strLen = Buf.readInt32(); 1.12031 + let octetLen = strLen / 2; 1.12032 + RIL.iccInfo.iccid = 1.12033 + this.context.GsmPDUHelper.readSwappedNibbleBcdString(octetLen, true); 1.12034 + // Consumes the remaining buffer if any. 1.12035 + let unReadBuffer = this.context.Buf.getReadAvailable() - 1.12036 + this.context.Buf.PDU_HEX_OCTET_SIZE; 1.12037 + if (unReadBuffer > 0) { 1.12038 + this.context.Buf.seekIncoming(unReadBuffer); 1.12039 + } 1.12040 + Buf.readStringDelimiter(strLen); 1.12041 + 1.12042 + if (DEBUG) this.context.debug("ICCID: " + RIL.iccInfo.iccid); 1.12043 + if (RIL.iccInfo.iccid) { 1.12044 + this.context.ICCUtilsHelper.handleICCInfoChange(); 1.12045 + RIL.reportStkServiceIsRunning(); 1.12046 + } 1.12047 + } 1.12048 + 1.12049 + this.context.ICCIOHelper.loadTransparentEF({ 1.12050 + fileId: ICC_EF_ICCID, 1.12051 + callback: callback.bind(this) 1.12052 + }); 1.12053 + }, 1.12054 + 1.12055 + /** 1.12056 + * Read ICC ADN like EF, i.e. EF_ADN, EF_FDN. 1.12057 + * 1.12058 + * @param fileId EF id of the ADN or FDN. 1.12059 + * @param onsuccess Callback to be called when success. 1.12060 + * @param onerror Callback to be called when error. 1.12061 + */ 1.12062 + readADNLike: function(fileId, onsuccess, onerror) { 1.12063 + let ICCIOHelper = this.context.ICCIOHelper; 1.12064 + 1.12065 + function callback(options) { 1.12066 + let contact = 1.12067 + this.context.ICCPDUHelper.readAlphaIdDiallingNumber(options.recordSize); 1.12068 + if (contact) { 1.12069 + contact.recordId = options.p1; 1.12070 + contacts.push(contact); 1.12071 + } 1.12072 + 1.12073 + if (options.p1 < options.totalRecords) { 1.12074 + ICCIOHelper.loadNextRecord(options); 1.12075 + } else { 1.12076 + if (DEBUG) { 1.12077 + for (let i = 0; i < contacts.length; i++) { 1.12078 + this.context.debug("contact [" + i + "] " + 1.12079 + JSON.stringify(contacts[i])); 1.12080 + } 1.12081 + } 1.12082 + if (onsuccess) { 1.12083 + onsuccess(contacts); 1.12084 + } 1.12085 + } 1.12086 + } 1.12087 + 1.12088 + let contacts = []; 1.12089 + ICCIOHelper.loadLinearFixedEF({fileId: fileId, 1.12090 + callback: callback.bind(this), 1.12091 + onerror: onerror}); 1.12092 + }, 1.12093 + 1.12094 + /** 1.12095 + * Update ICC ADN like EFs, like EF_ADN, EF_FDN. 1.12096 + * 1.12097 + * @param fileId EF id of the ADN or FDN. 1.12098 + * @param contact The contact will be updated. (Shall have recordId property) 1.12099 + * @param pin2 PIN2 is required when updating ICC_EF_FDN. 1.12100 + * @param onsuccess Callback to be called when success. 1.12101 + * @param onerror Callback to be called when error. 1.12102 + */ 1.12103 + updateADNLike: function(fileId, contact, pin2, onsuccess, onerror) { 1.12104 + function dataWriter(recordSize) { 1.12105 + this.context.ICCPDUHelper.writeAlphaIdDiallingNumber(recordSize, 1.12106 + contact.alphaId, 1.12107 + contact.number); 1.12108 + } 1.12109 + 1.12110 + function callback(options) { 1.12111 + if (onsuccess) { 1.12112 + onsuccess(); 1.12113 + } 1.12114 + } 1.12115 + 1.12116 + if (!contact || !contact.recordId) { 1.12117 + if (onerror) onerror(GECKO_ERROR_INVALID_PARAMETER); 1.12118 + return; 1.12119 + } 1.12120 + 1.12121 + this.context.ICCIOHelper.updateLinearFixedEF({ 1.12122 + fileId: fileId, 1.12123 + recordNumber: contact.recordId, 1.12124 + dataWriter: dataWriter.bind(this), 1.12125 + pin2: pin2, 1.12126 + callback: callback.bind(this), 1.12127 + onerror: onerror 1.12128 + }); 1.12129 + }, 1.12130 + 1.12131 + /** 1.12132 + * Read USIM/RUIM Phonebook. 1.12133 + * 1.12134 + * @param onsuccess Callback to be called when success. 1.12135 + * @param onerror Callback to be called when error. 1.12136 + */ 1.12137 + readPBR: function(onsuccess, onerror) { 1.12138 + let Buf = this.context.Buf; 1.12139 + let GsmPDUHelper = this.context.GsmPDUHelper; 1.12140 + let ICCIOHelper = this.context.ICCIOHelper; 1.12141 + let ICCUtilsHelper = this.context.ICCUtilsHelper; 1.12142 + let RIL = this.context.RIL; 1.12143 + 1.12144 + function callback(options) { 1.12145 + let strLen = Buf.readInt32(); 1.12146 + let octetLen = strLen / 2, readLen = 0; 1.12147 + 1.12148 + let pbrTlvs = []; 1.12149 + while (readLen < octetLen) { 1.12150 + let tag = GsmPDUHelper.readHexOctet(); 1.12151 + if (tag == 0xff) { 1.12152 + readLen++; 1.12153 + Buf.seekIncoming((octetLen - readLen) * Buf.PDU_HEX_OCTET_SIZE); 1.12154 + break; 1.12155 + } 1.12156 + 1.12157 + let tlvLen = GsmPDUHelper.readHexOctet(); 1.12158 + let tlvs = ICCUtilsHelper.decodeSimTlvs(tlvLen); 1.12159 + pbrTlvs.push({tag: tag, 1.12160 + length: tlvLen, 1.12161 + value: tlvs}); 1.12162 + 1.12163 + readLen += tlvLen + 2; // +2 for tag and tlvLen 1.12164 + } 1.12165 + Buf.readStringDelimiter(strLen); 1.12166 + 1.12167 + if (pbrTlvs.length > 0) { 1.12168 + let pbr = ICCUtilsHelper.parsePbrTlvs(pbrTlvs); 1.12169 + // EF_ADN is mandatory if and only if DF_PHONEBOOK is present. 1.12170 + if (!pbr.adn) { 1.12171 + if (onerror) onerror("Cannot access ADN."); 1.12172 + return; 1.12173 + } 1.12174 + pbrs.push(pbr); 1.12175 + } 1.12176 + 1.12177 + if (options.p1 < options.totalRecords) { 1.12178 + ICCIOHelper.loadNextRecord(options); 1.12179 + } else { 1.12180 + if (onsuccess) { 1.12181 + RIL.iccInfoPrivate.pbrs = pbrs; 1.12182 + onsuccess(pbrs); 1.12183 + } 1.12184 + } 1.12185 + } 1.12186 + 1.12187 + if (RIL.iccInfoPrivate.pbrs) { 1.12188 + onsuccess(RIL.iccInfoPrivate.pbrs); 1.12189 + return; 1.12190 + } 1.12191 + 1.12192 + let pbrs = []; 1.12193 + ICCIOHelper.loadLinearFixedEF({fileId : ICC_EF_PBR, 1.12194 + callback: callback.bind(this), 1.12195 + onerror: onerror}); 1.12196 + }, 1.12197 + 1.12198 + /** 1.12199 + * Cache EF_IAP record size. 1.12200 + */ 1.12201 + _iapRecordSize: null, 1.12202 + 1.12203 + /** 1.12204 + * Read ICC EF_IAP. (Index Administration Phonebook) 1.12205 + * 1.12206 + * @see TS 131.102, clause 4.4.2.2 1.12207 + * 1.12208 + * @param fileId EF id of the IAP. 1.12209 + * @param recordNumber The number of the record shall be loaded. 1.12210 + * @param onsuccess Callback to be called when success. 1.12211 + * @param onerror Callback to be called when error. 1.12212 + */ 1.12213 + readIAP: function(fileId, recordNumber, onsuccess, onerror) { 1.12214 + function callback(options) { 1.12215 + let Buf = this.context.Buf; 1.12216 + let strLen = Buf.readInt32(); 1.12217 + let octetLen = strLen / 2; 1.12218 + this._iapRecordSize = options.recordSize; 1.12219 + 1.12220 + let iap = this.context.GsmPDUHelper.readHexOctetArray(octetLen); 1.12221 + Buf.readStringDelimiter(strLen); 1.12222 + 1.12223 + if (onsuccess) { 1.12224 + onsuccess(iap); 1.12225 + } 1.12226 + } 1.12227 + 1.12228 + this.context.ICCIOHelper.loadLinearFixedEF({ 1.12229 + fileId: fileId, 1.12230 + recordNumber: recordNumber, 1.12231 + recordSize: this._iapRecordSize, 1.12232 + callback: callback.bind(this), 1.12233 + onerror: onerror 1.12234 + }); 1.12235 + }, 1.12236 + 1.12237 + /** 1.12238 + * Update USIM/RUIM Phonebook EF_IAP. 1.12239 + * 1.12240 + * @see TS 131.102, clause 4.4.2.13 1.12241 + * 1.12242 + * @param fileId EF id of the IAP. 1.12243 + * @param recordNumber The identifier of the record shall be updated. 1.12244 + * @param iap The IAP value to be written. 1.12245 + * @param onsuccess Callback to be called when success. 1.12246 + * @param onerror Callback to be called when error. 1.12247 + */ 1.12248 + updateIAP: function(fileId, recordNumber, iap, onsuccess, onerror) { 1.12249 + let dataWriter = function dataWriter(recordSize) { 1.12250 + let Buf = this.context.Buf; 1.12251 + let GsmPDUHelper = this.context.GsmPDUHelper; 1.12252 + 1.12253 + // Write String length 1.12254 + let strLen = recordSize * 2; 1.12255 + Buf.writeInt32(strLen); 1.12256 + 1.12257 + for (let i = 0; i < iap.length; i++) { 1.12258 + GsmPDUHelper.writeHexOctet(iap[i]); 1.12259 + } 1.12260 + 1.12261 + Buf.writeStringDelimiter(strLen); 1.12262 + }.bind(this); 1.12263 + 1.12264 + this.context.ICCIOHelper.updateLinearFixedEF({ 1.12265 + fileId: fileId, 1.12266 + recordNumber: recordNumber, 1.12267 + dataWriter: dataWriter, 1.12268 + callback: onsuccess, 1.12269 + onerror: onerror 1.12270 + }); 1.12271 + }, 1.12272 + 1.12273 + /** 1.12274 + * Cache EF_Email record size. 1.12275 + */ 1.12276 + _emailRecordSize: null, 1.12277 + 1.12278 + /** 1.12279 + * Read USIM/RUIM Phonebook EF_EMAIL. 1.12280 + * 1.12281 + * @see TS 131.102, clause 4.4.2.13 1.12282 + * 1.12283 + * @param fileId EF id of the EMAIL. 1.12284 + * @param fileType The type of the EMAIL, one of the ICC_USIM_TYPE* constants. 1.12285 + * @param recordNumber The number of the record shall be loaded. 1.12286 + * @param onsuccess Callback to be called when success. 1.12287 + * @param onerror Callback to be called when error. 1.12288 + */ 1.12289 + readEmail: function(fileId, fileType, recordNumber, onsuccess, onerror) { 1.12290 + function callback(options) { 1.12291 + let Buf = this.context.Buf; 1.12292 + let ICCPDUHelper = this.context.ICCPDUHelper; 1.12293 + 1.12294 + let strLen = Buf.readInt32(); 1.12295 + let octetLen = strLen / 2; 1.12296 + let email = null; 1.12297 + this._emailRecordSize = options.recordSize; 1.12298 + 1.12299 + // Read contact's email 1.12300 + // 1.12301 + // | Byte | Description | Length | M/O 1.12302 + // | 1 ~ X | E-mail Address | X | M 1.12303 + // | X+1 | ADN file SFI | 1 | C 1.12304 + // | X+2 | ADN file Record Identifier | 1 | C 1.12305 + // Note: The fields marked as C above are mandatort if the file 1.12306 + // is not type 1 (as specified in EF_PBR) 1.12307 + if (fileType == ICC_USIM_TYPE1_TAG) { 1.12308 + email = ICCPDUHelper.read8BitUnpackedToString(octetLen); 1.12309 + } else { 1.12310 + email = ICCPDUHelper.read8BitUnpackedToString(octetLen - 2); 1.12311 + 1.12312 + // Consumes the remaining buffer 1.12313 + Buf.seekIncoming(2 * Buf.PDU_HEX_OCTET_SIZE); // For ADN SFI and Record Identifier 1.12314 + } 1.12315 + 1.12316 + Buf.readStringDelimiter(strLen); 1.12317 + 1.12318 + if (onsuccess) { 1.12319 + onsuccess(email); 1.12320 + } 1.12321 + } 1.12322 + 1.12323 + this.context.ICCIOHelper.loadLinearFixedEF({ 1.12324 + fileId: fileId, 1.12325 + recordNumber: recordNumber, 1.12326 + recordSize: this._emailRecordSize, 1.12327 + callback: callback.bind(this), 1.12328 + onerror: onerror 1.12329 + }); 1.12330 + }, 1.12331 + 1.12332 + /** 1.12333 + * Update USIM/RUIM Phonebook EF_EMAIL. 1.12334 + * 1.12335 + * @see TS 131.102, clause 4.4.2.13 1.12336 + * 1.12337 + * @param pbr Phonebook Reference File. 1.12338 + * @param recordNumber The identifier of the record shall be updated. 1.12339 + * @param email The value to be written. 1.12340 + * @param adnRecordId The record Id of ADN, only needed if the fileType of Email is TYPE2. 1.12341 + * @param onsuccess Callback to be called when success. 1.12342 + * @param onerror Callback to be called when error. 1.12343 + */ 1.12344 + updateEmail: function(pbr, recordNumber, email, adnRecordId, onsuccess, onerror) { 1.12345 + let fileId = pbr[USIM_PBR_EMAIL].fileId; 1.12346 + let fileType = pbr[USIM_PBR_EMAIL].fileType; 1.12347 + let dataWriter = function dataWriter(recordSize) { 1.12348 + let Buf = this.context.Buf; 1.12349 + let GsmPDUHelper = this.context.GsmPDUHelper; 1.12350 + let ICCPDUHelper = this.context.ICCPDUHelper; 1.12351 + 1.12352 + // Write String length 1.12353 + let strLen = recordSize * 2; 1.12354 + Buf.writeInt32(strLen); 1.12355 + 1.12356 + if (fileType == ICC_USIM_TYPE1_TAG) { 1.12357 + ICCPDUHelper.writeStringTo8BitUnpacked(recordSize, email); 1.12358 + } else { 1.12359 + ICCPDUHelper.writeStringTo8BitUnpacked(recordSize - 2, email); 1.12360 + GsmPDUHelper.writeHexOctet(pbr.adn.sfi || 0xff); 1.12361 + GsmPDUHelper.writeHexOctet(adnRecordId); 1.12362 + } 1.12363 + 1.12364 + Buf.writeStringDelimiter(strLen); 1.12365 + }.bind(this); 1.12366 + 1.12367 + this.context.ICCIOHelper.updateLinearFixedEF({ 1.12368 + fileId: fileId, 1.12369 + recordNumber: recordNumber, 1.12370 + dataWriter: dataWriter, 1.12371 + callback: onsuccess, 1.12372 + onerror: onerror 1.12373 + }); 1.12374 + }, 1.12375 + 1.12376 + /** 1.12377 + * Cache EF_ANR record size. 1.12378 + */ 1.12379 + _anrRecordSize: null, 1.12380 + 1.12381 + /** 1.12382 + * Read USIM/RUIM Phonebook EF_ANR. 1.12383 + * 1.12384 + * @see TS 131.102, clause 4.4.2.9 1.12385 + * 1.12386 + * @param fileId EF id of the ANR. 1.12387 + * @param fileType One of the ICC_USIM_TYPE* constants. 1.12388 + * @param recordNumber The number of the record shall be loaded. 1.12389 + * @param onsuccess Callback to be called when success. 1.12390 + * @param onerror Callback to be called when error. 1.12391 + */ 1.12392 + readANR: function(fileId, fileType, recordNumber, onsuccess, onerror) { 1.12393 + function callback(options) { 1.12394 + let Buf = this.context.Buf; 1.12395 + let strLen = Buf.readInt32(); 1.12396 + let number = null; 1.12397 + this._anrRecordSize = options.recordSize; 1.12398 + 1.12399 + // Skip EF_AAS Record ID. 1.12400 + Buf.seekIncoming(1 * Buf.PDU_HEX_OCTET_SIZE); 1.12401 + 1.12402 + number = this.context.ICCPDUHelper.readNumberWithLength(); 1.12403 + 1.12404 + // Skip 2 unused octets, CCP and EXT1. 1.12405 + Buf.seekIncoming(2 * Buf.PDU_HEX_OCTET_SIZE); 1.12406 + 1.12407 + // For Type 2 there are two extra octets. 1.12408 + if (fileType == ICC_USIM_TYPE2_TAG) { 1.12409 + // Skip 2 unused octets, ADN SFI and Record Identifier. 1.12410 + Buf.seekIncoming(2 * Buf.PDU_HEX_OCTET_SIZE); 1.12411 + } 1.12412 + 1.12413 + Buf.readStringDelimiter(strLen); 1.12414 + 1.12415 + if (onsuccess) { 1.12416 + onsuccess(number); 1.12417 + } 1.12418 + } 1.12419 + 1.12420 + this.context.ICCIOHelper.loadLinearFixedEF({ 1.12421 + fileId: fileId, 1.12422 + recordNumber: recordNumber, 1.12423 + recordSize: this._anrRecordSize, 1.12424 + callback: callback.bind(this), 1.12425 + onerror: onerror 1.12426 + }); 1.12427 + }, 1.12428 + 1.12429 + /** 1.12430 + * Update USIM/RUIM Phonebook EF_ANR. 1.12431 + * 1.12432 + * @see TS 131.102, clause 4.4.2.9 1.12433 + * 1.12434 + * @param pbr Phonebook Reference File. 1.12435 + * @param recordNumber The identifier of the record shall be updated. 1.12436 + * @param number The value to be written. 1.12437 + * @param adnRecordId The record Id of ADN, only needed if the fileType of Email is TYPE2. 1.12438 + * @param onsuccess Callback to be called when success. 1.12439 + * @param onerror Callback to be called when error. 1.12440 + */ 1.12441 + updateANR: function(pbr, recordNumber, number, adnRecordId, onsuccess, onerror) { 1.12442 + let fileId = pbr[USIM_PBR_ANR0].fileId; 1.12443 + let fileType = pbr[USIM_PBR_ANR0].fileType; 1.12444 + let dataWriter = function dataWriter(recordSize) { 1.12445 + let Buf = this.context.Buf; 1.12446 + let GsmPDUHelper = this.context.GsmPDUHelper; 1.12447 + 1.12448 + // Write String length 1.12449 + let strLen = recordSize * 2; 1.12450 + Buf.writeInt32(strLen); 1.12451 + 1.12452 + // EF_AAS record Id. Unused for now. 1.12453 + GsmPDUHelper.writeHexOctet(0xff); 1.12454 + 1.12455 + this.context.ICCPDUHelper.writeNumberWithLength(number); 1.12456 + 1.12457 + // Write unused octets 0xff, CCP and EXT1. 1.12458 + GsmPDUHelper.writeHexOctet(0xff); 1.12459 + GsmPDUHelper.writeHexOctet(0xff); 1.12460 + 1.12461 + // For Type 2 there are two extra octets. 1.12462 + if (fileType == ICC_USIM_TYPE2_TAG) { 1.12463 + GsmPDUHelper.writeHexOctet(pbr.adn.sfi || 0xff); 1.12464 + GsmPDUHelper.writeHexOctet(adnRecordId); 1.12465 + } 1.12466 + 1.12467 + Buf.writeStringDelimiter(strLen); 1.12468 + }.bind(this); 1.12469 + 1.12470 + this.context.ICCIOHelper.updateLinearFixedEF({ 1.12471 + fileId: fileId, 1.12472 + recordNumber: recordNumber, 1.12473 + dataWriter: dataWriter, 1.12474 + callback: onsuccess, 1.12475 + onerror: onerror 1.12476 + }); 1.12477 + }, 1.12478 + 1.12479 + /** 1.12480 + * Find free record id. 1.12481 + * 1.12482 + * @param fileId EF id. 1.12483 + * @param onsuccess Callback to be called when success. 1.12484 + * @param onerror Callback to be called when error. 1.12485 + */ 1.12486 + findFreeRecordId: function(fileId, onsuccess, onerror) { 1.12487 + let ICCIOHelper = this.context.ICCIOHelper; 1.12488 + 1.12489 + function callback(options) { 1.12490 + let Buf = this.context.Buf; 1.12491 + let GsmPDUHelper = this.context.GsmPDUHelper; 1.12492 + 1.12493 + let strLen = Buf.readInt32(); 1.12494 + let octetLen = strLen / 2; 1.12495 + let readLen = 0; 1.12496 + 1.12497 + while (readLen < octetLen) { 1.12498 + let octet = GsmPDUHelper.readHexOctet(); 1.12499 + readLen++; 1.12500 + if (octet != 0xff) { 1.12501 + break; 1.12502 + } 1.12503 + } 1.12504 + 1.12505 + if (readLen == octetLen) { 1.12506 + // Find free record. 1.12507 + if (onsuccess) { 1.12508 + onsuccess(options.p1); 1.12509 + } 1.12510 + return; 1.12511 + } else { 1.12512 + Buf.seekIncoming((octetLen - readLen) * Buf.PDU_HEX_OCTET_SIZE); 1.12513 + } 1.12514 + 1.12515 + Buf.readStringDelimiter(strLen); 1.12516 + 1.12517 + if (options.p1 < options.totalRecords) { 1.12518 + ICCIOHelper.loadNextRecord(options); 1.12519 + } else { 1.12520 + // No free record found. 1.12521 + if (DEBUG) { 1.12522 + this.context.debug(CONTACT_ERR_NO_FREE_RECORD_FOUND); 1.12523 + } 1.12524 + onerror(CONTACT_ERR_NO_FREE_RECORD_FOUND); 1.12525 + } 1.12526 + } 1.12527 + 1.12528 + ICCIOHelper.loadLinearFixedEF({fileId: fileId, 1.12529 + callback: callback.bind(this), 1.12530 + onerror: onerror}); 1.12531 + }, 1.12532 +}; 1.12533 + 1.12534 +/** 1.12535 + * Helper for (U)SIM Records. 1.12536 + */ 1.12537 +function SimRecordHelperObject(aContext) { 1.12538 + this.context = aContext; 1.12539 +} 1.12540 +SimRecordHelperObject.prototype = { 1.12541 + context: null, 1.12542 + 1.12543 + /** 1.12544 + * Fetch (U)SIM records. 1.12545 + */ 1.12546 + fetchSimRecords: function() { 1.12547 + this.context.RIL.getIMSI(); 1.12548 + this.readAD(); 1.12549 + this.readSST(); 1.12550 + }, 1.12551 + 1.12552 + /** 1.12553 + * Read EF_phase. 1.12554 + * This EF is only available in SIM. 1.12555 + */ 1.12556 + readSimPhase: function() { 1.12557 + function callback() { 1.12558 + let Buf = this.context.Buf; 1.12559 + let strLen = Buf.readInt32(); 1.12560 + 1.12561 + let GsmPDUHelper = this.context.GsmPDUHelper; 1.12562 + let phase = GsmPDUHelper.readHexOctet(); 1.12563 + // If EF_phase is coded '03' or greater, an ME supporting STK shall 1.12564 + // perform the PROFILE DOWNLOAD procedure. 1.12565 + if (RILQUIRKS_SEND_STK_PROFILE_DOWNLOAD && 1.12566 + phase >= ICC_PHASE_2_PROFILE_DOWNLOAD_REQUIRED) { 1.12567 + this.context.RIL.sendStkTerminalProfile(STK_SUPPORTED_TERMINAL_PROFILE); 1.12568 + } 1.12569 + 1.12570 + Buf.readStringDelimiter(strLen); 1.12571 + } 1.12572 + 1.12573 + this.context.ICCIOHelper.loadTransparentEF({ 1.12574 + fileId: ICC_EF_PHASE, 1.12575 + callback: callback.bind(this) 1.12576 + }); 1.12577 + }, 1.12578 + 1.12579 + /** 1.12580 + * Read the MSISDN from the (U)SIM. 1.12581 + */ 1.12582 + readMSISDN: function() { 1.12583 + function callback(options) { 1.12584 + let RIL = this.context.RIL; 1.12585 + 1.12586 + let contact = 1.12587 + this.context.ICCPDUHelper.readAlphaIdDiallingNumber(options.recordSize); 1.12588 + if (!contact || 1.12589 + (RIL.iccInfo.msisdn !== undefined && 1.12590 + RIL.iccInfo.msisdn === contact.number)) { 1.12591 + return; 1.12592 + } 1.12593 + RIL.iccInfo.msisdn = contact.number; 1.12594 + if (DEBUG) this.context.debug("MSISDN: " + RIL.iccInfo.msisdn); 1.12595 + this.context.ICCUtilsHelper.handleICCInfoChange(); 1.12596 + } 1.12597 + 1.12598 + this.context.ICCIOHelper.loadLinearFixedEF({ 1.12599 + fileId: ICC_EF_MSISDN, 1.12600 + callback: callback.bind(this) 1.12601 + }); 1.12602 + }, 1.12603 + 1.12604 + /** 1.12605 + * Read the AD (Administrative Data) from the (U)SIM. 1.12606 + */ 1.12607 + readAD: function() { 1.12608 + function callback() { 1.12609 + let Buf = this.context.Buf; 1.12610 + let strLen = Buf.readInt32(); 1.12611 + // Each octet is encoded into two chars. 1.12612 + let octetLen = strLen / 2; 1.12613 + let ad = this.context.GsmPDUHelper.readHexOctetArray(octetLen); 1.12614 + Buf.readStringDelimiter(strLen); 1.12615 + 1.12616 + if (DEBUG) { 1.12617 + let str = ""; 1.12618 + for (let i = 0; i < ad.length; i++) { 1.12619 + str += ad[i] + ", "; 1.12620 + } 1.12621 + this.context.debug("AD: " + str); 1.12622 + } 1.12623 + 1.12624 + let ICCUtilsHelper = this.context.ICCUtilsHelper; 1.12625 + let RIL = this.context.RIL; 1.12626 + // The 4th byte of the response is the length of MNC. 1.12627 + let mccMnc = ICCUtilsHelper.parseMccMncFromImsi(RIL.iccInfoPrivate.imsi, 1.12628 + ad && ad[3]); 1.12629 + if (mccMnc) { 1.12630 + RIL.iccInfo.mcc = mccMnc.mcc; 1.12631 + RIL.iccInfo.mnc = mccMnc.mnc; 1.12632 + ICCUtilsHelper.handleICCInfoChange(); 1.12633 + } 1.12634 + } 1.12635 + 1.12636 + this.context.ICCIOHelper.loadTransparentEF({ 1.12637 + fileId: ICC_EF_AD, 1.12638 + callback: callback.bind(this) 1.12639 + }); 1.12640 + }, 1.12641 + 1.12642 + /** 1.12643 + * Read the SPN (Service Provider Name) from the (U)SIM. 1.12644 + */ 1.12645 + readSPN: function() { 1.12646 + function callback() { 1.12647 + let Buf = this.context.Buf; 1.12648 + let strLen = Buf.readInt32(); 1.12649 + // Each octet is encoded into two chars. 1.12650 + let octetLen = strLen / 2; 1.12651 + let spnDisplayCondition = this.context.GsmPDUHelper.readHexOctet(); 1.12652 + // Minus 1 because the first octet is used to store display condition. 1.12653 + let spn = this.context.ICCPDUHelper.readAlphaIdentifier(octetLen - 1); 1.12654 + Buf.readStringDelimiter(strLen); 1.12655 + 1.12656 + if (DEBUG) { 1.12657 + this.context.debug("SPN: spn = " + spn + 1.12658 + ", spnDisplayCondition = " + spnDisplayCondition); 1.12659 + } 1.12660 + 1.12661 + let RIL = this.context.RIL; 1.12662 + RIL.iccInfoPrivate.spnDisplayCondition = spnDisplayCondition; 1.12663 + RIL.iccInfo.spn = spn; 1.12664 + let ICCUtilsHelper = this.context.ICCUtilsHelper; 1.12665 + ICCUtilsHelper.updateDisplayCondition(); 1.12666 + ICCUtilsHelper.handleICCInfoChange(); 1.12667 + } 1.12668 + 1.12669 + this.context.ICCIOHelper.loadTransparentEF({ 1.12670 + fileId: ICC_EF_SPN, 1.12671 + callback: callback.bind(this) 1.12672 + }); 1.12673 + }, 1.12674 + 1.12675 + /** 1.12676 + * Read the (U)SIM Service Table from the (U)SIM. 1.12677 + */ 1.12678 + readSST: function() { 1.12679 + function callback() { 1.12680 + let Buf = this.context.Buf; 1.12681 + let RIL = this.context.RIL; 1.12682 + 1.12683 + let strLen = Buf.readInt32(); 1.12684 + // Each octet is encoded into two chars. 1.12685 + let octetLen = strLen / 2; 1.12686 + let sst = this.context.GsmPDUHelper.readHexOctetArray(octetLen); 1.12687 + Buf.readStringDelimiter(strLen); 1.12688 + RIL.iccInfoPrivate.sst = sst; 1.12689 + if (DEBUG) { 1.12690 + let str = ""; 1.12691 + for (let i = 0; i < sst.length; i++) { 1.12692 + str += sst[i] + ", "; 1.12693 + } 1.12694 + this.context.debug("SST: " + str); 1.12695 + } 1.12696 + 1.12697 + let ICCUtilsHelper = this.context.ICCUtilsHelper; 1.12698 + if (ICCUtilsHelper.isICCServiceAvailable("MSISDN")) { 1.12699 + if (DEBUG) this.context.debug("MSISDN: MSISDN is available"); 1.12700 + this.readMSISDN(); 1.12701 + } else { 1.12702 + if (DEBUG) this.context.debug("MSISDN: MSISDN service is not available"); 1.12703 + } 1.12704 + 1.12705 + // Fetch SPN and PLMN list, if some of them are available. 1.12706 + if (ICCUtilsHelper.isICCServiceAvailable("SPN")) { 1.12707 + if (DEBUG) this.context.debug("SPN: SPN is available"); 1.12708 + this.readSPN(); 1.12709 + } else { 1.12710 + if (DEBUG) this.context.debug("SPN: SPN service is not available"); 1.12711 + } 1.12712 + 1.12713 + if (ICCUtilsHelper.isICCServiceAvailable("MDN")) { 1.12714 + if (DEBUG) this.context.debug("MDN: MDN available."); 1.12715 + this.readMBDN(); 1.12716 + } else { 1.12717 + if (DEBUG) this.context.debug("MDN: MDN service is not available"); 1.12718 + } 1.12719 + 1.12720 + if (ICCUtilsHelper.isICCServiceAvailable("MWIS")) { 1.12721 + if (DEBUG) this.context.debug("MWIS: MWIS is available"); 1.12722 + this.readMWIS(); 1.12723 + } else { 1.12724 + if (DEBUG) this.context.debug("MWIS: MWIS is not available"); 1.12725 + } 1.12726 + 1.12727 + if (ICCUtilsHelper.isICCServiceAvailable("SPDI")) { 1.12728 + if (DEBUG) this.context.debug("SPDI: SPDI available."); 1.12729 + this.readSPDI(); 1.12730 + } else { 1.12731 + if (DEBUG) this.context.debug("SPDI: SPDI service is not available"); 1.12732 + } 1.12733 + 1.12734 + if (ICCUtilsHelper.isICCServiceAvailable("PNN")) { 1.12735 + if (DEBUG) this.context.debug("PNN: PNN is available"); 1.12736 + this.readPNN(); 1.12737 + } else { 1.12738 + if (DEBUG) this.context.debug("PNN: PNN is not available"); 1.12739 + } 1.12740 + 1.12741 + if (ICCUtilsHelper.isICCServiceAvailable("OPL")) { 1.12742 + if (DEBUG) this.context.debug("OPL: OPL is available"); 1.12743 + this.readOPL(); 1.12744 + } else { 1.12745 + if (DEBUG) this.context.debug("OPL: OPL is not available"); 1.12746 + } 1.12747 + 1.12748 + if (ICCUtilsHelper.isICCServiceAvailable("CBMI")) { 1.12749 + this.readCBMI(); 1.12750 + } else { 1.12751 + RIL.cellBroadcastConfigs.CBMI = null; 1.12752 + } 1.12753 + if (ICCUtilsHelper.isICCServiceAvailable("DATA_DOWNLOAD_SMS_CB")) { 1.12754 + this.readCBMID(); 1.12755 + } else { 1.12756 + RIL.cellBroadcastConfigs.CBMID = null; 1.12757 + } 1.12758 + if (ICCUtilsHelper.isICCServiceAvailable("CBMIR")) { 1.12759 + this.readCBMIR(); 1.12760 + } else { 1.12761 + RIL.cellBroadcastConfigs.CBMIR = null; 1.12762 + } 1.12763 + RIL._mergeAllCellBroadcastConfigs(); 1.12764 + } 1.12765 + 1.12766 + // ICC_EF_UST has the same value with ICC_EF_SST. 1.12767 + this.context.ICCIOHelper.loadTransparentEF({ 1.12768 + fileId: ICC_EF_SST, 1.12769 + callback: callback.bind(this) 1.12770 + }); 1.12771 + }, 1.12772 + 1.12773 + /** 1.12774 + * Read (U)SIM MBDN. (Mailbox Dialling Number) 1.12775 + * 1.12776 + * @see TS 131.102, clause 4.2.60 1.12777 + */ 1.12778 + readMBDN: function() { 1.12779 + function callback(options) { 1.12780 + let RIL = this.context.RIL; 1.12781 + let contact = 1.12782 + this.context.ICCPDUHelper.readAlphaIdDiallingNumber(options.recordSize); 1.12783 + if (!contact || 1.12784 + (RIL.iccInfoPrivate.mbdn !== undefined && 1.12785 + RIL.iccInfoPrivate.mbdn === contact.number)) { 1.12786 + return; 1.12787 + } 1.12788 + RIL.iccInfoPrivate.mbdn = contact.number; 1.12789 + if (DEBUG) { 1.12790 + this.context.debug("MBDN, alphaId=" + contact.alphaId + 1.12791 + " number=" + contact.number); 1.12792 + } 1.12793 + contact.rilMessageType = "iccmbdn"; 1.12794 + RIL.sendChromeMessage(contact); 1.12795 + } 1.12796 + 1.12797 + this.context.ICCIOHelper.loadLinearFixedEF({ 1.12798 + fileId: ICC_EF_MBDN, 1.12799 + callback: callback.bind(this) 1.12800 + }); 1.12801 + }, 1.12802 + 1.12803 + /** 1.12804 + * Read ICC MWIS. (Message Waiting Indication Status) 1.12805 + * 1.12806 + * @see TS 31.102, clause 4.2.63 for USIM and TS 51.011, clause 10.3.45 for SIM. 1.12807 + */ 1.12808 + readMWIS: function() { 1.12809 + function callback(options) { 1.12810 + let Buf = this.context.Buf; 1.12811 + let RIL = this.context.RIL; 1.12812 + 1.12813 + let strLen = Buf.readInt32(); 1.12814 + // Each octet is encoded into two chars. 1.12815 + let octetLen = strLen / 2; 1.12816 + let mwis = this.context.GsmPDUHelper.readHexOctetArray(octetLen); 1.12817 + Buf.readStringDelimiter(strLen); 1.12818 + if (!mwis) { 1.12819 + return; 1.12820 + } 1.12821 + RIL.iccInfoPrivate.mwis = mwis; //Keep raw MWIS for updateMWIS() 1.12822 + 1.12823 + let mwi = {}; 1.12824 + // b8 b7 B6 b5 b4 b3 b2 b1 4.2.63, TS 31.102 version 11.6.0 1.12825 + // | | | | | | | |__ Voicemail 1.12826 + // | | | | | | |_____ Fax 1.12827 + // | | | | | |________ Electronic Mail 1.12828 + // | | | | |___________ Other 1.12829 + // | | | |______________ Videomail 1.12830 + // |__|__|_________________ RFU 1.12831 + mwi.active = ((mwis[0] & 0x01) != 0); 1.12832 + 1.12833 + if (mwi.active) { 1.12834 + // In TS 23.040 msgCount is in the range from 0 to 255. 1.12835 + // The value 255 shall be taken to mean 255 or greater. 1.12836 + // 1.12837 + // However, There is no definition about 0 when MWI is active. 1.12838 + // 1.12839 + // Normally, when mwi is active, the msgCount must be larger than 0. 1.12840 + // Refer to other reference phone, 1.12841 + // 0 is usually treated as UNKNOWN for storing 2nd level MWI status (DCS). 1.12842 + mwi.msgCount = (mwis[1] === 0) ? GECKO_VOICEMAIL_MESSAGE_COUNT_UNKNOWN 1.12843 + : mwis[1]; 1.12844 + } else { 1.12845 + mwi.msgCount = 0; 1.12846 + } 1.12847 + 1.12848 + RIL.sendChromeMessage({ rilMessageType: "iccmwis", 1.12849 + mwi: mwi }); 1.12850 + } 1.12851 + 1.12852 + this.context.ICCIOHelper.loadLinearFixedEF({ 1.12853 + fileId: ICC_EF_MWIS, 1.12854 + recordNumber: 1, // Get 1st Subscriber Profile. 1.12855 + callback: callback.bind(this) 1.12856 + }); 1.12857 + }, 1.12858 + 1.12859 + /** 1.12860 + * Update ICC MWIS. (Message Waiting Indication Status) 1.12861 + * 1.12862 + * @see TS 31.102, clause 4.2.63 for USIM and TS 51.011, clause 10.3.45 for SIM. 1.12863 + */ 1.12864 + updateMWIS: function(mwi) { 1.12865 + let RIL = this.context.RIL; 1.12866 + if (!RIL.iccInfoPrivate.mwis) { 1.12867 + return; 1.12868 + } 1.12869 + 1.12870 + function dataWriter(recordSize) { 1.12871 + let mwis = RIL.iccInfoPrivate.mwis; 1.12872 + 1.12873 + let msgCount = 1.12874 + (mwi.msgCount === GECKO_VOICEMAIL_MESSAGE_COUNT_UNKNOWN) ? 0 : mwi.msgCount; 1.12875 + 1.12876 + [mwis[0], mwis[1]] = (mwi.active) ? [(mwis[0] | 0x01), msgCount] 1.12877 + : [(mwis[0] & 0xFE), 0]; 1.12878 + 1.12879 + let strLen = recordSize * 2; 1.12880 + let Buf = this.context.Buf; 1.12881 + Buf.writeInt32(strLen); 1.12882 + 1.12883 + let GsmPDUHelper = this.context.GsmPDUHelper; 1.12884 + for (let i = 0; i < mwis.length; i++) { 1.12885 + GsmPDUHelper.writeHexOctet(mwis[i]); 1.12886 + } 1.12887 + 1.12888 + Buf.writeStringDelimiter(strLen); 1.12889 + } 1.12890 + 1.12891 + this.context.ICCIOHelper.updateLinearFixedEF({ 1.12892 + fileId: ICC_EF_MWIS, 1.12893 + recordNumber: 1, // Update 1st Subscriber Profile. 1.12894 + dataWriter: dataWriter.bind(this) 1.12895 + }); 1.12896 + }, 1.12897 + 1.12898 + /** 1.12899 + * Read the SPDI (Service Provider Display Information) from the (U)SIM. 1.12900 + * 1.12901 + * See TS 131.102 section 4.2.66 for USIM and TS 51.011 section 10.3.50 1.12902 + * for SIM. 1.12903 + */ 1.12904 + readSPDI: function() { 1.12905 + function callback() { 1.12906 + let Buf = this.context.Buf; 1.12907 + let strLen = Buf.readInt32(); 1.12908 + let octetLen = strLen / 2; 1.12909 + let readLen = 0; 1.12910 + let endLoop = false; 1.12911 + 1.12912 + let RIL = this.context.RIL; 1.12913 + RIL.iccInfoPrivate.SPDI = null; 1.12914 + 1.12915 + let GsmPDUHelper = this.context.GsmPDUHelper; 1.12916 + while ((readLen < octetLen) && !endLoop) { 1.12917 + let tlvTag = GsmPDUHelper.readHexOctet(); 1.12918 + let tlvLen = GsmPDUHelper.readHexOctet(); 1.12919 + readLen += 2; // For tag and length fields. 1.12920 + switch (tlvTag) { 1.12921 + case SPDI_TAG_SPDI: 1.12922 + // The value part itself is a TLV. 1.12923 + continue; 1.12924 + case SPDI_TAG_PLMN_LIST: 1.12925 + // This PLMN list is what we want. 1.12926 + RIL.iccInfoPrivate.SPDI = this.readPLMNEntries(tlvLen / 3); 1.12927 + readLen += tlvLen; 1.12928 + endLoop = true; 1.12929 + break; 1.12930 + default: 1.12931 + // We don't care about its content if its tag is not SPDI nor 1.12932 + // PLMN_LIST. 1.12933 + endLoop = true; 1.12934 + break; 1.12935 + } 1.12936 + } 1.12937 + 1.12938 + // Consume unread octets. 1.12939 + Buf.seekIncoming((octetLen - readLen) * Buf.PDU_HEX_OCTET_SIZE); 1.12940 + Buf.readStringDelimiter(strLen); 1.12941 + 1.12942 + if (DEBUG) { 1.12943 + this.context.debug("SPDI: " + JSON.stringify(RIL.iccInfoPrivate.SPDI)); 1.12944 + } 1.12945 + let ICCUtilsHelper = this.context.ICCUtilsHelper; 1.12946 + if (ICCUtilsHelper.updateDisplayCondition()) { 1.12947 + ICCUtilsHelper.handleICCInfoChange(); 1.12948 + } 1.12949 + } 1.12950 + 1.12951 + // PLMN List is Servive 51 in USIM, EF_SPDI 1.12952 + this.context.ICCIOHelper.loadTransparentEF({ 1.12953 + fileId: ICC_EF_SPDI, 1.12954 + callback: callback.bind(this) 1.12955 + }); 1.12956 + }, 1.12957 + 1.12958 + _readCbmiHelper: function(which) { 1.12959 + let RIL = this.context.RIL; 1.12960 + 1.12961 + function callback() { 1.12962 + let Buf = this.context.Buf; 1.12963 + let strLength = Buf.readInt32(); 1.12964 + 1.12965 + // Each Message Identifier takes two octets and each octet is encoded 1.12966 + // into two chars. 1.12967 + let numIds = strLength / 4, list = null; 1.12968 + if (numIds) { 1.12969 + list = []; 1.12970 + let GsmPDUHelper = this.context.GsmPDUHelper; 1.12971 + for (let i = 0, id; i < numIds; i++) { 1.12972 + id = GsmPDUHelper.readHexOctet() << 8 | GsmPDUHelper.readHexOctet(); 1.12973 + // `Unused entries shall be set to 'FF FF'.` 1.12974 + if (id != 0xFFFF) { 1.12975 + list.push(id); 1.12976 + list.push(id + 1); 1.12977 + } 1.12978 + } 1.12979 + } 1.12980 + if (DEBUG) { 1.12981 + this.context.debug(which + ": " + JSON.stringify(list)); 1.12982 + } 1.12983 + 1.12984 + Buf.readStringDelimiter(strLength); 1.12985 + 1.12986 + RIL.cellBroadcastConfigs[which] = list; 1.12987 + RIL._mergeAllCellBroadcastConfigs(); 1.12988 + } 1.12989 + 1.12990 + function onerror() { 1.12991 + RIL.cellBroadcastConfigs[which] = null; 1.12992 + RIL._mergeAllCellBroadcastConfigs(); 1.12993 + } 1.12994 + 1.12995 + let fileId = GLOBAL["ICC_EF_" + which]; 1.12996 + this.context.ICCIOHelper.loadTransparentEF({ 1.12997 + fileId: fileId, 1.12998 + callback: callback.bind(this), 1.12999 + onerror: onerror.bind(this) 1.13000 + }); 1.13001 + }, 1.13002 + 1.13003 + /** 1.13004 + * Read EFcbmi (Cell Broadcast Message Identifier selection) 1.13005 + * 1.13006 + * @see 3GPP TS 31.102 v110.02.0 section 4.2.14 EFcbmi 1.13007 + * @see 3GPP TS 51.011 v5.0.0 section 10.3.13 EFcbmi 1.13008 + */ 1.13009 + readCBMI: function() { 1.13010 + this._readCbmiHelper("CBMI"); 1.13011 + }, 1.13012 + 1.13013 + /** 1.13014 + * Read EFcbmid (Cell Broadcast Message Identifier for Data Download) 1.13015 + * 1.13016 + * @see 3GPP TS 31.102 v110.02.0 section 4.2.20 EFcbmid 1.13017 + * @see 3GPP TS 51.011 v5.0.0 section 10.3.26 EFcbmid 1.13018 + */ 1.13019 + readCBMID: function() { 1.13020 + this._readCbmiHelper("CBMID"); 1.13021 + }, 1.13022 + 1.13023 + /** 1.13024 + * Read EFcbmir (Cell Broadcast Message Identifier Range selection) 1.13025 + * 1.13026 + * @see 3GPP TS 31.102 v110.02.0 section 4.2.22 EFcbmir 1.13027 + * @see 3GPP TS 51.011 v5.0.0 section 10.3.28 EFcbmir 1.13028 + */ 1.13029 + readCBMIR: function() { 1.13030 + let RIL = this.context.RIL; 1.13031 + 1.13032 + function callback() { 1.13033 + let Buf = this.context.Buf; 1.13034 + let strLength = Buf.readInt32(); 1.13035 + 1.13036 + // Each Message Identifier range takes four octets and each octet is 1.13037 + // encoded into two chars. 1.13038 + let numIds = strLength / 8, list = null; 1.13039 + if (numIds) { 1.13040 + list = []; 1.13041 + let GsmPDUHelper = this.context.GsmPDUHelper; 1.13042 + for (let i = 0, from, to; i < numIds; i++) { 1.13043 + // `Bytes one and two of each range identifier equal the lower value 1.13044 + // of a cell broadcast range, bytes three and four equal the upper 1.13045 + // value of a cell broadcast range.` 1.13046 + from = GsmPDUHelper.readHexOctet() << 8 | GsmPDUHelper.readHexOctet(); 1.13047 + to = GsmPDUHelper.readHexOctet() << 8 | GsmPDUHelper.readHexOctet(); 1.13048 + // `Unused entries shall be set to 'FF FF'.` 1.13049 + if ((from != 0xFFFF) && (to != 0xFFFF)) { 1.13050 + list.push(from); 1.13051 + list.push(to + 1); 1.13052 + } 1.13053 + } 1.13054 + } 1.13055 + if (DEBUG) { 1.13056 + this.context.debug("CBMIR: " + JSON.stringify(list)); 1.13057 + } 1.13058 + 1.13059 + Buf.readStringDelimiter(strLength); 1.13060 + 1.13061 + RIL.cellBroadcastConfigs.CBMIR = list; 1.13062 + RIL._mergeAllCellBroadcastConfigs(); 1.13063 + } 1.13064 + 1.13065 + function onerror() { 1.13066 + RIL.cellBroadcastConfigs.CBMIR = null; 1.13067 + RIL._mergeAllCellBroadcastConfigs(); 1.13068 + } 1.13069 + 1.13070 + this.context.ICCIOHelper.loadTransparentEF({ 1.13071 + fileId: ICC_EF_CBMIR, 1.13072 + callback: callback.bind(this), 1.13073 + onerror: onerror.bind(this) 1.13074 + }); 1.13075 + }, 1.13076 + 1.13077 + /** 1.13078 + * Read OPL (Operator PLMN List) from (U)SIM. 1.13079 + * 1.13080 + * See 3GPP TS 31.102 Sec. 4.2.59 for USIM 1.13081 + * 3GPP TS 51.011 Sec. 10.3.42 for SIM. 1.13082 + */ 1.13083 + readOPL: function() { 1.13084 + let ICCIOHelper = this.context.ICCIOHelper; 1.13085 + let opl = []; 1.13086 + function callback(options) { 1.13087 + let Buf = this.context.Buf; 1.13088 + let GsmPDUHelper = this.context.GsmPDUHelper; 1.13089 + 1.13090 + let strLen = Buf.readInt32(); 1.13091 + // The first 7 bytes are LAI (for UMTS) and the format of LAI is defined 1.13092 + // in 3GPP TS 23.003, Sec 4.1 1.13093 + // +-------------+---------+ 1.13094 + // | Octet 1 - 3 | MCC/MNC | 1.13095 + // +-------------+---------+ 1.13096 + // | Octet 4 - 7 | LAC | 1.13097 + // +-------------+---------+ 1.13098 + let mccMnc = [GsmPDUHelper.readHexOctet(), 1.13099 + GsmPDUHelper.readHexOctet(), 1.13100 + GsmPDUHelper.readHexOctet()]; 1.13101 + if (mccMnc[0] != 0xFF || mccMnc[1] != 0xFF || mccMnc[2] != 0xFF) { 1.13102 + let oplElement = {}; 1.13103 + let semiOctets = []; 1.13104 + for (let i = 0; i < mccMnc.length; i++) { 1.13105 + semiOctets.push((mccMnc[i] & 0xf0) >> 4); 1.13106 + semiOctets.push(mccMnc[i] & 0x0f); 1.13107 + } 1.13108 + let reformat = [semiOctets[1], semiOctets[0], semiOctets[3], 1.13109 + semiOctets[5], semiOctets[4], semiOctets[2]]; 1.13110 + let buf = ""; 1.13111 + for (let i = 0; i < reformat.length; i++) { 1.13112 + if (reformat[i] != 0xF) { 1.13113 + buf += GsmPDUHelper.semiOctetToBcdChar(reformat[i]); 1.13114 + } 1.13115 + if (i === 2) { 1.13116 + // 0-2: MCC 1.13117 + oplElement.mcc = buf; 1.13118 + buf = ""; 1.13119 + } else if (i === 5) { 1.13120 + // 3-5: MNC 1.13121 + oplElement.mnc = buf; 1.13122 + } 1.13123 + } 1.13124 + // LAC/TAC 1.13125 + oplElement.lacTacStart = 1.13126 + (GsmPDUHelper.readHexOctet() << 8) | GsmPDUHelper.readHexOctet(); 1.13127 + oplElement.lacTacEnd = 1.13128 + (GsmPDUHelper.readHexOctet() << 8) | GsmPDUHelper.readHexOctet(); 1.13129 + // PLMN Network Name Record Identifier 1.13130 + oplElement.pnnRecordId = GsmPDUHelper.readHexOctet(); 1.13131 + if (DEBUG) { 1.13132 + this.context.debug("OPL: [" + (opl.length + 1) + "]: " + 1.13133 + JSON.stringify(oplElement)); 1.13134 + } 1.13135 + opl.push(oplElement); 1.13136 + } else { 1.13137 + Buf.seekIncoming(5 * Buf.PDU_HEX_OCTET_SIZE); 1.13138 + } 1.13139 + Buf.readStringDelimiter(strLen); 1.13140 + 1.13141 + if (options.p1 < options.totalRecords) { 1.13142 + ICCIOHelper.loadNextRecord(options); 1.13143 + } else { 1.13144 + this.context.RIL.iccInfoPrivate.OPL = opl; 1.13145 + } 1.13146 + } 1.13147 + 1.13148 + ICCIOHelper.loadLinearFixedEF({fileId: ICC_EF_OPL, 1.13149 + callback: callback.bind(this)}); 1.13150 + }, 1.13151 + 1.13152 + /** 1.13153 + * Read PNN (PLMN Network Name) from (U)SIM. 1.13154 + * 1.13155 + * See 3GPP TS 31.102 Sec. 4.2.58 for USIM 1.13156 + * 3GPP TS 51.011 Sec. 10.3.41 for SIM. 1.13157 + */ 1.13158 + readPNN: function() { 1.13159 + let ICCIOHelper = this.context.ICCIOHelper; 1.13160 + function callback(options) { 1.13161 + let pnnElement; 1.13162 + let Buf = this.context.Buf; 1.13163 + let strLen = Buf.readInt32(); 1.13164 + let octetLen = strLen / 2; 1.13165 + let readLen = 0; 1.13166 + 1.13167 + let GsmPDUHelper = this.context.GsmPDUHelper; 1.13168 + while (readLen < octetLen) { 1.13169 + let tlvTag = GsmPDUHelper.readHexOctet(); 1.13170 + 1.13171 + if (tlvTag == 0xFF) { 1.13172 + // Unused byte 1.13173 + readLen++; 1.13174 + Buf.seekIncoming((octetLen - readLen) * Buf.PDU_HEX_OCTET_SIZE); 1.13175 + break; 1.13176 + } 1.13177 + 1.13178 + // Needs this check to avoid initializing twice. 1.13179 + pnnElement = pnnElement || {}; 1.13180 + 1.13181 + let tlvLen = GsmPDUHelper.readHexOctet(); 1.13182 + 1.13183 + switch (tlvTag) { 1.13184 + case PNN_IEI_FULL_NETWORK_NAME: 1.13185 + pnnElement.fullName = GsmPDUHelper.readNetworkName(tlvLen); 1.13186 + break; 1.13187 + case PNN_IEI_SHORT_NETWORK_NAME: 1.13188 + pnnElement.shortName = GsmPDUHelper.readNetworkName(tlvLen); 1.13189 + break; 1.13190 + default: 1.13191 + Buf.seekIncoming(tlvLen * Buf.PDU_HEX_OCTET_SIZE); 1.13192 + break; 1.13193 + } 1.13194 + 1.13195 + readLen += (tlvLen + 2); // +2 for tlvTag and tlvLen 1.13196 + } 1.13197 + Buf.readStringDelimiter(strLen); 1.13198 + 1.13199 + if (pnnElement) { 1.13200 + pnn.push(pnnElement); 1.13201 + } 1.13202 + 1.13203 + // Will ignore remaining records when got the contents of a record are all 0xff. 1.13204 + if (pnnElement && options.p1 < options.totalRecords) { 1.13205 + ICCIOHelper.loadNextRecord(options); 1.13206 + } else { 1.13207 + if (DEBUG) { 1.13208 + for (let i = 0; i < pnn.length; i++) { 1.13209 + this.context.debug("PNN: [" + i + "]: " + JSON.stringify(pnn[i])); 1.13210 + } 1.13211 + } 1.13212 + this.context.RIL.iccInfoPrivate.PNN = pnn; 1.13213 + } 1.13214 + } 1.13215 + 1.13216 + let pnn = []; 1.13217 + ICCIOHelper.loadLinearFixedEF({fileId: ICC_EF_PNN, 1.13218 + callback: callback.bind(this)}); 1.13219 + }, 1.13220 + 1.13221 + /** 1.13222 + * Read the list of PLMN (Public Land Mobile Network) entries 1.13223 + * We cannot directly rely on readSwappedNibbleBcdToString(), 1.13224 + * since it will no correctly handle some corner-cases that are 1.13225 + * not a problem in our case (0xFF 0xFF 0xFF). 1.13226 + * 1.13227 + * @param length The number of PLMN records. 1.13228 + * @return An array of string corresponding to the PLMNs. 1.13229 + */ 1.13230 + readPLMNEntries: function(length) { 1.13231 + let plmnList = []; 1.13232 + // Each PLMN entry has 3 bytes. 1.13233 + if (DEBUG) { 1.13234 + this.context.debug("PLMN entries length = " + length); 1.13235 + } 1.13236 + let GsmPDUHelper = this.context.GsmPDUHelper; 1.13237 + let index = 0; 1.13238 + while (index < length) { 1.13239 + // Unused entries will be 0xFFFFFF, according to EF_SPDI 1.13240 + // specs (TS 131 102, section 4.2.66) 1.13241 + try { 1.13242 + let plmn = [GsmPDUHelper.readHexOctet(), 1.13243 + GsmPDUHelper.readHexOctet(), 1.13244 + GsmPDUHelper.readHexOctet()]; 1.13245 + if (DEBUG) { 1.13246 + this.context.debug("Reading PLMN entry: [" + index + "]: '" + plmn + "'"); 1.13247 + } 1.13248 + if (plmn[0] != 0xFF && 1.13249 + plmn[1] != 0xFF && 1.13250 + plmn[2] != 0xFF) { 1.13251 + let semiOctets = []; 1.13252 + for (let idx = 0; idx < plmn.length; idx++) { 1.13253 + semiOctets.push((plmn[idx] & 0xF0) >> 4); 1.13254 + semiOctets.push(plmn[idx] & 0x0F); 1.13255 + } 1.13256 + 1.13257 + // According to TS 24.301, 9.9.3.12, the semi octets is arranged 1.13258 + // in format: 1.13259 + // Byte 1: MCC[2] | MCC[1] 1.13260 + // Byte 2: MNC[3] | MCC[3] 1.13261 + // Byte 3: MNC[2] | MNC[1] 1.13262 + // Therefore, we need to rearrange them. 1.13263 + let reformat = [semiOctets[1], semiOctets[0], semiOctets[3], 1.13264 + semiOctets[5], semiOctets[4], semiOctets[2]]; 1.13265 + let buf = ""; 1.13266 + let plmnEntry = {}; 1.13267 + for (let i = 0; i < reformat.length; i++) { 1.13268 + if (reformat[i] != 0xF) { 1.13269 + buf += GsmPDUHelper.semiOctetToBcdChar(reformat[i]); 1.13270 + } 1.13271 + if (i === 2) { 1.13272 + // 0-2: MCC 1.13273 + plmnEntry.mcc = buf; 1.13274 + buf = ""; 1.13275 + } else if (i === 5) { 1.13276 + // 3-5: MNC 1.13277 + plmnEntry.mnc = buf; 1.13278 + } 1.13279 + } 1.13280 + if (DEBUG) { 1.13281 + this.context.debug("PLMN = " + plmnEntry.mcc + ", " + plmnEntry.mnc); 1.13282 + } 1.13283 + plmnList.push(plmnEntry); 1.13284 + } 1.13285 + } catch (e) { 1.13286 + if (DEBUG) { 1.13287 + this.context.debug("PLMN entry " + index + " is invalid."); 1.13288 + } 1.13289 + break; 1.13290 + } 1.13291 + index ++; 1.13292 + } 1.13293 + return plmnList; 1.13294 + }, 1.13295 + 1.13296 + /** 1.13297 + * Read the SMS from the ICC. 1.13298 + * 1.13299 + * @param recordNumber The number of the record shall be loaded. 1.13300 + * @param onsuccess Callback to be called when success. 1.13301 + * @param onerror Callback to be called when error. 1.13302 + */ 1.13303 + readSMS: function(recordNumber, onsuccess, onerror) { 1.13304 + function callback(options) { 1.13305 + let Buf = this.context.Buf; 1.13306 + let strLen = Buf.readInt32(); 1.13307 + 1.13308 + // TS 51.011, 10.5.3 EF_SMS 1.13309 + // b3 b2 b1 1.13310 + // 0 0 1 message received by MS from network; message read 1.13311 + // 0 1 1 message received by MS from network; message to be read 1.13312 + // 1 1 1 MS originating message; message to be sent 1.13313 + // 1 0 1 MS originating message; message sent to the network: 1.13314 + let GsmPDUHelper = this.context.GsmPDUHelper; 1.13315 + let status = GsmPDUHelper.readHexOctet(); 1.13316 + 1.13317 + let message = GsmPDUHelper.readMessage(); 1.13318 + message.simStatus = status; 1.13319 + 1.13320 + // Consumes the remaining buffer 1.13321 + Buf.seekIncoming(Buf.getReadAvailable() - Buf.PDU_HEX_OCTET_SIZE); 1.13322 + 1.13323 + Buf.readStringDelimiter(strLen); 1.13324 + 1.13325 + if (message) { 1.13326 + onsuccess(message); 1.13327 + } else { 1.13328 + onerror("Failed to decode SMS on SIM #" + recordNumber); 1.13329 + } 1.13330 + } 1.13331 + 1.13332 + this.context.ICCIOHelper.loadLinearFixedEF({ 1.13333 + fileId: ICC_EF_SMS, 1.13334 + recordNumber: recordNumber, 1.13335 + callback: callback.bind(this), 1.13336 + onerror: onerror 1.13337 + }); 1.13338 + }, 1.13339 +}; 1.13340 + 1.13341 +function RuimRecordHelperObject(aContext) { 1.13342 + this.context = aContext; 1.13343 +} 1.13344 +RuimRecordHelperObject.prototype = { 1.13345 + context: null, 1.13346 + 1.13347 + fetchRuimRecords: function() { 1.13348 + this.getIMSI_M(); 1.13349 + this.readCST(); 1.13350 + this.readCDMAHome(); 1.13351 + this.context.RIL.getCdmaSubscription(); 1.13352 + }, 1.13353 + 1.13354 + /** 1.13355 + * Get IMSI_M from CSIM/RUIM. 1.13356 + * See 3GPP2 C.S0065 Sec. 5.2.2 1.13357 + */ 1.13358 + getIMSI_M: function() { 1.13359 + function callback() { 1.13360 + let Buf = this.context.Buf; 1.13361 + let strLen = Buf.readInt32(); 1.13362 + let encodedImsi = this.context.GsmPDUHelper.readHexOctetArray(strLen / 2); 1.13363 + Buf.readStringDelimiter(strLen); 1.13364 + 1.13365 + if ((encodedImsi[CSIM_IMSI_M_PROGRAMMED_BYTE] & 0x80)) { // IMSI_M programmed 1.13366 + let RIL = this.context.RIL; 1.13367 + RIL.iccInfoPrivate.imsi = this.decodeIMSI(encodedImsi); 1.13368 + RIL.sendChromeMessage({rilMessageType: "iccimsi", 1.13369 + imsi: RIL.iccInfoPrivate.imsi}); 1.13370 + 1.13371 + let ICCUtilsHelper = this.context.ICCUtilsHelper; 1.13372 + let mccMnc = ICCUtilsHelper.parseMccMncFromImsi(RIL.iccInfoPrivate.imsi); 1.13373 + if (mccMnc) { 1.13374 + RIL.iccInfo.mcc = mccMnc.mcc; 1.13375 + RIL.iccInfo.mnc = mccMnc.mnc; 1.13376 + ICCUtilsHelper.handleICCInfoChange(); 1.13377 + } 1.13378 + } 1.13379 + } 1.13380 + 1.13381 + this.context.ICCIOHelper.loadTransparentEF({ 1.13382 + fileId: ICC_EF_CSIM_IMSI_M, 1.13383 + callback: callback.bind(this) 1.13384 + }); 1.13385 + }, 1.13386 + 1.13387 + /** 1.13388 + * Decode IMSI from IMSI_M 1.13389 + * See 3GPP2 C.S0005 Sec. 2.3.1 1.13390 + * +---+---------+------------+---+--------+---------+---+---------+--------+ 1.13391 + * |RFU| MCC | programmed |RFU| MNC | MIN1 |RFU| MIN2 | CLASS | 1.13392 + * +---+---------+------------+---+--------+---------+---+---------+--------+ 1.13393 + * | 6 | 10 bits | 8 bits | 1 | 7 bits | 24 bits | 6 | 10 bits | 8 bits | 1.13394 + * +---+---------+------------+---+--------+---------+---+---------+--------+ 1.13395 + */ 1.13396 + decodeIMSI: function(encodedImsi) { 1.13397 + // MCC: 10 bits, 3 digits 1.13398 + let encodedMCC = ((encodedImsi[CSIM_IMSI_M_MCC_BYTE + 1] & 0x03) << 8) + 1.13399 + (encodedImsi[CSIM_IMSI_M_MCC_BYTE] & 0xff); 1.13400 + let mcc = this.decodeIMSIValue(encodedMCC, 3); 1.13401 + 1.13402 + // MNC: 7 bits, 2 digits 1.13403 + let encodedMNC = encodedImsi[CSIM_IMSI_M_MNC_BYTE] & 0x7f; 1.13404 + let mnc = this.decodeIMSIValue(encodedMNC, 2); 1.13405 + 1.13406 + // MIN2: 10 bits, 3 digits 1.13407 + let encodedMIN2 = ((encodedImsi[CSIM_IMSI_M_MIN2_BYTE + 1] & 0x03) << 8) + 1.13408 + (encodedImsi[CSIM_IMSI_M_MIN2_BYTE] & 0xff); 1.13409 + let min2 = this.decodeIMSIValue(encodedMIN2, 3); 1.13410 + 1.13411 + // MIN1: 10+4+10 bits, 3+1+3 digits 1.13412 + let encodedMIN1First3 = ((encodedImsi[CSIM_IMSI_M_MIN1_BYTE + 2] & 0xff) << 2) + 1.13413 + ((encodedImsi[CSIM_IMSI_M_MIN1_BYTE + 1] & 0xc0) >> 6); 1.13414 + let min1First3 = this.decodeIMSIValue(encodedMIN1First3, 3); 1.13415 + 1.13416 + let encodedFourthDigit = (encodedImsi[CSIM_IMSI_M_MIN1_BYTE + 1] & 0x3c) >> 2; 1.13417 + if (encodedFourthDigit > 9) { 1.13418 + encodedFourthDigit = 0; 1.13419 + } 1.13420 + let fourthDigit = encodedFourthDigit.toString(); 1.13421 + 1.13422 + let encodedMIN1Last3 = ((encodedImsi[CSIM_IMSI_M_MIN1_BYTE + 1] & 0x03) << 8) + 1.13423 + (encodedImsi[CSIM_IMSI_M_MIN1_BYTE] & 0xff); 1.13424 + let min1Last3 = this.decodeIMSIValue(encodedMIN1Last3, 3); 1.13425 + 1.13426 + return mcc + mnc + min2 + min1First3 + fourthDigit + min1Last3; 1.13427 + }, 1.13428 + 1.13429 + /** 1.13430 + * Decode IMSI Helper function 1.13431 + * See 3GPP2 C.S0005 section 2.3.1.1 1.13432 + */ 1.13433 + decodeIMSIValue: function(encoded, length) { 1.13434 + let offset = length === 3 ? 111 : 11; 1.13435 + let value = encoded + offset; 1.13436 + 1.13437 + for (let base = 10, temp = value, i = 0; i < length; i++) { 1.13438 + if (temp % 10 === 0) { 1.13439 + value -= base; 1.13440 + } 1.13441 + temp = Math.floor(value / base); 1.13442 + base = base * 10; 1.13443 + } 1.13444 + 1.13445 + let s = value.toString(); 1.13446 + while (s.length < length) { 1.13447 + s = "0" + s; 1.13448 + } 1.13449 + 1.13450 + return s; 1.13451 + }, 1.13452 + 1.13453 + /** 1.13454 + * Read CDMAHOME for CSIM. 1.13455 + * See 3GPP2 C.S0023 Sec. 3.4.8. 1.13456 + */ 1.13457 + readCDMAHome: function() { 1.13458 + let ICCIOHelper = this.context.ICCIOHelper; 1.13459 + 1.13460 + function callback(options) { 1.13461 + let Buf = this.context.Buf; 1.13462 + let GsmPDUHelper = this.context.GsmPDUHelper; 1.13463 + 1.13464 + let strLen = Buf.readInt32(); 1.13465 + let tempOctet = GsmPDUHelper.readHexOctet(); 1.13466 + cdmaHomeSystemId.push(((GsmPDUHelper.readHexOctet() & 0x7f) << 8) | tempOctet); 1.13467 + tempOctet = GsmPDUHelper.readHexOctet(); 1.13468 + cdmaHomeNetworkId.push(((GsmPDUHelper.readHexOctet() & 0xff) << 8) | tempOctet); 1.13469 + 1.13470 + // Consuming the last octet: band class. 1.13471 + Buf.seekIncoming(Buf.PDU_HEX_OCTET_SIZE); 1.13472 + 1.13473 + Buf.readStringDelimiter(strLen); 1.13474 + if (options.p1 < options.totalRecords) { 1.13475 + ICCIOHelper.loadNextRecord(options); 1.13476 + } else { 1.13477 + if (DEBUG) { 1.13478 + this.context.debug("CDMAHome system id: " + 1.13479 + JSON.stringify(cdmaHomeSystemId)); 1.13480 + this.context.debug("CDMAHome network id: " + 1.13481 + JSON.stringify(cdmaHomeNetworkId)); 1.13482 + } 1.13483 + this.context.RIL.cdmaHome = { 1.13484 + systemId: cdmaHomeSystemId, 1.13485 + networkId: cdmaHomeNetworkId 1.13486 + }; 1.13487 + } 1.13488 + } 1.13489 + 1.13490 + let cdmaHomeSystemId = [], cdmaHomeNetworkId = []; 1.13491 + ICCIOHelper.loadLinearFixedEF({fileId: ICC_EF_CSIM_CDMAHOME, 1.13492 + callback: callback.bind(this)}); 1.13493 + }, 1.13494 + 1.13495 + /** 1.13496 + * Read CDMA Service Table. 1.13497 + * See 3GPP2 C.S0023 Sec. 3.4.18 1.13498 + */ 1.13499 + readCST: function() { 1.13500 + function callback() { 1.13501 + let Buf = this.context.Buf; 1.13502 + let RIL = this.context.RIL; 1.13503 + 1.13504 + let strLen = Buf.readInt32(); 1.13505 + // Each octet is encoded into two chars. 1.13506 + RIL.iccInfoPrivate.cst = 1.13507 + this.context.GsmPDUHelper.readHexOctetArray(strLen / 2); 1.13508 + Buf.readStringDelimiter(strLen); 1.13509 + 1.13510 + if (DEBUG) { 1.13511 + let str = ""; 1.13512 + for (let i = 0; i < RIL.iccInfoPrivate.cst.length; i++) { 1.13513 + str += RIL.iccInfoPrivate.cst[i] + ", "; 1.13514 + } 1.13515 + this.context.debug("CST: " + str); 1.13516 + } 1.13517 + 1.13518 + if (this.context.ICCUtilsHelper.isICCServiceAvailable("SPN")) { 1.13519 + if (DEBUG) this.context.debug("SPN: SPN is available"); 1.13520 + this.readSPN(); 1.13521 + } 1.13522 + } 1.13523 + this.context.ICCIOHelper.loadTransparentEF({ 1.13524 + fileId: ICC_EF_CSIM_CST, 1.13525 + callback: callback.bind(this) 1.13526 + }); 1.13527 + }, 1.13528 + 1.13529 + readSPN: function() { 1.13530 + function callback() { 1.13531 + let Buf = this.context.Buf; 1.13532 + let strLen = Buf.readInt32(); 1.13533 + let octetLen = strLen / 2; 1.13534 + 1.13535 + let GsmPDUHelper = this.context.GsmPDUHelper; 1.13536 + let displayCondition = GsmPDUHelper.readHexOctet(); 1.13537 + let codingScheme = GsmPDUHelper.readHexOctet(); 1.13538 + // Skip one octet: language indicator. 1.13539 + Buf.seekIncoming(Buf.PDU_HEX_OCTET_SIZE); 1.13540 + let readLen = 3; 1.13541 + 1.13542 + // SPN String ends up with 0xff. 1.13543 + let userDataBuffer = []; 1.13544 + 1.13545 + while (readLen < octetLen) { 1.13546 + let octet = GsmPDUHelper.readHexOctet(); 1.13547 + readLen++; 1.13548 + if (octet == 0xff) { 1.13549 + break; 1.13550 + } 1.13551 + userDataBuffer.push(octet); 1.13552 + } 1.13553 + 1.13554 + this.context.BitBufferHelper.startRead(userDataBuffer); 1.13555 + 1.13556 + let CdmaPDUHelper = this.context.CdmaPDUHelper; 1.13557 + let msgLen; 1.13558 + switch (CdmaPDUHelper.getCdmaMsgEncoding(codingScheme)) { 1.13559 + case PDU_DCS_MSG_CODING_7BITS_ALPHABET: 1.13560 + msgLen = Math.floor(userDataBuffer.length * 8 / 7); 1.13561 + break; 1.13562 + case PDU_DCS_MSG_CODING_8BITS_ALPHABET: 1.13563 + msgLen = userDataBuffer.length; 1.13564 + break; 1.13565 + case PDU_DCS_MSG_CODING_16BITS_ALPHABET: 1.13566 + msgLen = Math.floor(userDataBuffer.length / 2); 1.13567 + break; 1.13568 + } 1.13569 + 1.13570 + let RIL = this.context.RIL; 1.13571 + RIL.iccInfo.spn = CdmaPDUHelper.decodeCdmaPDUMsg(codingScheme, null, msgLen); 1.13572 + if (DEBUG) { 1.13573 + this.context.debug("CDMA SPN: " + RIL.iccInfo.spn + 1.13574 + ", Display condition: " + displayCondition); 1.13575 + } 1.13576 + RIL.iccInfoPrivate.spnDisplayCondition = displayCondition; 1.13577 + Buf.seekIncoming((octetLen - readLen) * Buf.PDU_HEX_OCTET_SIZE); 1.13578 + Buf.readStringDelimiter(strLen); 1.13579 + } 1.13580 + 1.13581 + this.context.ICCIOHelper.loadTransparentEF({ 1.13582 + fileId: ICC_EF_CSIM_SPN, 1.13583 + callback: callback.bind(this) 1.13584 + }); 1.13585 + } 1.13586 +}; 1.13587 + 1.13588 +/** 1.13589 + * Helper functions for ICC utilities. 1.13590 + */ 1.13591 +function ICCUtilsHelperObject(aContext) { 1.13592 + this.context = aContext; 1.13593 +} 1.13594 +ICCUtilsHelperObject.prototype = { 1.13595 + context: null, 1.13596 + 1.13597 + /** 1.13598 + * Get network names by using EF_OPL and EF_PNN 1.13599 + * 1.13600 + * @See 3GPP TS 31.102 sec. 4.2.58 and sec. 4.2.59 for USIM, 1.13601 + * 3GPP TS 51.011 sec. 10.3.41 and sec. 10.3.42 for SIM. 1.13602 + * 1.13603 + * @param mcc The mobile country code of the network. 1.13604 + * @param mnc The mobile network code of the network. 1.13605 + * @param lac The location area code of the network. 1.13606 + */ 1.13607 + getNetworkNameFromICC: function(mcc, mnc, lac) { 1.13608 + let RIL = this.context.RIL; 1.13609 + let iccInfoPriv = RIL.iccInfoPrivate; 1.13610 + let iccInfo = RIL.iccInfo; 1.13611 + let pnnEntry; 1.13612 + 1.13613 + if (!mcc || !mnc || !lac) { 1.13614 + return null; 1.13615 + } 1.13616 + 1.13617 + // We won't get network name if there is no PNN file. 1.13618 + if (!iccInfoPriv.PNN) { 1.13619 + return null; 1.13620 + } 1.13621 + 1.13622 + if (!iccInfoPriv.OPL) { 1.13623 + // When OPL is not present: 1.13624 + // According to 3GPP TS 31.102 Sec. 4.2.58 and 3GPP TS 51.011 Sec. 10.3.41, 1.13625 + // If EF_OPL is not present, the first record in this EF is used for the 1.13626 + // default network name when registered to the HPLMN. 1.13627 + // If we haven't get pnnEntry assigned, we should try to assign default 1.13628 + // value to it. 1.13629 + if (mcc == iccInfo.mcc && mnc == iccInfo.mnc) { 1.13630 + pnnEntry = iccInfoPriv.PNN[0]; 1.13631 + } 1.13632 + } else { 1.13633 + // According to 3GPP TS 31.102 Sec. 4.2.59 and 3GPP TS 51.011 Sec. 10.3.42, 1.13634 + // the ME shall use this EF_OPL in association with the EF_PNN in place 1.13635 + // of any network name stored within the ME's internal list and any network 1.13636 + // name received when registered to the PLMN. 1.13637 + let length = iccInfoPriv.OPL ? iccInfoPriv.OPL.length : 0; 1.13638 + for (let i = 0; i < length; i++) { 1.13639 + let opl = iccInfoPriv.OPL[i]; 1.13640 + // Try to match the MCC/MNC. 1.13641 + if (mcc != opl.mcc || mnc != opl.mnc) { 1.13642 + continue; 1.13643 + } 1.13644 + // Try to match the location area code. If current local area code is 1.13645 + // covered by lac range that specified in the OPL entry, use the PNN 1.13646 + // that specified in the OPL entry. 1.13647 + if ((opl.lacTacStart === 0x0 && opl.lacTacEnd == 0xFFFE) || 1.13648 + (opl.lacTacStart <= lac && opl.lacTacEnd >= lac)) { 1.13649 + if (opl.pnnRecordId === 0) { 1.13650 + // See 3GPP TS 31.102 Sec. 4.2.59 and 3GPP TS 51.011 Sec. 10.3.42, 1.13651 + // A value of '00' indicates that the name is to be taken from other 1.13652 + // sources. 1.13653 + return null; 1.13654 + } 1.13655 + pnnEntry = iccInfoPriv.PNN[opl.pnnRecordId - 1]; 1.13656 + break; 1.13657 + } 1.13658 + } 1.13659 + } 1.13660 + 1.13661 + if (!pnnEntry) { 1.13662 + return null; 1.13663 + } 1.13664 + 1.13665 + // Return a new object to avoid global variable, PNN, be modified by accident. 1.13666 + return { fullName: pnnEntry.fullName || "", 1.13667 + shortName: pnnEntry.shortName || "" }; 1.13668 + }, 1.13669 + 1.13670 + /** 1.13671 + * This will compute the spnDisplay field of the network. 1.13672 + * See TS 22.101 Annex A and TS 51.011 10.3.11 for details. 1.13673 + * 1.13674 + * @return True if some of iccInfo is changed in by this function. 1.13675 + */ 1.13676 + updateDisplayCondition: function() { 1.13677 + let RIL = this.context.RIL; 1.13678 + 1.13679 + // If EFspn isn't existed in SIM or it haven't been read yet, we should 1.13680 + // just set isDisplayNetworkNameRequired = true and 1.13681 + // isDisplaySpnRequired = false 1.13682 + let iccInfo = RIL.iccInfo; 1.13683 + let iccInfoPriv = RIL.iccInfoPrivate; 1.13684 + let displayCondition = iccInfoPriv.spnDisplayCondition; 1.13685 + let origIsDisplayNetworkNameRequired = iccInfo.isDisplayNetworkNameRequired; 1.13686 + let origIsDisplaySPNRequired = iccInfo.isDisplaySpnRequired; 1.13687 + 1.13688 + if (displayCondition === undefined) { 1.13689 + iccInfo.isDisplayNetworkNameRequired = true; 1.13690 + iccInfo.isDisplaySpnRequired = false; 1.13691 + } else if (RIL._isCdma) { 1.13692 + // CDMA family display rule. 1.13693 + let cdmaHome = RIL.cdmaHome; 1.13694 + let cell = RIL.voiceRegistrationState.cell; 1.13695 + let sid = cell && cell.cdmaSystemId; 1.13696 + let nid = cell && cell.cdmaNetworkId; 1.13697 + 1.13698 + iccInfo.isDisplayNetworkNameRequired = false; 1.13699 + 1.13700 + // If display condition is 0x0, we don't even need to check network id 1.13701 + // or system id. 1.13702 + if (displayCondition === 0x0) { 1.13703 + iccInfo.isDisplaySpnRequired = false; 1.13704 + } else { 1.13705 + // CDMA SPN Display condition dosen't specify whenever network name is 1.13706 + // reqired. 1.13707 + if (!cdmaHome || 1.13708 + !cdmaHome.systemId || 1.13709 + cdmaHome.systemId.length === 0 || 1.13710 + cdmaHome.systemId.length != cdmaHome.networkId.length || 1.13711 + !sid || !nid) { 1.13712 + // CDMA Home haven't been ready, or we haven't got the system id and 1.13713 + // network id of the network we register to, assuming we are in home 1.13714 + // network. 1.13715 + iccInfo.isDisplaySpnRequired = true; 1.13716 + } else { 1.13717 + // Determine if we are registered in the home service area. 1.13718 + // System ID and Network ID are described in 3GPP2 C.S0005 Sec. 2.6.5.2. 1.13719 + let inHomeArea = false; 1.13720 + for (let i = 0; i < cdmaHome.systemId.length; i++) { 1.13721 + let homeSid = cdmaHome.systemId[i], 1.13722 + homeNid = cdmaHome.networkId[i]; 1.13723 + if (homeSid === 0 || homeNid === 0 // Reserved system id/network id 1.13724 + || homeSid != sid) { 1.13725 + continue; 1.13726 + } 1.13727 + // According to 3GPP2 C.S0005 Sec. 2.6.5.2, NID number 65535 means 1.13728 + // all networks in the system should be considered as home. 1.13729 + if (homeNid == 65535 || homeNid == nid) { 1.13730 + inHomeArea = true; 1.13731 + break; 1.13732 + } 1.13733 + } 1.13734 + iccInfo.isDisplaySpnRequired = inHomeArea; 1.13735 + } 1.13736 + } 1.13737 + } else { 1.13738 + // GSM family display rule. 1.13739 + let operatorMnc = RIL.operator.mnc; 1.13740 + let operatorMcc = RIL.operator.mcc; 1.13741 + 1.13742 + // First detect if we are on HPLMN or one of the PLMN 1.13743 + // specified by the SIM card. 1.13744 + let isOnMatchingPlmn = false; 1.13745 + 1.13746 + // If the current network is the one defined as mcc/mnc 1.13747 + // in SIM card, it's okay. 1.13748 + if (iccInfo.mcc == operatorMcc && iccInfo.mnc == operatorMnc) { 1.13749 + isOnMatchingPlmn = true; 1.13750 + } 1.13751 + 1.13752 + // Test to see if operator's mcc/mnc match mcc/mnc of PLMN. 1.13753 + if (!isOnMatchingPlmn && iccInfoPriv.SPDI) { 1.13754 + let iccSpdi = iccInfoPriv.SPDI; // PLMN list 1.13755 + for (let plmn in iccSpdi) { 1.13756 + let plmnMcc = iccSpdi[plmn].mcc; 1.13757 + let plmnMnc = iccSpdi[plmn].mnc; 1.13758 + isOnMatchingPlmn = (plmnMcc == operatorMcc) && (plmnMnc == operatorMnc); 1.13759 + if (isOnMatchingPlmn) { 1.13760 + break; 1.13761 + } 1.13762 + } 1.13763 + } 1.13764 + 1.13765 + if (isOnMatchingPlmn) { 1.13766 + // The first bit of display condition tells us if we should display 1.13767 + // registered PLMN. 1.13768 + if (DEBUG) { 1.13769 + this.context.debug("PLMN is HPLMN or PLMN " + "is in PLMN list"); 1.13770 + } 1.13771 + 1.13772 + // TS 31.102 Sec. 4.2.66 and TS 51.011 Sec. 10.3.50 1.13773 + // EF_SPDI contains a list of PLMNs in which the Service Provider Name 1.13774 + // shall be displayed. 1.13775 + iccInfo.isDisplaySpnRequired = true; 1.13776 + iccInfo.isDisplayNetworkNameRequired = (displayCondition & 0x01) !== 0; 1.13777 + } else { 1.13778 + // The second bit of display condition tells us if we should display 1.13779 + // registered PLMN. 1.13780 + if (DEBUG) { 1.13781 + this.context.debug("PLMN isn't HPLMN and PLMN isn't in PLMN list"); 1.13782 + } 1.13783 + 1.13784 + // We didn't found the requirement of displaying network name if 1.13785 + // current PLMN isn't HPLMN nor one of PLMN in SPDI. So we keep 1.13786 + // isDisplayNetworkNameRequired false. 1.13787 + iccInfo.isDisplayNetworkNameRequired = false; 1.13788 + iccInfo.isDisplaySpnRequired = (displayCondition & 0x02) === 0; 1.13789 + } 1.13790 + } 1.13791 + 1.13792 + if (DEBUG) { 1.13793 + this.context.debug("isDisplayNetworkNameRequired = " + 1.13794 + iccInfo.isDisplayNetworkNameRequired); 1.13795 + this.context.debug("isDisplaySpnRequired = " + iccInfo.isDisplaySpnRequired); 1.13796 + } 1.13797 + 1.13798 + return ((origIsDisplayNetworkNameRequired !== iccInfo.isDisplayNetworkNameRequired) || 1.13799 + (origIsDisplaySPNRequired !== iccInfo.isDisplaySpnRequired)); 1.13800 + }, 1.13801 + 1.13802 + decodeSimTlvs: function(tlvsLen) { 1.13803 + let GsmPDUHelper = this.context.GsmPDUHelper; 1.13804 + 1.13805 + let index = 0; 1.13806 + let tlvs = []; 1.13807 + while (index < tlvsLen) { 1.13808 + let simTlv = { 1.13809 + tag : GsmPDUHelper.readHexOctet(), 1.13810 + length : GsmPDUHelper.readHexOctet(), 1.13811 + }; 1.13812 + simTlv.value = GsmPDUHelper.readHexOctetArray(simTlv.length); 1.13813 + tlvs.push(simTlv); 1.13814 + index += simTlv.length + 2; // The length of 'tag' and 'length' field. 1.13815 + } 1.13816 + return tlvs; 1.13817 + }, 1.13818 + 1.13819 + /** 1.13820 + * Parse those TLVs and convert it to an object. 1.13821 + */ 1.13822 + parsePbrTlvs: function(pbrTlvs) { 1.13823 + let pbr = {}; 1.13824 + for (let i = 0; i < pbrTlvs.length; i++) { 1.13825 + let pbrTlv = pbrTlvs[i]; 1.13826 + let anrIndex = 0; 1.13827 + for (let j = 0; j < pbrTlv.value.length; j++) { 1.13828 + let tlv = pbrTlv.value[j]; 1.13829 + let tagName = USIM_TAG_NAME[tlv.tag]; 1.13830 + 1.13831 + // ANR could have multiple files. We save it as anr0, anr1,...etc. 1.13832 + if (tlv.tag == ICC_USIM_EFANR_TAG) { 1.13833 + tagName += anrIndex; 1.13834 + anrIndex++; 1.13835 + } 1.13836 + pbr[tagName] = tlv; 1.13837 + pbr[tagName].fileType = pbrTlv.tag; 1.13838 + pbr[tagName].fileId = (tlv.value[0] << 8) | tlv.value[1]; 1.13839 + pbr[tagName].sfi = tlv.value[2]; 1.13840 + 1.13841 + // For Type 2, the order of files is in the same order in IAP. 1.13842 + if (pbrTlv.tag == ICC_USIM_TYPE2_TAG) { 1.13843 + pbr[tagName].indexInIAP = j; 1.13844 + } 1.13845 + } 1.13846 + } 1.13847 + 1.13848 + return pbr; 1.13849 + }, 1.13850 + 1.13851 + /** 1.13852 + * Update the ICC information to RadioInterfaceLayer. 1.13853 + */ 1.13854 + handleICCInfoChange: function() { 1.13855 + let RIL = this.context.RIL; 1.13856 + RIL.iccInfo.rilMessageType = "iccinfochange"; 1.13857 + RIL.sendChromeMessage(RIL.iccInfo); 1.13858 + }, 1.13859 + 1.13860 + /** 1.13861 + * Get whether specificed (U)SIM service is available. 1.13862 + * 1.13863 + * @param geckoService 1.13864 + * Service name like "ADN", "BDN", etc. 1.13865 + * 1.13866 + * @return true if the service is enabled, false otherwise. 1.13867 + */ 1.13868 + isICCServiceAvailable: function(geckoService) { 1.13869 + let RIL = this.context.RIL; 1.13870 + let serviceTable = RIL._isCdma ? RIL.iccInfoPrivate.cst: 1.13871 + RIL.iccInfoPrivate.sst; 1.13872 + let index, bitmask; 1.13873 + if (RIL.appType == CARD_APPTYPE_SIM || RIL.appType == CARD_APPTYPE_RUIM) { 1.13874 + /** 1.13875 + * Service id is valid in 1..N, and 2 bits are used to code each service. 1.13876 + * 1.13877 + * +----+-- --+----+----+ 1.13878 + * | b8 | ... | b2 | b1 | 1.13879 + * +----+-- --+----+----+ 1.13880 + * 1.13881 + * b1 = 0, service not allocated. 1.13882 + * 1, service allocated. 1.13883 + * b2 = 0, service not activatd. 1.13884 + * 1, service allocated. 1.13885 + * 1.13886 + * @see 3GPP TS 51.011 10.3.7. 1.13887 + */ 1.13888 + let simService; 1.13889 + if (RIL.appType == CARD_APPTYPE_SIM) { 1.13890 + simService = GECKO_ICC_SERVICES.sim[geckoService]; 1.13891 + } else { 1.13892 + simService = GECKO_ICC_SERVICES.ruim[geckoService]; 1.13893 + } 1.13894 + if (!simService) { 1.13895 + return false; 1.13896 + } 1.13897 + simService -= 1; 1.13898 + index = Math.floor(simService / 4); 1.13899 + bitmask = 2 << ((simService % 4) << 1); 1.13900 + } else if (RIL.appType == CARD_APPTYPE_USIM) { 1.13901 + /** 1.13902 + * Service id is valid in 1..N, and 1 bit is used to code each service. 1.13903 + * 1.13904 + * +----+-- --+----+----+ 1.13905 + * | b8 | ... | b2 | b1 | 1.13906 + * +----+-- --+----+----+ 1.13907 + * 1.13908 + * b1 = 0, service not avaiable. 1.13909 + * 1, service available. 1.13910 + * b2 = 0, service not avaiable. 1.13911 + * 1, service available. 1.13912 + * 1.13913 + * @see 3GPP TS 31.102 4.2.8. 1.13914 + */ 1.13915 + let usimService = GECKO_ICC_SERVICES.usim[geckoService]; 1.13916 + if (!usimService) { 1.13917 + return false; 1.13918 + } 1.13919 + usimService -= 1; 1.13920 + index = Math.floor(usimService / 8); 1.13921 + bitmask = 1 << ((usimService % 8) << 0); 1.13922 + } 1.13923 + 1.13924 + return (serviceTable !== null) && 1.13925 + (index < serviceTable.length) && 1.13926 + ((serviceTable[index] & bitmask) !== 0); 1.13927 + }, 1.13928 + 1.13929 + /** 1.13930 + * Check if the string is of GSM default 7-bit coded alphabets with bit 8 1.13931 + * set to 0. 1.13932 + * 1.13933 + * @param str String to be checked. 1.13934 + */ 1.13935 + isGsm8BitAlphabet: function(str) { 1.13936 + if (!str) { 1.13937 + return false; 1.13938 + } 1.13939 + 1.13940 + const langTable = PDU_NL_LOCKING_SHIFT_TABLES[PDU_NL_IDENTIFIER_DEFAULT]; 1.13941 + const langShiftTable = PDU_NL_SINGLE_SHIFT_TABLES[PDU_NL_IDENTIFIER_DEFAULT]; 1.13942 + 1.13943 + for (let i = 0; i < str.length; i++) { 1.13944 + let c = str.charAt(i); 1.13945 + let octet = langTable.indexOf(c); 1.13946 + if (octet == -1) { 1.13947 + octet = langShiftTable.indexOf(c); 1.13948 + if (octet == -1) { 1.13949 + return false; 1.13950 + } 1.13951 + } 1.13952 + } 1.13953 + 1.13954 + return true; 1.13955 + }, 1.13956 + 1.13957 + /** 1.13958 + * Parse MCC/MNC from IMSI. If there is no available value for the length of 1.13959 + * mnc, it will use the data in MCC table to parse. 1.13960 + * 1.13961 + * @param imsi 1.13962 + * The imsi of icc. 1.13963 + * @param mncLength [optional] 1.13964 + * The length of mnc. 1.13965 + * 1.13966 + * @return An object contains the parsing result of mcc and mnc. 1.13967 + * Or null if any error occurred. 1.13968 + */ 1.13969 + parseMccMncFromImsi: function(imsi, mncLength) { 1.13970 + if (!imsi) { 1.13971 + return null; 1.13972 + } 1.13973 + 1.13974 + // MCC is the first 3 digits of IMSI. 1.13975 + let mcc = imsi.substr(0,3); 1.13976 + if (!mncLength) { 1.13977 + // Check the MCC table to decide the length of MNC. 1.13978 + let index = MCC_TABLE_FOR_MNC_LENGTH_IS_3.indexOf(mcc); 1.13979 + mncLength = (index !== -1) ? 3 : 2; 1.13980 + } 1.13981 + let mnc = imsi.substr(3, mncLength); 1.13982 + if (DEBUG) { 1.13983 + this.context.debug("IMSI: " + imsi + " MCC: " + mcc + " MNC: " + mnc); 1.13984 + } 1.13985 + 1.13986 + return { mcc: mcc, mnc: mnc}; 1.13987 + }, 1.13988 +}; 1.13989 + 1.13990 +/** 1.13991 + * Helper for ICC Contacts. 1.13992 + */ 1.13993 +function ICCContactHelperObject(aContext) { 1.13994 + this.context = aContext; 1.13995 +} 1.13996 +ICCContactHelperObject.prototype = { 1.13997 + context: null, 1.13998 + 1.13999 + /** 1.14000 + * Helper function to check DF_PHONEBOOK. 1.14001 + */ 1.14002 + hasDfPhoneBook: function(appType) { 1.14003 + switch (appType) { 1.14004 + case CARD_APPTYPE_SIM: 1.14005 + return false; 1.14006 + case CARD_APPTYPE_USIM: 1.14007 + return true; 1.14008 + case CARD_APPTYPE_RUIM: 1.14009 + let ICCUtilsHelper = this.context.ICCUtilsHelper; 1.14010 + return ICCUtilsHelper.isICCServiceAvailable("ENHANCED_PHONEBOOK"); 1.14011 + default: 1.14012 + return false; 1.14013 + } 1.14014 + }, 1.14015 + 1.14016 + /** 1.14017 + * Helper function to read ICC contacts. 1.14018 + * 1.14019 + * @param appType One of CARD_APPTYPE_*. 1.14020 + * @param contactType "adn" or "fdn". 1.14021 + * @param onsuccess Callback to be called when success. 1.14022 + * @param onerror Callback to be called when error. 1.14023 + */ 1.14024 + readICCContacts: function(appType, contactType, onsuccess, onerror) { 1.14025 + let ICCRecordHelper = this.context.ICCRecordHelper; 1.14026 + 1.14027 + switch (contactType) { 1.14028 + case "adn": 1.14029 + if (!this.hasDfPhoneBook(appType)) { 1.14030 + ICCRecordHelper.readADNLike(ICC_EF_ADN, onsuccess, onerror); 1.14031 + } else { 1.14032 + this.readUSimContacts(onsuccess, onerror); 1.14033 + } 1.14034 + break; 1.14035 + case "fdn": 1.14036 + ICCRecordHelper.readADNLike(ICC_EF_FDN, onsuccess, onerror); 1.14037 + break; 1.14038 + default: 1.14039 + if (DEBUG) { 1.14040 + this.context.debug("Unsupported contactType :" + contactType); 1.14041 + } 1.14042 + onerror(CONTACT_ERR_CONTACT_TYPE_NOT_SUPPORTED); 1.14043 + break; 1.14044 + } 1.14045 + }, 1.14046 + 1.14047 + /** 1.14048 + * Helper function to find free contact record. 1.14049 + * 1.14050 + * @param appType One of CARD_APPTYPE_*. 1.14051 + * @param contactType "adn" or "fdn". 1.14052 + * @param onsuccess Callback to be called when success. 1.14053 + * @param onerror Callback to be called when error. 1.14054 + */ 1.14055 + findFreeICCContact: function(appType, contactType, onsuccess, onerror) { 1.14056 + let ICCRecordHelper = this.context.ICCRecordHelper; 1.14057 + 1.14058 + switch (contactType) { 1.14059 + case "adn": 1.14060 + if (!this.hasDfPhoneBook(appType)) { 1.14061 + ICCRecordHelper.findFreeRecordId(ICC_EF_ADN, onsuccess.bind(null, 0), onerror); 1.14062 + } else { 1.14063 + let gotPbrCb = function gotPbrCb(pbrs) { 1.14064 + this.findUSimFreeADNRecordId(pbrs, onsuccess, onerror); 1.14065 + }.bind(this); 1.14066 + 1.14067 + ICCRecordHelper.readPBR(gotPbrCb, onerror); 1.14068 + } 1.14069 + break; 1.14070 + case "fdn": 1.14071 + ICCRecordHelper.findFreeRecordId(ICC_EF_FDN, onsuccess.bind(null, 0), onerror); 1.14072 + break; 1.14073 + default: 1.14074 + if (DEBUG) { 1.14075 + this.context.debug("Unsupported contactType :" + contactType); 1.14076 + } 1.14077 + onerror(CONTACT_ERR_CONTACT_TYPE_NOT_SUPPORTED); 1.14078 + break; 1.14079 + } 1.14080 + }, 1.14081 + 1.14082 + /** 1.14083 + * Find free ADN record id in USIM. 1.14084 + * 1.14085 + * @param pbrs All Phonebook Reference Files read. 1.14086 + * @param onsuccess Callback to be called when success. 1.14087 + * @param onerror Callback to be called when error. 1.14088 + */ 1.14089 + findUSimFreeADNRecordId: function(pbrs, onsuccess, onerror) { 1.14090 + let ICCRecordHelper = this.context.ICCRecordHelper; 1.14091 + 1.14092 + (function findFreeRecordId(pbrIndex) { 1.14093 + if (pbrIndex >= pbrs.length) { 1.14094 + if (DEBUG) { 1.14095 + this.context.debug(CONTACT_ERR_NO_FREE_RECORD_FOUND); 1.14096 + } 1.14097 + onerror(CONTACT_ERR_NO_FREE_RECORD_FOUND); 1.14098 + return; 1.14099 + } 1.14100 + 1.14101 + let pbr = pbrs[pbrIndex]; 1.14102 + ICCRecordHelper.findFreeRecordId( 1.14103 + pbr.adn.fileId, 1.14104 + onsuccess.bind(this, pbrIndex), 1.14105 + findFreeRecordId.bind(null, pbrIndex + 1)); 1.14106 + })(0); 1.14107 + }, 1.14108 + 1.14109 + /** 1.14110 + * Helper function to add a new ICC contact. 1.14111 + * 1.14112 + * @param appType One of CARD_APPTYPE_*. 1.14113 + * @param contactType "adn" or "fdn". 1.14114 + * @param contact The contact will be added. 1.14115 + * @param pin2 PIN2 is required for FDN. 1.14116 + * @param onsuccess Callback to be called when success. 1.14117 + * @param onerror Callback to be called when error. 1.14118 + */ 1.14119 + addICCContact: function(appType, contactType, contact, pin2, onsuccess, onerror) { 1.14120 + let foundFreeCb = (function foundFreeCb(pbrIndex, recordId) { 1.14121 + contact.pbrIndex = pbrIndex; 1.14122 + contact.recordId = recordId; 1.14123 + this.updateICCContact(appType, contactType, contact, pin2, onsuccess, onerror); 1.14124 + }).bind(this); 1.14125 + 1.14126 + // Find free record first. 1.14127 + this.findFreeICCContact(appType, contactType, foundFreeCb, onerror); 1.14128 + }, 1.14129 + 1.14130 + /** 1.14131 + * Helper function to update ICC contact. 1.14132 + * 1.14133 + * @param appType One of CARD_APPTYPE_*. 1.14134 + * @param contactType "adn" or "fdn". 1.14135 + * @param contact The contact will be updated. 1.14136 + * @param pin2 PIN2 is required for FDN. 1.14137 + * @param onsuccess Callback to be called when success. 1.14138 + * @param onerror Callback to be called when error. 1.14139 + */ 1.14140 + updateICCContact: function(appType, contactType, contact, pin2, onsuccess, onerror) { 1.14141 + let ICCRecordHelper = this.context.ICCRecordHelper; 1.14142 + 1.14143 + switch (contactType) { 1.14144 + case "adn": 1.14145 + if (!this.hasDfPhoneBook(appType)) { 1.14146 + ICCRecordHelper.updateADNLike(ICC_EF_ADN, contact, null, onsuccess, onerror); 1.14147 + } else { 1.14148 + this.updateUSimContact(contact, onsuccess, onerror); 1.14149 + } 1.14150 + break; 1.14151 + case "fdn": 1.14152 + if (!pin2) { 1.14153 + onerror(GECKO_ERROR_SIM_PIN2); 1.14154 + return; 1.14155 + } 1.14156 + ICCRecordHelper.updateADNLike(ICC_EF_FDN, contact, pin2, onsuccess, onerror); 1.14157 + break; 1.14158 + default: 1.14159 + if (DEBUG) { 1.14160 + this.context.debug("Unsupported contactType :" + contactType); 1.14161 + } 1.14162 + onerror(CONTACT_ERR_CONTACT_TYPE_NOT_SUPPORTED); 1.14163 + break; 1.14164 + } 1.14165 + }, 1.14166 + 1.14167 + /** 1.14168 + * Read contacts from USIM. 1.14169 + * 1.14170 + * @param onsuccess Callback to be called when success. 1.14171 + * @param onerror Callback to be called when error. 1.14172 + */ 1.14173 + readUSimContacts: function(onsuccess, onerror) { 1.14174 + let gotPbrCb = function gotPbrCb(pbrs) { 1.14175 + this.readAllPhonebookSets(pbrs, onsuccess, onerror); 1.14176 + }.bind(this); 1.14177 + 1.14178 + this.context.ICCRecordHelper.readPBR(gotPbrCb, onerror); 1.14179 + }, 1.14180 + 1.14181 + /** 1.14182 + * Read all Phonebook sets. 1.14183 + * 1.14184 + * @param pbrs All Phonebook Reference Files read. 1.14185 + * @param onsuccess Callback to be called when success. 1.14186 + * @param onerror Callback to be called when error. 1.14187 + */ 1.14188 + readAllPhonebookSets: function(pbrs, onsuccess, onerror) { 1.14189 + let allContacts = [], pbrIndex = 0; 1.14190 + let readPhonebook = function readPhonebook(contacts) { 1.14191 + if (contacts) { 1.14192 + allContacts = allContacts.concat(contacts); 1.14193 + } 1.14194 + 1.14195 + let cLen = contacts ? contacts.length : 0; 1.14196 + for (let i = 0; i < cLen; i++) { 1.14197 + contacts[i].pbrIndex = pbrIndex; 1.14198 + } 1.14199 + 1.14200 + pbrIndex++; 1.14201 + if (pbrIndex >= pbrs.length) { 1.14202 + if (onsuccess) { 1.14203 + onsuccess(allContacts); 1.14204 + } 1.14205 + return; 1.14206 + } 1.14207 + 1.14208 + this.readPhonebookSet(pbrs[pbrIndex], readPhonebook, onerror); 1.14209 + }.bind(this); 1.14210 + 1.14211 + this.readPhonebookSet(pbrs[pbrIndex], readPhonebook, onerror); 1.14212 + }, 1.14213 + 1.14214 + /** 1.14215 + * Read from Phonebook Reference File. 1.14216 + * 1.14217 + * @param pbr Phonebook Reference File to be read. 1.14218 + * @param onsuccess Callback to be called when success. 1.14219 + * @param onerror Callback to be called when error. 1.14220 + */ 1.14221 + readPhonebookSet: function(pbr, onsuccess, onerror) { 1.14222 + let gotAdnCb = function gotAdnCb(contacts) { 1.14223 + this.readSupportedPBRFields(pbr, contacts, onsuccess, onerror); 1.14224 + }.bind(this); 1.14225 + 1.14226 + this.context.ICCRecordHelper.readADNLike(pbr.adn.fileId, gotAdnCb, onerror); 1.14227 + }, 1.14228 + 1.14229 + /** 1.14230 + * Read supported Phonebook fields. 1.14231 + * 1.14232 + * @param pbr Phone Book Reference file. 1.14233 + * @param contacts Contacts stored on ICC. 1.14234 + * @param onsuccess Callback to be called when success. 1.14235 + * @param onerror Callback to be called when error. 1.14236 + */ 1.14237 + readSupportedPBRFields: function(pbr, contacts, onsuccess, onerror) { 1.14238 + let fieldIndex = 0; 1.14239 + (function readField() { 1.14240 + let field = USIM_PBR_FIELDS[fieldIndex]; 1.14241 + fieldIndex += 1; 1.14242 + if (!field) { 1.14243 + if (onsuccess) { 1.14244 + onsuccess(contacts); 1.14245 + } 1.14246 + return; 1.14247 + } 1.14248 + 1.14249 + this.readPhonebookField(pbr, contacts, field, readField.bind(this), onerror); 1.14250 + }).call(this); 1.14251 + }, 1.14252 + 1.14253 + /** 1.14254 + * Read Phonebook field. 1.14255 + * 1.14256 + * @param pbr The phonebook reference file. 1.14257 + * @param contacts Contacts stored on ICC. 1.14258 + * @param field Phonebook field to be retrieved. 1.14259 + * @param onsuccess Callback to be called when success. 1.14260 + * @param onerror Callback to be called when error. 1.14261 + */ 1.14262 + readPhonebookField: function(pbr, contacts, field, onsuccess, onerror) { 1.14263 + if (!pbr[field]) { 1.14264 + if (onsuccess) { 1.14265 + onsuccess(contacts); 1.14266 + } 1.14267 + return; 1.14268 + } 1.14269 + 1.14270 + (function doReadContactField(n) { 1.14271 + if (n >= contacts.length) { 1.14272 + // All contact's fields are read. 1.14273 + if (onsuccess) { 1.14274 + onsuccess(contacts); 1.14275 + } 1.14276 + return; 1.14277 + } 1.14278 + 1.14279 + // get n-th contact's field. 1.14280 + this.readContactField(pbr, contacts[n], field, 1.14281 + doReadContactField.bind(this, n + 1), onerror); 1.14282 + }).call(this, 0); 1.14283 + }, 1.14284 + 1.14285 + /** 1.14286 + * Read contact's field from USIM. 1.14287 + * 1.14288 + * @param pbr The phonebook reference file. 1.14289 + * @param contact The contact needs to get field. 1.14290 + * @param field Phonebook field to be retrieved. 1.14291 + * @param onsuccess Callback to be called when success. 1.14292 + * @param onerror Callback to be called when error. 1.14293 + */ 1.14294 + readContactField: function(pbr, contact, field, onsuccess, onerror) { 1.14295 + let gotRecordIdCb = function gotRecordIdCb(recordId) { 1.14296 + if (recordId == 0xff) { 1.14297 + if (onsuccess) { 1.14298 + onsuccess(); 1.14299 + } 1.14300 + return; 1.14301 + } 1.14302 + 1.14303 + let fileId = pbr[field].fileId; 1.14304 + let fileType = pbr[field].fileType; 1.14305 + let gotFieldCb = function gotFieldCb(value) { 1.14306 + if (value) { 1.14307 + // Move anr0 anr1,.. into anr[]. 1.14308 + if (field.startsWith(USIM_PBR_ANR)) { 1.14309 + if (!contact[USIM_PBR_ANR]) { 1.14310 + contact[USIM_PBR_ANR] = []; 1.14311 + } 1.14312 + contact[USIM_PBR_ANR].push(value); 1.14313 + } else { 1.14314 + contact[field] = value; 1.14315 + } 1.14316 + } 1.14317 + 1.14318 + if (onsuccess) { 1.14319 + onsuccess(); 1.14320 + } 1.14321 + }.bind(this); 1.14322 + 1.14323 + let ICCRecordHelper = this.context.ICCRecordHelper; 1.14324 + // Detect EF to be read, for anr, it could have anr0, anr1,... 1.14325 + let ef = field.startsWith(USIM_PBR_ANR) ? USIM_PBR_ANR : field; 1.14326 + switch (ef) { 1.14327 + case USIM_PBR_EMAIL: 1.14328 + ICCRecordHelper.readEmail(fileId, fileType, recordId, gotFieldCb, onerror); 1.14329 + break; 1.14330 + case USIM_PBR_ANR: 1.14331 + ICCRecordHelper.readANR(fileId, fileType, recordId, gotFieldCb, onerror); 1.14332 + break; 1.14333 + default: 1.14334 + if (DEBUG) { 1.14335 + this.context.debug("Unsupported field :" + field); 1.14336 + } 1.14337 + onerror(CONTACT_ERR_FIELD_NOT_SUPPORTED); 1.14338 + break; 1.14339 + } 1.14340 + }.bind(this); 1.14341 + 1.14342 + this.getContactFieldRecordId(pbr, contact, field, gotRecordIdCb, onerror); 1.14343 + }, 1.14344 + 1.14345 + /** 1.14346 + * Get the recordId. 1.14347 + * 1.14348 + * If the fileType of field is ICC_USIM_TYPE1_TAG, use corresponding ADN recordId. 1.14349 + * otherwise get the recordId from IAP. 1.14350 + * 1.14351 + * @see TS 131.102, clause 4.4.2.2 1.14352 + * 1.14353 + * @param pbr The phonebook reference file. 1.14354 + * @param contact The contact will be updated. 1.14355 + * @param onsuccess Callback to be called when success. 1.14356 + * @param onerror Callback to be called when error. 1.14357 + */ 1.14358 + getContactFieldRecordId: function(pbr, contact, field, onsuccess, onerror) { 1.14359 + if (pbr[field].fileType == ICC_USIM_TYPE1_TAG) { 1.14360 + // If the file type is ICC_USIM_TYPE1_TAG, use corresponding ADN recordId. 1.14361 + if (onsuccess) { 1.14362 + onsuccess(contact.recordId); 1.14363 + } 1.14364 + } else if (pbr[field].fileType == ICC_USIM_TYPE2_TAG) { 1.14365 + // If the file type is ICC_USIM_TYPE2_TAG, the recordId shall be got from IAP. 1.14366 + let gotIapCb = function gotIapCb(iap) { 1.14367 + let indexInIAP = pbr[field].indexInIAP; 1.14368 + let recordId = iap[indexInIAP]; 1.14369 + 1.14370 + if (onsuccess) { 1.14371 + onsuccess(recordId); 1.14372 + } 1.14373 + }.bind(this); 1.14374 + 1.14375 + this.context.ICCRecordHelper.readIAP(pbr.iap.fileId, contact.recordId, 1.14376 + gotIapCb, onerror); 1.14377 + } else { 1.14378 + if (DEBUG) { 1.14379 + this.context.debug("USIM PBR files in Type 3 format are not supported."); 1.14380 + } 1.14381 + onerror(CONTACT_ERR_REQUEST_NOT_SUPPORTED); 1.14382 + } 1.14383 + }, 1.14384 + 1.14385 + /** 1.14386 + * Update USIM contact. 1.14387 + * 1.14388 + * @param contact The contact will be updated. 1.14389 + * @param onsuccess Callback to be called when success. 1.14390 + * @param onerror Callback to be called when error. 1.14391 + */ 1.14392 + updateUSimContact: function(contact, onsuccess, onerror) { 1.14393 + let gotPbrCb = function gotPbrCb(pbrs) { 1.14394 + let pbr = pbrs[contact.pbrIndex]; 1.14395 + if (!pbr) { 1.14396 + if (DEBUG) { 1.14397 + this.context.debug(CONTACT_ERR_CANNOT_ACCESS_PHONEBOOK); 1.14398 + } 1.14399 + onerror(CONTACT_ERR_CANNOT_ACCESS_PHONEBOOK); 1.14400 + return; 1.14401 + } 1.14402 + this.updatePhonebookSet(pbr, contact, onsuccess, onerror); 1.14403 + }.bind(this); 1.14404 + 1.14405 + this.context.ICCRecordHelper.readPBR(gotPbrCb, onerror); 1.14406 + }, 1.14407 + 1.14408 + /** 1.14409 + * Update fields in Phonebook Reference File. 1.14410 + * 1.14411 + * @param pbr Phonebook Reference File to be read. 1.14412 + * @param onsuccess Callback to be called when success. 1.14413 + * @param onerror Callback to be called when error. 1.14414 + */ 1.14415 + updatePhonebookSet: function(pbr, contact, onsuccess, onerror) { 1.14416 + let updateAdnCb = function() { 1.14417 + this.updateSupportedPBRFields(pbr, contact, onsuccess, onerror); 1.14418 + }.bind(this); 1.14419 + 1.14420 + this.context.ICCRecordHelper.updateADNLike(pbr.adn.fileId, contact, null, 1.14421 + updateAdnCb, onerror); 1.14422 + }, 1.14423 + 1.14424 + /** 1.14425 + * Update supported Phonebook fields. 1.14426 + * 1.14427 + * @param pbr Phone Book Reference file. 1.14428 + * @param contact Contact to be updated. 1.14429 + * @param onsuccess Callback to be called when success. 1.14430 + * @param onerror Callback to be called when error. 1.14431 + */ 1.14432 + updateSupportedPBRFields: function(pbr, contact, onsuccess, onerror) { 1.14433 + let fieldIndex = 0; 1.14434 + (function updateField() { 1.14435 + let field = USIM_PBR_FIELDS[fieldIndex]; 1.14436 + fieldIndex += 1; 1.14437 + if (!field) { 1.14438 + if (onsuccess) { 1.14439 + onsuccess(); 1.14440 + } 1.14441 + return; 1.14442 + } 1.14443 + 1.14444 + // Check if PBR has this field. 1.14445 + if (!pbr[field]) { 1.14446 + updateField.call(this); 1.14447 + return; 1.14448 + } 1.14449 + 1.14450 + this.updateContactField(pbr, contact, field, updateField.bind(this), onerror); 1.14451 + }).call(this); 1.14452 + }, 1.14453 + 1.14454 + /** 1.14455 + * Update contact's field from USIM. 1.14456 + * 1.14457 + * @param pbr The phonebook reference file. 1.14458 + * @param contact The contact needs to be updated. 1.14459 + * @param field Phonebook field to be updated. 1.14460 + * @param onsuccess Callback to be called when success. 1.14461 + * @param onerror Callback to be called when error. 1.14462 + */ 1.14463 + updateContactField: function(pbr, contact, field, onsuccess, onerror) { 1.14464 + if (pbr[field].fileType === ICC_USIM_TYPE1_TAG) { 1.14465 + this.updateContactFieldType1(pbr, contact, field, onsuccess, onerror); 1.14466 + } else if (pbr[field].fileType === ICC_USIM_TYPE2_TAG) { 1.14467 + this.updateContactFieldType2(pbr, contact, field, onsuccess, onerror); 1.14468 + } else { 1.14469 + if (DEBUG) { 1.14470 + this.context.debug("USIM PBR files in Type 3 format are not supported."); 1.14471 + } 1.14472 + onerror(CONTACT_ERR_REQUEST_NOT_SUPPORTED); 1.14473 + } 1.14474 + }, 1.14475 + 1.14476 + /** 1.14477 + * Update Type 1 USIM contact fields. 1.14478 + * 1.14479 + * @param pbr The phonebook reference file. 1.14480 + * @param contact The contact needs to be updated. 1.14481 + * @param field Phonebook field to be updated. 1.14482 + * @param onsuccess Callback to be called when success. 1.14483 + * @param onerror Callback to be called when error. 1.14484 + */ 1.14485 + updateContactFieldType1: function(pbr, contact, field, onsuccess, onerror) { 1.14486 + let ICCRecordHelper = this.context.ICCRecordHelper; 1.14487 + 1.14488 + if (field === USIM_PBR_EMAIL) { 1.14489 + ICCRecordHelper.updateEmail(pbr, contact.recordId, contact.email, null, onsuccess, onerror); 1.14490 + } else if (field === USIM_PBR_ANR0) { 1.14491 + let anr = Array.isArray(contact.anr) ? contact.anr[0] : null; 1.14492 + ICCRecordHelper.updateANR(pbr, contact.recordId, anr, null, onsuccess, onerror); 1.14493 + } else { 1.14494 + if (DEBUG) { 1.14495 + this.context.debug("Unsupported field :" + field); 1.14496 + } 1.14497 + onerror(CONTACT_ERR_FIELD_NOT_SUPPORTED); 1.14498 + } 1.14499 + }, 1.14500 + 1.14501 + /** 1.14502 + * Update Type 2 USIM contact fields. 1.14503 + * 1.14504 + * @param pbr The phonebook reference file. 1.14505 + * @param contact The contact needs to be updated. 1.14506 + * @param field Phonebook field to be updated. 1.14507 + * @param onsuccess Callback to be called when success. 1.14508 + * @param onerror Callback to be called when error. 1.14509 + */ 1.14510 + updateContactFieldType2: function(pbr, contact, field, onsuccess, onerror) { 1.14511 + let ICCRecordHelper = this.context.ICCRecordHelper; 1.14512 + 1.14513 + // Case 1 : EF_IAP[adnRecordId] doesn't have a value(0xff) 1.14514 + // Find a free recordId for EF_field 1.14515 + // Update field with that free recordId. 1.14516 + // Update IAP. 1.14517 + // 1.14518 + // Case 2: EF_IAP[adnRecordId] has a value 1.14519 + // update EF_field[iap[field.indexInIAP]] 1.14520 + 1.14521 + let gotIapCb = function gotIapCb(iap) { 1.14522 + let recordId = iap[pbr[field].indexInIAP]; 1.14523 + if (recordId === 0xff) { 1.14524 + // If the value in IAP[index] is 0xff, which means the contact stored on 1.14525 + // the SIM doesn't have the additional attribute (email or anr). 1.14526 + // So if the contact to be updated doesn't have the attribute either, 1.14527 + // we don't have to update it. 1.14528 + if ((field === USIM_PBR_EMAIL && contact.email) || 1.14529 + (field === USIM_PBR_ANR0 && 1.14530 + (Array.isArray(contact.anr) && contact.anr[0]))) { 1.14531 + // Case 1. 1.14532 + this.addContactFieldType2(pbr, contact, field, onsuccess, onerror); 1.14533 + } else { 1.14534 + if (onsuccess) { 1.14535 + onsuccess(); 1.14536 + } 1.14537 + } 1.14538 + return; 1.14539 + } 1.14540 + 1.14541 + // Case 2. 1.14542 + if (field === USIM_PBR_EMAIL) { 1.14543 + ICCRecordHelper.updateEmail(pbr, recordId, contact.email, contact.recordId, onsuccess, onerror); 1.14544 + } else if (field === USIM_PBR_ANR0) { 1.14545 + let anr = Array.isArray(contact.anr) ? contact.anr[0] : null; 1.14546 + ICCRecordHelper.updateANR(pbr, recordId, anr, contact.recordId, onsuccess, onerror); 1.14547 + } else { 1.14548 + if (DEBUG) { 1.14549 + this.context.debug("Unsupported field :" + field); 1.14550 + } 1.14551 + onerror(CONTACT_ERR_FIELD_NOT_SUPPORTED); 1.14552 + } 1.14553 + 1.14554 + }.bind(this); 1.14555 + 1.14556 + ICCRecordHelper.readIAP(pbr.iap.fileId, contact.recordId, gotIapCb, onerror); 1.14557 + }, 1.14558 + 1.14559 + /** 1.14560 + * Add Type 2 USIM contact fields. 1.14561 + * 1.14562 + * @param pbr The phonebook reference file. 1.14563 + * @param contact The contact needs to be updated. 1.14564 + * @param field Phonebook field to be updated. 1.14565 + * @param onsuccess Callback to be called when success. 1.14566 + * @param onerror Callback to be called when error. 1.14567 + */ 1.14568 + addContactFieldType2: function(pbr, contact, field, onsuccess, onerror) { 1.14569 + let ICCRecordHelper = this.context.ICCRecordHelper; 1.14570 + 1.14571 + let successCb = function successCb(recordId) { 1.14572 + let updateCb = function updateCb() { 1.14573 + this.updateContactFieldIndexInIAP(pbr, contact.recordId, field, recordId, onsuccess, onerror); 1.14574 + }.bind(this); 1.14575 + 1.14576 + if (field === USIM_PBR_EMAIL) { 1.14577 + ICCRecordHelper.updateEmail(pbr, recordId, contact.email, contact.recordId, updateCb, onerror); 1.14578 + } else if (field === USIM_PBR_ANR0) { 1.14579 + ICCRecordHelper.updateANR(pbr, recordId, contact.anr[0], contact.recordId, updateCb, onerror); 1.14580 + } 1.14581 + }.bind(this); 1.14582 + 1.14583 + let errorCb = function errorCb(errorMsg) { 1.14584 + if (DEBUG) { 1.14585 + this.context.debug(errorMsg + " USIM field " + field); 1.14586 + } 1.14587 + onerror(errorMsg); 1.14588 + }.bind(this); 1.14589 + 1.14590 + ICCRecordHelper.findFreeRecordId(pbr[field].fileId, successCb, errorCb); 1.14591 + }, 1.14592 + 1.14593 + /** 1.14594 + * Update IAP value. 1.14595 + * 1.14596 + * @param pbr The phonebook reference file. 1.14597 + * @param recordNumber The record identifier of EF_IAP. 1.14598 + * @param field Phonebook field. 1.14599 + * @param value The value of 'field' in IAP. 1.14600 + * @param onsuccess Callback to be called when success. 1.14601 + * @param onerror Callback to be called when error. 1.14602 + * 1.14603 + */ 1.14604 + updateContactFieldIndexInIAP: function(pbr, recordNumber, field, value, onsuccess, onerror) { 1.14605 + let ICCRecordHelper = this.context.ICCRecordHelper; 1.14606 + 1.14607 + let gotIAPCb = function gotIAPCb(iap) { 1.14608 + iap[pbr[field].indexInIAP] = value; 1.14609 + ICCRecordHelper.updateIAP(pbr.iap.fileId, recordNumber, iap, onsuccess, onerror); 1.14610 + }.bind(this); 1.14611 + ICCRecordHelper.readIAP(pbr.iap.fileId, recordNumber, gotIAPCb, onerror); 1.14612 + }, 1.14613 +}; 1.14614 + 1.14615 +/** 1.14616 + * Global stuff. 1.14617 + */ 1.14618 + 1.14619 +function Context(aClientId) { 1.14620 + this.clientId = aClientId; 1.14621 + 1.14622 + this.Buf = new BufObject(this); 1.14623 + this.Buf.init(); 1.14624 + 1.14625 + this.RIL = new RilObject(this); 1.14626 + this.RIL.initRILState(); 1.14627 +} 1.14628 +Context.prototype = { 1.14629 + clientId: null, 1.14630 + Buf: null, 1.14631 + RIL: null, 1.14632 + 1.14633 + debug: function(aMessage) { 1.14634 + GLOBAL.debug("[" + this.clientId + "] " + aMessage); 1.14635 + } 1.14636 +}; 1.14637 + 1.14638 +(function() { 1.14639 + let lazySymbols = [ 1.14640 + "BerTlvHelper", "BitBufferHelper", "CdmaPDUHelper", 1.14641 + "ComprehensionTlvHelper", "GsmPDUHelper", "ICCContactHelper", 1.14642 + "ICCFileHelper", "ICCIOHelper", "ICCPDUHelper", "ICCRecordHelper", 1.14643 + "ICCUtilsHelper", "RuimRecordHelper", "SimRecordHelper", 1.14644 + "StkCommandParamsFactory", "StkProactiveCmdHelper", 1.14645 + ]; 1.14646 + 1.14647 + for (let i = 0; i < lazySymbols.length; i++) { 1.14648 + let symbol = lazySymbols[i]; 1.14649 + Object.defineProperty(Context.prototype, symbol, { 1.14650 + get: function() { 1.14651 + let real = new GLOBAL[symbol + "Object"](this); 1.14652 + Object.defineProperty(this, symbol, { 1.14653 + value: real, 1.14654 + enumerable: true 1.14655 + }); 1.14656 + return real; 1.14657 + }, 1.14658 + configurable: true, 1.14659 + enumerable: true 1.14660 + }); 1.14661 + } 1.14662 +})(); 1.14663 + 1.14664 +let ContextPool = { 1.14665 + _contexts: [], 1.14666 + 1.14667 + handleRilMessage: function(aClientId, aUint8Array) { 1.14668 + let context = this._contexts[aClientId]; 1.14669 + context.Buf.processIncoming(aUint8Array); 1.14670 + }, 1.14671 + 1.14672 + handleChromeMessage: function(aMessage) { 1.14673 + let clientId = aMessage.rilMessageClientId; 1.14674 + if (clientId != null) { 1.14675 + let context = this._contexts[clientId]; 1.14676 + context.RIL.handleChromeMessage(aMessage); 1.14677 + return; 1.14678 + } 1.14679 + 1.14680 + if (DEBUG) debug("Received global chrome message " + JSON.stringify(aMessage)); 1.14681 + let method = this[aMessage.rilMessageType]; 1.14682 + if (typeof method != "function") { 1.14683 + if (DEBUG) { 1.14684 + debug("Don't know what to do"); 1.14685 + } 1.14686 + return; 1.14687 + } 1.14688 + method.call(this, aMessage); 1.14689 + }, 1.14690 + 1.14691 + setInitialOptions: function(aOptions) { 1.14692 + DEBUG = DEBUG_WORKER || aOptions.debug; 1.14693 + RIL_EMERGENCY_NUMBERS = aOptions.rilEmergencyNumbers; 1.14694 + RIL_CELLBROADCAST_DISABLED = aOptions.cellBroadcastDisabled; 1.14695 + RIL_CLIR_MODE = aOptions.clirMode; 1.14696 + 1.14697 + let quirks = aOptions.quirks; 1.14698 + RILQUIRKS_CALLSTATE_EXTRA_UINT32 = quirks.callstateExtraUint32; 1.14699 + RILQUIRKS_V5_LEGACY = quirks.v5Legacy; 1.14700 + RILQUIRKS_REQUEST_USE_DIAL_EMERGENCY_CALL = quirks.requestUseDialEmergencyCall; 1.14701 + RILQUIRKS_SIM_APP_STATE_EXTRA_FIELDS = quirks.simAppStateExtraFields; 1.14702 + RILQUIRKS_EXTRA_UINT32_2ND_CALL = quirks.extraUint2ndCall; 1.14703 + RILQUIRKS_HAVE_QUERY_ICC_LOCK_RETRY_COUNT = quirks.haveQueryIccLockRetryCount; 1.14704 + RILQUIRKS_SEND_STK_PROFILE_DOWNLOAD = quirks.sendStkProfileDownload; 1.14705 + RILQUIRKS_DATA_REGISTRATION_ON_DEMAND = quirks.dataRegistrationOnDemand; 1.14706 + }, 1.14707 + 1.14708 + registerClient: function(aOptions) { 1.14709 + let clientId = aOptions.clientId; 1.14710 + this._contexts[clientId] = new Context(clientId); 1.14711 + }, 1.14712 +}; 1.14713 + 1.14714 +function onRILMessage(aClientId, aUint8Array) { 1.14715 + ContextPool.handleRilMessage(aClientId, aUint8Array); 1.14716 +} 1.14717 + 1.14718 +onmessage = function onmessage(event) { 1.14719 + ContextPool.handleChromeMessage(event.data); 1.14720 +}; 1.14721 + 1.14722 +onerror = function onerror(event) { 1.14723 + if (DEBUG) debug("onerror" + event.message + "\n"); 1.14724 +};