Wed, 31 Dec 2014 06:09:35 +0100
Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.
michael@0 | 1 | /* Copyright 2012 Mozilla Foundation and Mozilla contributors |
michael@0 | 2 | * |
michael@0 | 3 | * Licensed under the Apache License, Version 2.0 (the "License"); |
michael@0 | 4 | * you may not use this file except in compliance with the License. |
michael@0 | 5 | * You may obtain a copy of the License at |
michael@0 | 6 | * |
michael@0 | 7 | * http://www.apache.org/licenses/LICENSE-2.0 |
michael@0 | 8 | * |
michael@0 | 9 | * Unless required by applicable law or agreed to in writing, software |
michael@0 | 10 | * distributed under the License is distributed on an "AS IS" BASIS, |
michael@0 | 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
michael@0 | 12 | * See the License for the specific language governing permissions and |
michael@0 | 13 | * limitations under the License. |
michael@0 | 14 | */ |
michael@0 | 15 | |
michael@0 | 16 | /** |
michael@0 | 17 | * This file implements the RIL worker thread. It communicates with |
michael@0 | 18 | * the main thread to provide a high-level API to the phone's RIL |
michael@0 | 19 | * stack, and with the RIL IPC thread to communicate with the RIL |
michael@0 | 20 | * device itself. These communication channels use message events as |
michael@0 | 21 | * known from Web Workers: |
michael@0 | 22 | * |
michael@0 | 23 | * - postMessage()/"message" events for main thread communication |
michael@0 | 24 | * |
michael@0 | 25 | * - postRILMessage()/"RILMessageEvent" events for RIL IPC thread |
michael@0 | 26 | * communication. |
michael@0 | 27 | * |
michael@0 | 28 | * The two main objects in this file represent individual parts of this |
michael@0 | 29 | * communication chain: |
michael@0 | 30 | * |
michael@0 | 31 | * - RILMessageEvent -> Buf -> RIL -> postMessage() -> nsIRadioInterfaceLayer |
michael@0 | 32 | * - nsIRadioInterfaceLayer -> postMessage() -> RIL -> Buf -> postRILMessage() |
michael@0 | 33 | * |
michael@0 | 34 | * Note: The code below is purposely lean on abstractions to be as lean in |
michael@0 | 35 | * terms of object allocations. As a result, it may look more like C than |
michael@0 | 36 | * JavaScript, and that's intended. |
michael@0 | 37 | */ |
michael@0 | 38 | |
michael@0 | 39 | "use strict"; |
michael@0 | 40 | |
michael@0 | 41 | importScripts("ril_consts.js"); |
michael@0 | 42 | importScripts("resource://gre/modules/workers/require.js"); |
michael@0 | 43 | |
michael@0 | 44 | // set to true in ril_consts.js to see debug messages |
michael@0 | 45 | let DEBUG = DEBUG_WORKER; |
michael@0 | 46 | let GLOBAL = this; |
michael@0 | 47 | |
michael@0 | 48 | if (!this.debug) { |
michael@0 | 49 | // Debugging stub that goes nowhere. |
michael@0 | 50 | this.debug = function debug(message) { |
michael@0 | 51 | dump("RIL Worker: " + message + "\n"); |
michael@0 | 52 | }; |
michael@0 | 53 | } |
michael@0 | 54 | |
michael@0 | 55 | let RIL_CELLBROADCAST_DISABLED; |
michael@0 | 56 | let RIL_CLIR_MODE; |
michael@0 | 57 | let RIL_EMERGENCY_NUMBERS; |
michael@0 | 58 | const DEFAULT_EMERGENCY_NUMBERS = ["112", "911"]; |
michael@0 | 59 | |
michael@0 | 60 | // Timeout value for emergency callback mode. |
michael@0 | 61 | const EMERGENCY_CB_MODE_TIMEOUT_MS = 300000; // 5 mins = 300000 ms. |
michael@0 | 62 | |
michael@0 | 63 | const ICC_MAX_LINEAR_FIXED_RECORDS = 0xfe; |
michael@0 | 64 | |
michael@0 | 65 | // MMI match groups |
michael@0 | 66 | const MMI_MATCH_GROUP_FULL_MMI = 1; |
michael@0 | 67 | const MMI_MATCH_GROUP_MMI_PROCEDURE = 2; |
michael@0 | 68 | const MMI_MATCH_GROUP_SERVICE_CODE = 3; |
michael@0 | 69 | const MMI_MATCH_GROUP_SIA = 5; |
michael@0 | 70 | const MMI_MATCH_GROUP_SIB = 7; |
michael@0 | 71 | const MMI_MATCH_GROUP_SIC = 9; |
michael@0 | 72 | const MMI_MATCH_GROUP_PWD_CONFIRM = 11; |
michael@0 | 73 | const MMI_MATCH_GROUP_DIALING_NUMBER = 12; |
michael@0 | 74 | |
michael@0 | 75 | const MMI_MAX_LENGTH_SHORT_CODE = 2; |
michael@0 | 76 | |
michael@0 | 77 | const MMI_END_OF_USSD = "#"; |
michael@0 | 78 | |
michael@0 | 79 | // Should match the value we set in dom/telephony/TelephonyCommon.h |
michael@0 | 80 | const OUTGOING_PLACEHOLDER_CALL_INDEX = 0xffffffff; |
michael@0 | 81 | |
michael@0 | 82 | let RILQUIRKS_CALLSTATE_EXTRA_UINT32; |
michael@0 | 83 | // This may change at runtime since in RIL v6 and later, we get the version |
michael@0 | 84 | // number via the UNSOLICITED_RIL_CONNECTED parcel. |
michael@0 | 85 | let RILQUIRKS_V5_LEGACY; |
michael@0 | 86 | let RILQUIRKS_REQUEST_USE_DIAL_EMERGENCY_CALL; |
michael@0 | 87 | let RILQUIRKS_SIM_APP_STATE_EXTRA_FIELDS; |
michael@0 | 88 | // Needed for call-waiting on Peak device |
michael@0 | 89 | let RILQUIRKS_EXTRA_UINT32_2ND_CALL; |
michael@0 | 90 | // On the emulator we support querying the number of lock retries |
michael@0 | 91 | let RILQUIRKS_HAVE_QUERY_ICC_LOCK_RETRY_COUNT; |
michael@0 | 92 | |
michael@0 | 93 | // Ril quirk to Send STK Profile Download |
michael@0 | 94 | let RILQUIRKS_SEND_STK_PROFILE_DOWNLOAD; |
michael@0 | 95 | |
michael@0 | 96 | // Ril quirk to attach data registration on demand. |
michael@0 | 97 | let RILQUIRKS_DATA_REGISTRATION_ON_DEMAND; |
michael@0 | 98 | |
michael@0 | 99 | function BufObject(aContext) { |
michael@0 | 100 | this.context = aContext; |
michael@0 | 101 | } |
michael@0 | 102 | BufObject.prototype = { |
michael@0 | 103 | context: null, |
michael@0 | 104 | |
michael@0 | 105 | mToken: 0, |
michael@0 | 106 | mTokenRequestMap: null, |
michael@0 | 107 | |
michael@0 | 108 | init: function() { |
michael@0 | 109 | this._init(); |
michael@0 | 110 | |
michael@0 | 111 | // This gets incremented each time we send out a parcel. |
michael@0 | 112 | this.mToken = 1; |
michael@0 | 113 | |
michael@0 | 114 | // Maps tokens we send out with requests to the request type, so that |
michael@0 | 115 | // when we get a response parcel back, we know what request it was for. |
michael@0 | 116 | this.mTokenRequestMap = new Map(); |
michael@0 | 117 | }, |
michael@0 | 118 | |
michael@0 | 119 | /** |
michael@0 | 120 | * Process one parcel. |
michael@0 | 121 | */ |
michael@0 | 122 | processParcel: function() { |
michael@0 | 123 | let response_type = this.readInt32(); |
michael@0 | 124 | |
michael@0 | 125 | let request_type, options; |
michael@0 | 126 | if (response_type == RESPONSE_TYPE_SOLICITED) { |
michael@0 | 127 | let token = this.readInt32(); |
michael@0 | 128 | let error = this.readInt32(); |
michael@0 | 129 | |
michael@0 | 130 | options = this.mTokenRequestMap.get(token); |
michael@0 | 131 | if (!options) { |
michael@0 | 132 | if (DEBUG) { |
michael@0 | 133 | this.context.debug("Suspicious uninvited request found: " + |
michael@0 | 134 | token + ". Ignored!"); |
michael@0 | 135 | } |
michael@0 | 136 | return; |
michael@0 | 137 | } |
michael@0 | 138 | |
michael@0 | 139 | this.mTokenRequestMap.delete(token); |
michael@0 | 140 | request_type = options.rilRequestType; |
michael@0 | 141 | |
michael@0 | 142 | options.rilRequestError = error; |
michael@0 | 143 | if (DEBUG) { |
michael@0 | 144 | this.context.debug("Solicited response for request type " + request_type + |
michael@0 | 145 | ", token " + token + ", error " + error); |
michael@0 | 146 | } |
michael@0 | 147 | } else if (response_type == RESPONSE_TYPE_UNSOLICITED) { |
michael@0 | 148 | request_type = this.readInt32(); |
michael@0 | 149 | if (DEBUG) { |
michael@0 | 150 | this.context.debug("Unsolicited response for request type " + request_type); |
michael@0 | 151 | } |
michael@0 | 152 | } else { |
michael@0 | 153 | if (DEBUG) { |
michael@0 | 154 | this.context.debug("Unknown response type: " + response_type); |
michael@0 | 155 | } |
michael@0 | 156 | return; |
michael@0 | 157 | } |
michael@0 | 158 | |
michael@0 | 159 | this.context.RIL.handleParcel(request_type, this.readAvailable, options); |
michael@0 | 160 | }, |
michael@0 | 161 | |
michael@0 | 162 | /** |
michael@0 | 163 | * Start a new outgoing parcel. |
michael@0 | 164 | * |
michael@0 | 165 | * @param type |
michael@0 | 166 | * Integer specifying the request type. |
michael@0 | 167 | * @param options [optional] |
michael@0 | 168 | * Object containing information about the request, e.g. the |
michael@0 | 169 | * original main thread message object that led to the RIL request. |
michael@0 | 170 | */ |
michael@0 | 171 | newParcel: function(type, options) { |
michael@0 | 172 | if (DEBUG) this.context.debug("New outgoing parcel of type " + type); |
michael@0 | 173 | |
michael@0 | 174 | // We're going to leave room for the parcel size at the beginning. |
michael@0 | 175 | this.outgoingIndex = this.PARCEL_SIZE_SIZE; |
michael@0 | 176 | this.writeInt32(type); |
michael@0 | 177 | this.writeInt32(this.mToken); |
michael@0 | 178 | |
michael@0 | 179 | if (!options) { |
michael@0 | 180 | options = {}; |
michael@0 | 181 | } |
michael@0 | 182 | options.rilRequestType = type; |
michael@0 | 183 | options.rilRequestError = null; |
michael@0 | 184 | this.mTokenRequestMap.set(this.mToken, options); |
michael@0 | 185 | this.mToken++; |
michael@0 | 186 | return this.mToken; |
michael@0 | 187 | }, |
michael@0 | 188 | |
michael@0 | 189 | simpleRequest: function(type, options) { |
michael@0 | 190 | this.newParcel(type, options); |
michael@0 | 191 | this.sendParcel(); |
michael@0 | 192 | }, |
michael@0 | 193 | |
michael@0 | 194 | onSendParcel: function(parcel) { |
michael@0 | 195 | postRILMessage(this.context.clientId, parcel); |
michael@0 | 196 | } |
michael@0 | 197 | }; |
michael@0 | 198 | |
michael@0 | 199 | (function() { |
michael@0 | 200 | let base = require("resource://gre/modules/workers/worker_buf.js").Buf; |
michael@0 | 201 | for (let p in base) { |
michael@0 | 202 | BufObject.prototype[p] = base[p]; |
michael@0 | 203 | } |
michael@0 | 204 | })(); |
michael@0 | 205 | |
michael@0 | 206 | /** |
michael@0 | 207 | * The RIL state machine. |
michael@0 | 208 | * |
michael@0 | 209 | * This object communicates with rild via parcels and with the main thread |
michael@0 | 210 | * via post messages. It maintains state about the radio, ICC, calls, etc. |
michael@0 | 211 | * and acts upon state changes accordingly. |
michael@0 | 212 | */ |
michael@0 | 213 | function RilObject(aContext) { |
michael@0 | 214 | this.context = aContext; |
michael@0 | 215 | |
michael@0 | 216 | this.currentCalls = {}; |
michael@0 | 217 | this.currentConference = {state: null, participants: {}}; |
michael@0 | 218 | this.currentDataCalls = {}; |
michael@0 | 219 | this._pendingSentSmsMap = {}; |
michael@0 | 220 | this.pendingNetworkType = {}; |
michael@0 | 221 | this._receivedSmsCbPagesMap = {}; |
michael@0 | 222 | |
michael@0 | 223 | // Init properties that are only initialized once. |
michael@0 | 224 | this.v5Legacy = RILQUIRKS_V5_LEGACY; |
michael@0 | 225 | this.cellBroadcastDisabled = RIL_CELLBROADCAST_DISABLED; |
michael@0 | 226 | this.clirMode = RIL_CLIR_MODE; |
michael@0 | 227 | } |
michael@0 | 228 | RilObject.prototype = { |
michael@0 | 229 | context: null, |
michael@0 | 230 | |
michael@0 | 231 | v5Legacy: null, |
michael@0 | 232 | |
michael@0 | 233 | /** |
michael@0 | 234 | * Valid calls. |
michael@0 | 235 | */ |
michael@0 | 236 | currentCalls: null, |
michael@0 | 237 | |
michael@0 | 238 | /** |
michael@0 | 239 | * Existing conference call and its participants. |
michael@0 | 240 | */ |
michael@0 | 241 | currentConference: null, |
michael@0 | 242 | |
michael@0 | 243 | /** |
michael@0 | 244 | * Existing data calls. |
michael@0 | 245 | */ |
michael@0 | 246 | currentDataCalls: null, |
michael@0 | 247 | |
michael@0 | 248 | /** |
michael@0 | 249 | * Outgoing messages waiting for SMS-STATUS-REPORT. |
michael@0 | 250 | */ |
michael@0 | 251 | _pendingSentSmsMap: null, |
michael@0 | 252 | |
michael@0 | 253 | /** |
michael@0 | 254 | * Index of the RIL_PREFERRED_NETWORK_TYPE_TO_GECKO. Its value should be |
michael@0 | 255 | * preserved over rild reset. |
michael@0 | 256 | */ |
michael@0 | 257 | preferredNetworkType: null, |
michael@0 | 258 | |
michael@0 | 259 | /** |
michael@0 | 260 | * Marker object. |
michael@0 | 261 | */ |
michael@0 | 262 | pendingNetworkType: null, |
michael@0 | 263 | |
michael@0 | 264 | /** |
michael@0 | 265 | * Global Cell Broadcast switch. |
michael@0 | 266 | */ |
michael@0 | 267 | cellBroadcastDisabled: false, |
michael@0 | 268 | |
michael@0 | 269 | /** |
michael@0 | 270 | * Global CLIR mode settings. |
michael@0 | 271 | */ |
michael@0 | 272 | clirMode: CLIR_DEFAULT, |
michael@0 | 273 | |
michael@0 | 274 | /** |
michael@0 | 275 | * Parsed Cell Broadcast search lists. |
michael@0 | 276 | * cellBroadcastConfigs.MMI should be preserved over rild reset. |
michael@0 | 277 | */ |
michael@0 | 278 | cellBroadcastConfigs: null, |
michael@0 | 279 | mergedCellBroadcastConfig: null, |
michael@0 | 280 | |
michael@0 | 281 | _receivedSmsCbPagesMap: null, |
michael@0 | 282 | |
michael@0 | 283 | initRILState: function() { |
michael@0 | 284 | /** |
michael@0 | 285 | * One of the RADIO_STATE_* constants. |
michael@0 | 286 | */ |
michael@0 | 287 | this.radioState = GECKO_RADIOSTATE_UNAVAILABLE; |
michael@0 | 288 | |
michael@0 | 289 | /** |
michael@0 | 290 | * True if we are on a CDMA phone. |
michael@0 | 291 | */ |
michael@0 | 292 | this._isCdma = false; |
michael@0 | 293 | |
michael@0 | 294 | /** |
michael@0 | 295 | * True if we are in emergency callback mode. |
michael@0 | 296 | */ |
michael@0 | 297 | this._isInEmergencyCbMode = false; |
michael@0 | 298 | |
michael@0 | 299 | /** |
michael@0 | 300 | * Set when radio is ready but radio tech is unknown. That is, we are |
michael@0 | 301 | * waiting for REQUEST_VOICE_RADIO_TECH |
michael@0 | 302 | */ |
michael@0 | 303 | this._waitingRadioTech = false; |
michael@0 | 304 | |
michael@0 | 305 | /** |
michael@0 | 306 | * ICC status. Keeps a reference of the data response to the |
michael@0 | 307 | * getICCStatus request. |
michael@0 | 308 | */ |
michael@0 | 309 | this.iccStatus = null; |
michael@0 | 310 | |
michael@0 | 311 | /** |
michael@0 | 312 | * Card state |
michael@0 | 313 | */ |
michael@0 | 314 | this.cardState = GECKO_CARDSTATE_UNINITIALIZED; |
michael@0 | 315 | |
michael@0 | 316 | /** |
michael@0 | 317 | * Strings |
michael@0 | 318 | */ |
michael@0 | 319 | this.IMEI = null; |
michael@0 | 320 | this.IMEISV = null; |
michael@0 | 321 | this.ESN = null; |
michael@0 | 322 | this.MEID = null; |
michael@0 | 323 | this.SMSC = null; |
michael@0 | 324 | |
michael@0 | 325 | /** |
michael@0 | 326 | * ICC information that is not exposed to Gaia. |
michael@0 | 327 | */ |
michael@0 | 328 | this.iccInfoPrivate = {}; |
michael@0 | 329 | |
michael@0 | 330 | /** |
michael@0 | 331 | * ICC information, such as MSISDN, MCC, MNC, SPN...etc. |
michael@0 | 332 | */ |
michael@0 | 333 | this.iccInfo = {}; |
michael@0 | 334 | |
michael@0 | 335 | /** |
michael@0 | 336 | * CDMA specific information. ex. CDMA Network ID, CDMA System ID... etc. |
michael@0 | 337 | */ |
michael@0 | 338 | this.cdmaHome = null; |
michael@0 | 339 | |
michael@0 | 340 | /** |
michael@0 | 341 | * Application identification for apps in ICC. |
michael@0 | 342 | */ |
michael@0 | 343 | this.aid = null; |
michael@0 | 344 | |
michael@0 | 345 | /** |
michael@0 | 346 | * Application type for apps in ICC. |
michael@0 | 347 | */ |
michael@0 | 348 | this.appType = null; |
michael@0 | 349 | |
michael@0 | 350 | this.networkSelectionMode = null; |
michael@0 | 351 | |
michael@0 | 352 | this.voiceRegistrationState = {}; |
michael@0 | 353 | this.dataRegistrationState = {}; |
michael@0 | 354 | |
michael@0 | 355 | /** |
michael@0 | 356 | * List of strings identifying the network operator. |
michael@0 | 357 | */ |
michael@0 | 358 | this.operator = null; |
michael@0 | 359 | |
michael@0 | 360 | /** |
michael@0 | 361 | * String containing the baseband version. |
michael@0 | 362 | */ |
michael@0 | 363 | this.basebandVersion = null; |
michael@0 | 364 | |
michael@0 | 365 | // Clean up this.currentCalls: rild might have restarted. |
michael@0 | 366 | for each (let currentCall in this.currentCalls) { |
michael@0 | 367 | delete this.currentCalls[currentCall.callIndex]; |
michael@0 | 368 | this._handleDisconnectedCall(currentCall); |
michael@0 | 369 | } |
michael@0 | 370 | |
michael@0 | 371 | // Deactivate this.currentDataCalls: rild might have restarted. |
michael@0 | 372 | for each (let datacall in this.currentDataCalls) { |
michael@0 | 373 | this.deactivateDataCall(datacall); |
michael@0 | 374 | } |
michael@0 | 375 | |
michael@0 | 376 | // Don't clean up this._pendingSentSmsMap |
michael@0 | 377 | // because on rild restart: we may continue with the pending segments. |
michael@0 | 378 | |
michael@0 | 379 | /** |
michael@0 | 380 | * Whether or not the multiple requests in requestNetworkInfo() are currently |
michael@0 | 381 | * being processed |
michael@0 | 382 | */ |
michael@0 | 383 | this._processingNetworkInfo = false; |
michael@0 | 384 | |
michael@0 | 385 | /** |
michael@0 | 386 | * Multiple requestNetworkInfo() in a row before finishing the first |
michael@0 | 387 | * request, hence we need to fire requestNetworkInfo() again after |
michael@0 | 388 | * gathering all necessary stuffs. This is to make sure that ril_worker |
michael@0 | 389 | * gets precise network information. |
michael@0 | 390 | */ |
michael@0 | 391 | this._needRepollNetworkInfo = false; |
michael@0 | 392 | |
michael@0 | 393 | /** |
michael@0 | 394 | * Pending messages to be send in batch from requestNetworkInfo() |
michael@0 | 395 | */ |
michael@0 | 396 | this._pendingNetworkInfo = {rilMessageType: "networkinfochanged"}; |
michael@0 | 397 | |
michael@0 | 398 | /** |
michael@0 | 399 | * USSD session flag. |
michael@0 | 400 | * Only one USSD session may exist at a time, and the session is assumed |
michael@0 | 401 | * to exist until: |
michael@0 | 402 | * a) There's a call to cancelUSSD() |
michael@0 | 403 | * b) The implementation sends a UNSOLICITED_ON_USSD with a type code |
michael@0 | 404 | * of "0" (USSD-Notify/no further action) or "2" (session terminated) |
michael@0 | 405 | */ |
michael@0 | 406 | this._ussdSession = null; |
michael@0 | 407 | |
michael@0 | 408 | /** |
michael@0 | 409 | * Regular expresion to parse MMI codes. |
michael@0 | 410 | */ |
michael@0 | 411 | this._mmiRegExp = null; |
michael@0 | 412 | |
michael@0 | 413 | /** |
michael@0 | 414 | * Cell Broadcast Search Lists. |
michael@0 | 415 | */ |
michael@0 | 416 | let cbmmi = this.cellBroadcastConfigs && this.cellBroadcastConfigs.MMI; |
michael@0 | 417 | this.cellBroadcastConfigs = { |
michael@0 | 418 | MMI: cbmmi || null |
michael@0 | 419 | }; |
michael@0 | 420 | this.mergedCellBroadcastConfig = null; |
michael@0 | 421 | }, |
michael@0 | 422 | |
michael@0 | 423 | /** |
michael@0 | 424 | * Parse an integer from a string, falling back to a default value |
michael@0 | 425 | * if the the provided value is not a string or does not contain a valid |
michael@0 | 426 | * number. |
michael@0 | 427 | * |
michael@0 | 428 | * @param string |
michael@0 | 429 | * String to be parsed. |
michael@0 | 430 | * @param defaultValue [optional] |
michael@0 | 431 | * Default value to be used. |
michael@0 | 432 | * @param radix [optional] |
michael@0 | 433 | * A number that represents the numeral system to be used. Default 10. |
michael@0 | 434 | */ |
michael@0 | 435 | parseInt: function(string, defaultValue, radix) { |
michael@0 | 436 | let number = parseInt(string, radix || 10); |
michael@0 | 437 | if (!isNaN(number)) { |
michael@0 | 438 | return number; |
michael@0 | 439 | } |
michael@0 | 440 | if (defaultValue === undefined) { |
michael@0 | 441 | defaultValue = null; |
michael@0 | 442 | } |
michael@0 | 443 | return defaultValue; |
michael@0 | 444 | }, |
michael@0 | 445 | |
michael@0 | 446 | |
michael@0 | 447 | /** |
michael@0 | 448 | * Outgoing requests to the RIL. These can be triggered from the |
michael@0 | 449 | * main thread via messages that look like this: |
michael@0 | 450 | * |
michael@0 | 451 | * {rilMessageType: "methodName", |
michael@0 | 452 | * extra: "parameters", |
michael@0 | 453 | * go: "here"} |
michael@0 | 454 | * |
michael@0 | 455 | * So if one of the following methods takes arguments, it takes only one, |
michael@0 | 456 | * an object, which then contains all of the parameters as attributes. |
michael@0 | 457 | * The "@param" documentation is to be interpreted accordingly. |
michael@0 | 458 | */ |
michael@0 | 459 | |
michael@0 | 460 | /** |
michael@0 | 461 | * Retrieve the ICC's status. |
michael@0 | 462 | */ |
michael@0 | 463 | getICCStatus: function() { |
michael@0 | 464 | this.context.Buf.simpleRequest(REQUEST_GET_SIM_STATUS); |
michael@0 | 465 | }, |
michael@0 | 466 | |
michael@0 | 467 | /** |
michael@0 | 468 | * Helper function for unlocking ICC locks. |
michael@0 | 469 | */ |
michael@0 | 470 | iccUnlockCardLock: function(options) { |
michael@0 | 471 | switch (options.lockType) { |
michael@0 | 472 | case GECKO_CARDLOCK_PIN: |
michael@0 | 473 | this.enterICCPIN(options); |
michael@0 | 474 | break; |
michael@0 | 475 | case GECKO_CARDLOCK_PIN2: |
michael@0 | 476 | this.enterICCPIN2(options); |
michael@0 | 477 | break; |
michael@0 | 478 | case GECKO_CARDLOCK_PUK: |
michael@0 | 479 | this.enterICCPUK(options); |
michael@0 | 480 | break; |
michael@0 | 481 | case GECKO_CARDLOCK_PUK2: |
michael@0 | 482 | this.enterICCPUK2(options); |
michael@0 | 483 | break; |
michael@0 | 484 | case GECKO_CARDLOCK_NCK: |
michael@0 | 485 | case GECKO_CARDLOCK_NCK1: |
michael@0 | 486 | case GECKO_CARDLOCK_NCK2: |
michael@0 | 487 | case GECKO_CARDLOCK_HNCK: |
michael@0 | 488 | case GECKO_CARDLOCK_CCK: |
michael@0 | 489 | case GECKO_CARDLOCK_SPCK: |
michael@0 | 490 | case GECKO_CARDLOCK_RCCK: // Fall through. |
michael@0 | 491 | case GECKO_CARDLOCK_RSPCK: { |
michael@0 | 492 | let type = GECKO_PERSO_LOCK_TO_CARD_PERSO_LOCK[options.lockType]; |
michael@0 | 493 | this.enterDepersonalization(type, options.pin, options); |
michael@0 | 494 | break; |
michael@0 | 495 | } |
michael@0 | 496 | case GECKO_CARDLOCK_NCK_PUK: |
michael@0 | 497 | case GECKO_CARDLOCK_NCK1_PUK: |
michael@0 | 498 | case GECKO_CARDLOCK_NCK2_PUK: |
michael@0 | 499 | case GECKO_CARDLOCK_HNCK_PUK: |
michael@0 | 500 | case GECKO_CARDLOCK_CCK_PUK: |
michael@0 | 501 | case GECKO_CARDLOCK_SPCK_PUK: |
michael@0 | 502 | case GECKO_CARDLOCK_RCCK_PUK: // Fall through. |
michael@0 | 503 | case GECKO_CARDLOCK_RSPCK_PUK: { |
michael@0 | 504 | let type = GECKO_PERSO_LOCK_TO_CARD_PERSO_LOCK[options.lockType]; |
michael@0 | 505 | this.enterDepersonalization(type, options.puk, options); |
michael@0 | 506 | break; |
michael@0 | 507 | } |
michael@0 | 508 | default: |
michael@0 | 509 | options.errorMsg = "Unsupported Card Lock."; |
michael@0 | 510 | options.success = false; |
michael@0 | 511 | this.sendChromeMessage(options); |
michael@0 | 512 | } |
michael@0 | 513 | }, |
michael@0 | 514 | |
michael@0 | 515 | /** |
michael@0 | 516 | * Enter a PIN to unlock the ICC. |
michael@0 | 517 | * |
michael@0 | 518 | * @param pin |
michael@0 | 519 | * String containing the PIN. |
michael@0 | 520 | * @param [optional] aid |
michael@0 | 521 | * AID value. |
michael@0 | 522 | */ |
michael@0 | 523 | enterICCPIN: function(options) { |
michael@0 | 524 | let Buf = this.context.Buf; |
michael@0 | 525 | Buf.newParcel(REQUEST_ENTER_SIM_PIN, options); |
michael@0 | 526 | Buf.writeInt32(this.v5Legacy ? 1 : 2); |
michael@0 | 527 | Buf.writeString(options.pin); |
michael@0 | 528 | if (!this.v5Legacy) { |
michael@0 | 529 | Buf.writeString(options.aid || this.aid); |
michael@0 | 530 | } |
michael@0 | 531 | Buf.sendParcel(); |
michael@0 | 532 | }, |
michael@0 | 533 | |
michael@0 | 534 | /** |
michael@0 | 535 | * Enter a PIN2 to unlock the ICC. |
michael@0 | 536 | * |
michael@0 | 537 | * @param pin |
michael@0 | 538 | * String containing the PIN2. |
michael@0 | 539 | * @param [optional] aid |
michael@0 | 540 | * AID value. |
michael@0 | 541 | */ |
michael@0 | 542 | enterICCPIN2: function(options) { |
michael@0 | 543 | let Buf = this.context.Buf; |
michael@0 | 544 | Buf.newParcel(REQUEST_ENTER_SIM_PIN2, options); |
michael@0 | 545 | Buf.writeInt32(this.v5Legacy ? 1 : 2); |
michael@0 | 546 | Buf.writeString(options.pin); |
michael@0 | 547 | if (!this.v5Legacy) { |
michael@0 | 548 | Buf.writeString(options.aid || this.aid); |
michael@0 | 549 | } |
michael@0 | 550 | Buf.sendParcel(); |
michael@0 | 551 | }, |
michael@0 | 552 | |
michael@0 | 553 | /** |
michael@0 | 554 | * Requests a network personalization be deactivated. |
michael@0 | 555 | * |
michael@0 | 556 | * @param type |
michael@0 | 557 | * Integer indicating the network personalization be deactivated. |
michael@0 | 558 | * @param password |
michael@0 | 559 | * String containing the password. |
michael@0 | 560 | */ |
michael@0 | 561 | enterDepersonalization: function(type, password, options) { |
michael@0 | 562 | let Buf = this.context.Buf; |
michael@0 | 563 | Buf.newParcel(REQUEST_ENTER_NETWORK_DEPERSONALIZATION_CODE, options); |
michael@0 | 564 | Buf.writeInt32(type); |
michael@0 | 565 | Buf.writeString(password); |
michael@0 | 566 | Buf.sendParcel(); |
michael@0 | 567 | }, |
michael@0 | 568 | |
michael@0 | 569 | /** |
michael@0 | 570 | * Helper function for changing ICC locks. |
michael@0 | 571 | */ |
michael@0 | 572 | iccSetCardLock: function(options) { |
michael@0 | 573 | if (options.newPin !== undefined) { // Change PIN lock. |
michael@0 | 574 | switch (options.lockType) { |
michael@0 | 575 | case GECKO_CARDLOCK_PIN: |
michael@0 | 576 | this.changeICCPIN(options); |
michael@0 | 577 | break; |
michael@0 | 578 | case GECKO_CARDLOCK_PIN2: |
michael@0 | 579 | this.changeICCPIN2(options); |
michael@0 | 580 | break; |
michael@0 | 581 | default: |
michael@0 | 582 | options.errorMsg = "Unsupported Card Lock."; |
michael@0 | 583 | options.success = false; |
michael@0 | 584 | this.sendChromeMessage(options); |
michael@0 | 585 | } |
michael@0 | 586 | } else { // Enable/Disable lock. |
michael@0 | 587 | switch (options.lockType) { |
michael@0 | 588 | case GECKO_CARDLOCK_PIN: |
michael@0 | 589 | options.facility = ICC_CB_FACILITY_SIM; |
michael@0 | 590 | options.password = options.pin; |
michael@0 | 591 | break; |
michael@0 | 592 | case GECKO_CARDLOCK_FDN: |
michael@0 | 593 | options.facility = ICC_CB_FACILITY_FDN; |
michael@0 | 594 | options.password = options.pin2; |
michael@0 | 595 | break; |
michael@0 | 596 | default: |
michael@0 | 597 | options.errorMsg = "Unsupported Card Lock."; |
michael@0 | 598 | options.success = false; |
michael@0 | 599 | this.sendChromeMessage(options); |
michael@0 | 600 | return; |
michael@0 | 601 | } |
michael@0 | 602 | options.enabled = options.enabled; |
michael@0 | 603 | options.serviceClass = ICC_SERVICE_CLASS_VOICE | |
michael@0 | 604 | ICC_SERVICE_CLASS_DATA | |
michael@0 | 605 | ICC_SERVICE_CLASS_FAX; |
michael@0 | 606 | this.setICCFacilityLock(options); |
michael@0 | 607 | } |
michael@0 | 608 | }, |
michael@0 | 609 | |
michael@0 | 610 | /** |
michael@0 | 611 | * Change the current ICC PIN number. |
michael@0 | 612 | * |
michael@0 | 613 | * @param pin |
michael@0 | 614 | * String containing the old PIN value |
michael@0 | 615 | * @param newPin |
michael@0 | 616 | * String containing the new PIN value |
michael@0 | 617 | * @param [optional] aid |
michael@0 | 618 | * AID value. |
michael@0 | 619 | */ |
michael@0 | 620 | changeICCPIN: function(options) { |
michael@0 | 621 | let Buf = this.context.Buf; |
michael@0 | 622 | Buf.newParcel(REQUEST_CHANGE_SIM_PIN, options); |
michael@0 | 623 | Buf.writeInt32(this.v5Legacy ? 2 : 3); |
michael@0 | 624 | Buf.writeString(options.pin); |
michael@0 | 625 | Buf.writeString(options.newPin); |
michael@0 | 626 | if (!this.v5Legacy) { |
michael@0 | 627 | Buf.writeString(options.aid || this.aid); |
michael@0 | 628 | } |
michael@0 | 629 | Buf.sendParcel(); |
michael@0 | 630 | }, |
michael@0 | 631 | |
michael@0 | 632 | /** |
michael@0 | 633 | * Change the current ICC PIN2 number. |
michael@0 | 634 | * |
michael@0 | 635 | * @param pin |
michael@0 | 636 | * String containing the old PIN2 value |
michael@0 | 637 | * @param newPin |
michael@0 | 638 | * String containing the new PIN2 value |
michael@0 | 639 | * @param [optional] aid |
michael@0 | 640 | * AID value. |
michael@0 | 641 | */ |
michael@0 | 642 | changeICCPIN2: function(options) { |
michael@0 | 643 | let Buf = this.context.Buf; |
michael@0 | 644 | Buf.newParcel(REQUEST_CHANGE_SIM_PIN2, options); |
michael@0 | 645 | Buf.writeInt32(this.v5Legacy ? 2 : 3); |
michael@0 | 646 | Buf.writeString(options.pin); |
michael@0 | 647 | Buf.writeString(options.newPin); |
michael@0 | 648 | if (!this.v5Legacy) { |
michael@0 | 649 | Buf.writeString(options.aid || this.aid); |
michael@0 | 650 | } |
michael@0 | 651 | Buf.sendParcel(); |
michael@0 | 652 | }, |
michael@0 | 653 | /** |
michael@0 | 654 | * Supplies ICC PUK and a new PIN to unlock the ICC. |
michael@0 | 655 | * |
michael@0 | 656 | * @param puk |
michael@0 | 657 | * String containing the PUK value. |
michael@0 | 658 | * @param newPin |
michael@0 | 659 | * String containing the new PIN value. |
michael@0 | 660 | * @param [optional] aid |
michael@0 | 661 | * AID value. |
michael@0 | 662 | */ |
michael@0 | 663 | enterICCPUK: function(options) { |
michael@0 | 664 | let Buf = this.context.Buf; |
michael@0 | 665 | Buf.newParcel(REQUEST_ENTER_SIM_PUK, options); |
michael@0 | 666 | Buf.writeInt32(this.v5Legacy ? 2 : 3); |
michael@0 | 667 | Buf.writeString(options.puk); |
michael@0 | 668 | Buf.writeString(options.newPin); |
michael@0 | 669 | if (!this.v5Legacy) { |
michael@0 | 670 | Buf.writeString(options.aid || this.aid); |
michael@0 | 671 | } |
michael@0 | 672 | Buf.sendParcel(); |
michael@0 | 673 | }, |
michael@0 | 674 | |
michael@0 | 675 | /** |
michael@0 | 676 | * Supplies ICC PUK2 and a new PIN2 to unlock the ICC. |
michael@0 | 677 | * |
michael@0 | 678 | * @param puk |
michael@0 | 679 | * String containing the PUK2 value. |
michael@0 | 680 | * @param newPin |
michael@0 | 681 | * String containing the new PIN2 value. |
michael@0 | 682 | * @param [optional] aid |
michael@0 | 683 | * AID value. |
michael@0 | 684 | */ |
michael@0 | 685 | enterICCPUK2: function(options) { |
michael@0 | 686 | let Buf = this.context.Buf; |
michael@0 | 687 | Buf.newParcel(REQUEST_ENTER_SIM_PUK2, options); |
michael@0 | 688 | Buf.writeInt32(this.v5Legacy ? 2 : 3); |
michael@0 | 689 | Buf.writeString(options.puk); |
michael@0 | 690 | Buf.writeString(options.newPin); |
michael@0 | 691 | if (!this.v5Legacy) { |
michael@0 | 692 | Buf.writeString(options.aid || this.aid); |
michael@0 | 693 | } |
michael@0 | 694 | Buf.sendParcel(); |
michael@0 | 695 | }, |
michael@0 | 696 | |
michael@0 | 697 | /** |
michael@0 | 698 | * Helper function for fetching the state of ICC locks. |
michael@0 | 699 | */ |
michael@0 | 700 | iccGetCardLockState: function(options) { |
michael@0 | 701 | switch (options.lockType) { |
michael@0 | 702 | case GECKO_CARDLOCK_PIN: |
michael@0 | 703 | options.facility = ICC_CB_FACILITY_SIM; |
michael@0 | 704 | break; |
michael@0 | 705 | case GECKO_CARDLOCK_FDN: |
michael@0 | 706 | options.facility = ICC_CB_FACILITY_FDN; |
michael@0 | 707 | break; |
michael@0 | 708 | default: |
michael@0 | 709 | options.errorMsg = "Unsupported Card Lock."; |
michael@0 | 710 | options.success = false; |
michael@0 | 711 | this.sendChromeMessage(options); |
michael@0 | 712 | return; |
michael@0 | 713 | } |
michael@0 | 714 | |
michael@0 | 715 | options.password = ""; // For query no need to provide pin. |
michael@0 | 716 | options.serviceClass = ICC_SERVICE_CLASS_VOICE | |
michael@0 | 717 | ICC_SERVICE_CLASS_DATA | |
michael@0 | 718 | ICC_SERVICE_CLASS_FAX; |
michael@0 | 719 | this.queryICCFacilityLock(options); |
michael@0 | 720 | }, |
michael@0 | 721 | |
michael@0 | 722 | /** |
michael@0 | 723 | * Helper function for fetching the number of unlock retries of ICC locks. |
michael@0 | 724 | * |
michael@0 | 725 | * We only query the retry count when we're on the emulator. The phones do |
michael@0 | 726 | * not support the request id and their rild doesn't return an error. |
michael@0 | 727 | */ |
michael@0 | 728 | iccGetCardLockRetryCount: function(options) { |
michael@0 | 729 | var selCode = { |
michael@0 | 730 | pin: ICC_SEL_CODE_SIM_PIN, |
michael@0 | 731 | puk: ICC_SEL_CODE_SIM_PUK, |
michael@0 | 732 | pin2: ICC_SEL_CODE_SIM_PIN2, |
michael@0 | 733 | puk2: ICC_SEL_CODE_SIM_PUK2, |
michael@0 | 734 | nck: ICC_SEL_CODE_PH_NET_PIN, |
michael@0 | 735 | cck: ICC_SEL_CODE_PH_CORP_PIN, |
michael@0 | 736 | spck: ICC_SEL_CODE_PH_SP_PIN |
michael@0 | 737 | }; |
michael@0 | 738 | |
michael@0 | 739 | if (typeof(selCode[options.lockType]) === 'undefined') { |
michael@0 | 740 | /* unknown lock type */ |
michael@0 | 741 | options.errorMsg = GECKO_ERROR_GENERIC_FAILURE; |
michael@0 | 742 | options.success = false; |
michael@0 | 743 | this.sendChromeMessage(options); |
michael@0 | 744 | return; |
michael@0 | 745 | } |
michael@0 | 746 | |
michael@0 | 747 | if (RILQUIRKS_HAVE_QUERY_ICC_LOCK_RETRY_COUNT) { |
michael@0 | 748 | /* Only the emulator supports this request, ... */ |
michael@0 | 749 | options.selCode = selCode[options.lockType]; |
michael@0 | 750 | this.queryICCLockRetryCount(options); |
michael@0 | 751 | } else { |
michael@0 | 752 | /* ... while the phones do not. */ |
michael@0 | 753 | options.errorMsg = GECKO_ERROR_REQUEST_NOT_SUPPORTED; |
michael@0 | 754 | options.success = false; |
michael@0 | 755 | this.sendChromeMessage(options); |
michael@0 | 756 | } |
michael@0 | 757 | }, |
michael@0 | 758 | |
michael@0 | 759 | /** |
michael@0 | 760 | * Query ICC lock retry count. |
michael@0 | 761 | * |
michael@0 | 762 | * @param selCode |
michael@0 | 763 | * One of ICC_SEL_CODE_*. |
michael@0 | 764 | * @param serviceClass |
michael@0 | 765 | * One of ICC_SERVICE_CLASS_*. |
michael@0 | 766 | */ |
michael@0 | 767 | queryICCLockRetryCount: function(options) { |
michael@0 | 768 | let Buf = this.context.Buf; |
michael@0 | 769 | Buf.newParcel(REQUEST_GET_UNLOCK_RETRY_COUNT, options); |
michael@0 | 770 | Buf.writeInt32(1); |
michael@0 | 771 | Buf.writeString(options.selCode); |
michael@0 | 772 | Buf.sendParcel(); |
michael@0 | 773 | }, |
michael@0 | 774 | |
michael@0 | 775 | /** |
michael@0 | 776 | * Query ICC facility lock. |
michael@0 | 777 | * |
michael@0 | 778 | * @param facility |
michael@0 | 779 | * One of ICC_CB_FACILITY_*. |
michael@0 | 780 | * @param password |
michael@0 | 781 | * Password for the facility, or "" if not required. |
michael@0 | 782 | * @param serviceClass |
michael@0 | 783 | * One of ICC_SERVICE_CLASS_*. |
michael@0 | 784 | * @param [optional] aid |
michael@0 | 785 | * AID value. |
michael@0 | 786 | */ |
michael@0 | 787 | queryICCFacilityLock: function(options) { |
michael@0 | 788 | let Buf = this.context.Buf; |
michael@0 | 789 | Buf.newParcel(REQUEST_QUERY_FACILITY_LOCK, options); |
michael@0 | 790 | Buf.writeInt32(this.v5Legacy ? 3 : 4); |
michael@0 | 791 | Buf.writeString(options.facility); |
michael@0 | 792 | Buf.writeString(options.password); |
michael@0 | 793 | Buf.writeString(options.serviceClass.toString()); |
michael@0 | 794 | if (!this.v5Legacy) { |
michael@0 | 795 | Buf.writeString(options.aid || this.aid); |
michael@0 | 796 | } |
michael@0 | 797 | Buf.sendParcel(); |
michael@0 | 798 | }, |
michael@0 | 799 | |
michael@0 | 800 | /** |
michael@0 | 801 | * Set ICC facility lock. |
michael@0 | 802 | * |
michael@0 | 803 | * @param facility |
michael@0 | 804 | * One of ICC_CB_FACILITY_*. |
michael@0 | 805 | * @param enabled |
michael@0 | 806 | * true to enable, false to disable. |
michael@0 | 807 | * @param password |
michael@0 | 808 | * Password for the facility, or "" if not required. |
michael@0 | 809 | * @param serviceClass |
michael@0 | 810 | * One of ICC_SERVICE_CLASS_*. |
michael@0 | 811 | * @param [optional] aid |
michael@0 | 812 | * AID value. |
michael@0 | 813 | */ |
michael@0 | 814 | setICCFacilityLock: function(options) { |
michael@0 | 815 | let Buf = this.context.Buf; |
michael@0 | 816 | Buf.newParcel(REQUEST_SET_FACILITY_LOCK, options); |
michael@0 | 817 | Buf.writeInt32(this.v5Legacy ? 4 : 5); |
michael@0 | 818 | Buf.writeString(options.facility); |
michael@0 | 819 | Buf.writeString(options.enabled ? "1" : "0"); |
michael@0 | 820 | Buf.writeString(options.password); |
michael@0 | 821 | Buf.writeString(options.serviceClass.toString()); |
michael@0 | 822 | if (!this.v5Legacy) { |
michael@0 | 823 | Buf.writeString(options.aid || this.aid); |
michael@0 | 824 | } |
michael@0 | 825 | Buf.sendParcel(); |
michael@0 | 826 | }, |
michael@0 | 827 | |
michael@0 | 828 | /** |
michael@0 | 829 | * Request an ICC I/O operation. |
michael@0 | 830 | * |
michael@0 | 831 | * See TS 27.007 "restricted SIM" operation, "AT Command +CRSM". |
michael@0 | 832 | * The sequence is in the same order as how libril reads this parcel, |
michael@0 | 833 | * see the struct RIL_SIM_IO_v5 or RIL_SIM_IO_v6 defined in ril.h |
michael@0 | 834 | * |
michael@0 | 835 | * @param command |
michael@0 | 836 | * The I/O command, one of the ICC_COMMAND_* constants. |
michael@0 | 837 | * @param fileId |
michael@0 | 838 | * The file to operate on, one of the ICC_EF_* constants. |
michael@0 | 839 | * @param pathId |
michael@0 | 840 | * String type, check the 'pathid' parameter from TS 27.007 +CRSM. |
michael@0 | 841 | * @param p1, p2, p3 |
michael@0 | 842 | * Arbitrary integer parameters for the command. |
michael@0 | 843 | * @param [optional] dataWriter |
michael@0 | 844 | * The function for writing string parameter for the ICC_COMMAND_UPDATE_RECORD. |
michael@0 | 845 | * @param [optional] pin2 |
michael@0 | 846 | * String containing the PIN2. |
michael@0 | 847 | * @param [optional] aid |
michael@0 | 848 | * AID value. |
michael@0 | 849 | */ |
michael@0 | 850 | iccIO: function(options) { |
michael@0 | 851 | let Buf = this.context.Buf; |
michael@0 | 852 | Buf.newParcel(REQUEST_SIM_IO, options); |
michael@0 | 853 | Buf.writeInt32(options.command); |
michael@0 | 854 | Buf.writeInt32(options.fileId); |
michael@0 | 855 | Buf.writeString(options.pathId); |
michael@0 | 856 | Buf.writeInt32(options.p1); |
michael@0 | 857 | Buf.writeInt32(options.p2); |
michael@0 | 858 | Buf.writeInt32(options.p3); |
michael@0 | 859 | |
michael@0 | 860 | // Write data. |
michael@0 | 861 | if (options.command == ICC_COMMAND_UPDATE_RECORD && |
michael@0 | 862 | options.dataWriter) { |
michael@0 | 863 | options.dataWriter(options.p3); |
michael@0 | 864 | } else { |
michael@0 | 865 | Buf.writeString(null); |
michael@0 | 866 | } |
michael@0 | 867 | |
michael@0 | 868 | // Write pin2. |
michael@0 | 869 | if (options.command == ICC_COMMAND_UPDATE_RECORD && |
michael@0 | 870 | options.pin2) { |
michael@0 | 871 | Buf.writeString(options.pin2); |
michael@0 | 872 | } else { |
michael@0 | 873 | Buf.writeString(null); |
michael@0 | 874 | } |
michael@0 | 875 | |
michael@0 | 876 | if (!this.v5Legacy) { |
michael@0 | 877 | Buf.writeString(options.aid || this.aid); |
michael@0 | 878 | } |
michael@0 | 879 | Buf.sendParcel(); |
michael@0 | 880 | }, |
michael@0 | 881 | |
michael@0 | 882 | /** |
michael@0 | 883 | * Get IMSI. |
michael@0 | 884 | * |
michael@0 | 885 | * @param [optional] aid |
michael@0 | 886 | * AID value. |
michael@0 | 887 | */ |
michael@0 | 888 | getIMSI: function(aid) { |
michael@0 | 889 | let Buf = this.context.Buf; |
michael@0 | 890 | if (this.v5Legacy) { |
michael@0 | 891 | Buf.simpleRequest(REQUEST_GET_IMSI); |
michael@0 | 892 | return; |
michael@0 | 893 | } |
michael@0 | 894 | Buf.newParcel(REQUEST_GET_IMSI); |
michael@0 | 895 | Buf.writeInt32(1); |
michael@0 | 896 | Buf.writeString(aid || this.aid); |
michael@0 | 897 | Buf.sendParcel(); |
michael@0 | 898 | }, |
michael@0 | 899 | |
michael@0 | 900 | /** |
michael@0 | 901 | * Read UICC Phonebook contacts. |
michael@0 | 902 | * |
michael@0 | 903 | * @param contactType |
michael@0 | 904 | * "adn" or "fdn". |
michael@0 | 905 | * @param requestId |
michael@0 | 906 | * Request id from RadioInterfaceLayer. |
michael@0 | 907 | */ |
michael@0 | 908 | readICCContacts: function(options) { |
michael@0 | 909 | if (!this.appType) { |
michael@0 | 910 | options.errorMsg = CONTACT_ERR_REQUEST_NOT_SUPPORTED; |
michael@0 | 911 | this.sendChromeMessage(options); |
michael@0 | 912 | return; |
michael@0 | 913 | } |
michael@0 | 914 | |
michael@0 | 915 | this.context.ICCContactHelper.readICCContacts( |
michael@0 | 916 | this.appType, |
michael@0 | 917 | options.contactType, |
michael@0 | 918 | function onsuccess(contacts) { |
michael@0 | 919 | for (let i = 0; i < contacts.length; i++) { |
michael@0 | 920 | let contact = contacts[i]; |
michael@0 | 921 | let pbrIndex = contact.pbrIndex || 0; |
michael@0 | 922 | let recordIndex = pbrIndex * ICC_MAX_LINEAR_FIXED_RECORDS + contact.recordId; |
michael@0 | 923 | contact.contactId = this.iccInfo.iccid + recordIndex; |
michael@0 | 924 | } |
michael@0 | 925 | // Reuse 'options' to get 'requestId' and 'contactType'. |
michael@0 | 926 | options.contacts = contacts; |
michael@0 | 927 | this.sendChromeMessage(options); |
michael@0 | 928 | }.bind(this), |
michael@0 | 929 | function onerror(errorMsg) { |
michael@0 | 930 | options.errorMsg = errorMsg; |
michael@0 | 931 | this.sendChromeMessage(options); |
michael@0 | 932 | }.bind(this)); |
michael@0 | 933 | }, |
michael@0 | 934 | |
michael@0 | 935 | /** |
michael@0 | 936 | * Update UICC Phonebook. |
michael@0 | 937 | * |
michael@0 | 938 | * @param contactType "adn" or "fdn". |
michael@0 | 939 | * @param contact The contact will be updated. |
michael@0 | 940 | * @param pin2 PIN2 is required for updating FDN. |
michael@0 | 941 | * @param requestId Request id from RadioInterfaceLayer. |
michael@0 | 942 | */ |
michael@0 | 943 | updateICCContact: function(options) { |
michael@0 | 944 | let onsuccess = function onsuccess() { |
michael@0 | 945 | let recordIndex = |
michael@0 | 946 | contact.pbrIndex * ICC_MAX_LINEAR_FIXED_RECORDS + contact.recordId; |
michael@0 | 947 | contact.contactId = this.iccInfo.iccid + recordIndex; |
michael@0 | 948 | // Reuse 'options' to get 'requestId' and 'contactType'. |
michael@0 | 949 | this.sendChromeMessage(options); |
michael@0 | 950 | }.bind(this); |
michael@0 | 951 | |
michael@0 | 952 | let onerror = function onerror(errorMsg) { |
michael@0 | 953 | options.errorMsg = errorMsg; |
michael@0 | 954 | this.sendChromeMessage(options); |
michael@0 | 955 | }.bind(this); |
michael@0 | 956 | |
michael@0 | 957 | if (!this.appType || !options.contact) { |
michael@0 | 958 | onerror(CONTACT_ERR_REQUEST_NOT_SUPPORTED ); |
michael@0 | 959 | return; |
michael@0 | 960 | } |
michael@0 | 961 | |
michael@0 | 962 | let contact = options.contact; |
michael@0 | 963 | let iccid = this.iccInfo.iccid; |
michael@0 | 964 | let isValidRecordId = false; |
michael@0 | 965 | if (typeof contact.contactId === "string" && |
michael@0 | 966 | contact.contactId.startsWith(iccid)) { |
michael@0 | 967 | let recordIndex = contact.contactId.substring(iccid.length); |
michael@0 | 968 | contact.pbrIndex = Math.floor(recordIndex / ICC_MAX_LINEAR_FIXED_RECORDS); |
michael@0 | 969 | contact.recordId = recordIndex % ICC_MAX_LINEAR_FIXED_RECORDS; |
michael@0 | 970 | isValidRecordId = contact.recordId > 0 && contact.recordId < 0xff; |
michael@0 | 971 | } |
michael@0 | 972 | |
michael@0 | 973 | if (DEBUG) { |
michael@0 | 974 | this.context.debug("Update ICC Contact " + JSON.stringify(contact)); |
michael@0 | 975 | } |
michael@0 | 976 | |
michael@0 | 977 | let ICCContactHelper = this.context.ICCContactHelper; |
michael@0 | 978 | // If contact has 'recordId' property, updates corresponding record. |
michael@0 | 979 | // If not, inserts the contact into a free record. |
michael@0 | 980 | if (isValidRecordId) { |
michael@0 | 981 | ICCContactHelper.updateICCContact( |
michael@0 | 982 | this.appType, options.contactType, contact, options.pin2, onsuccess, onerror); |
michael@0 | 983 | } else { |
michael@0 | 984 | ICCContactHelper.addICCContact( |
michael@0 | 985 | this.appType, options.contactType, contact, options.pin2, onsuccess, onerror); |
michael@0 | 986 | } |
michael@0 | 987 | }, |
michael@0 | 988 | |
michael@0 | 989 | /** |
michael@0 | 990 | * Request the phone's radio to be enabled or disabled. |
michael@0 | 991 | * |
michael@0 | 992 | * @param enabled |
michael@0 | 993 | * Boolean indicating the desired state. |
michael@0 | 994 | */ |
michael@0 | 995 | setRadioEnabled: function(options) { |
michael@0 | 996 | let Buf = this.context.Buf; |
michael@0 | 997 | Buf.newParcel(REQUEST_RADIO_POWER, options); |
michael@0 | 998 | Buf.writeInt32(1); |
michael@0 | 999 | Buf.writeInt32(options.enabled ? 1 : 0); |
michael@0 | 1000 | Buf.sendParcel(); |
michael@0 | 1001 | }, |
michael@0 | 1002 | |
michael@0 | 1003 | /** |
michael@0 | 1004 | * Query call waiting status via MMI. |
michael@0 | 1005 | */ |
michael@0 | 1006 | _handleQueryMMICallWaiting: function(options) { |
michael@0 | 1007 | let Buf = this.context.Buf; |
michael@0 | 1008 | |
michael@0 | 1009 | function callback(options) { |
michael@0 | 1010 | options.length = Buf.readInt32(); |
michael@0 | 1011 | options.enabled = (Buf.readInt32() === 1); |
michael@0 | 1012 | let services = Buf.readInt32(); |
michael@0 | 1013 | if (options.enabled) { |
michael@0 | 1014 | options.statusMessage = MMI_SM_KS_SERVICE_ENABLED_FOR; |
michael@0 | 1015 | let serviceClass = []; |
michael@0 | 1016 | for (let serviceClassMask = 1; |
michael@0 | 1017 | serviceClassMask <= ICC_SERVICE_CLASS_MAX; |
michael@0 | 1018 | serviceClassMask <<= 1) { |
michael@0 | 1019 | if ((serviceClassMask & services) !== 0) { |
michael@0 | 1020 | serviceClass.push(MMI_KS_SERVICE_CLASS_MAPPING[serviceClassMask]); |
michael@0 | 1021 | } |
michael@0 | 1022 | } |
michael@0 | 1023 | options.additionalInformation = serviceClass; |
michael@0 | 1024 | } else { |
michael@0 | 1025 | options.statusMessage = MMI_SM_KS_SERVICE_DISABLED; |
michael@0 | 1026 | } |
michael@0 | 1027 | |
michael@0 | 1028 | // Prevent DataCloneError when sending chrome messages. |
michael@0 | 1029 | delete options.callback; |
michael@0 | 1030 | this.sendChromeMessage(options); |
michael@0 | 1031 | } |
michael@0 | 1032 | |
michael@0 | 1033 | options.callback = callback; |
michael@0 | 1034 | this.queryCallWaiting(options); |
michael@0 | 1035 | }, |
michael@0 | 1036 | |
michael@0 | 1037 | /** |
michael@0 | 1038 | * Set call waiting status via MMI. |
michael@0 | 1039 | */ |
michael@0 | 1040 | _handleSetMMICallWaiting: function(options) { |
michael@0 | 1041 | function callback(options) { |
michael@0 | 1042 | if (options.enabled) { |
michael@0 | 1043 | options.statusMessage = MMI_SM_KS_SERVICE_ENABLED; |
michael@0 | 1044 | } else { |
michael@0 | 1045 | options.statusMessage = MMI_SM_KS_SERVICE_DISABLED; |
michael@0 | 1046 | } |
michael@0 | 1047 | |
michael@0 | 1048 | // Prevent DataCloneError when sending chrome messages. |
michael@0 | 1049 | delete options.callback; |
michael@0 | 1050 | this.sendChromeMessage(options); |
michael@0 | 1051 | } |
michael@0 | 1052 | |
michael@0 | 1053 | options.callback = callback; |
michael@0 | 1054 | this.setCallWaiting(options); |
michael@0 | 1055 | }, |
michael@0 | 1056 | |
michael@0 | 1057 | /** |
michael@0 | 1058 | * Query call waiting status. |
michael@0 | 1059 | * |
michael@0 | 1060 | */ |
michael@0 | 1061 | queryCallWaiting: function(options) { |
michael@0 | 1062 | let Buf = this.context.Buf; |
michael@0 | 1063 | Buf.newParcel(REQUEST_QUERY_CALL_WAITING, options); |
michael@0 | 1064 | Buf.writeInt32(1); |
michael@0 | 1065 | // As per 3GPP TS 24.083, section 1.6 UE doesn't need to send service |
michael@0 | 1066 | // class parameter in call waiting interrogation to network |
michael@0 | 1067 | Buf.writeInt32(ICC_SERVICE_CLASS_NONE); |
michael@0 | 1068 | Buf.sendParcel(); |
michael@0 | 1069 | }, |
michael@0 | 1070 | |
michael@0 | 1071 | /** |
michael@0 | 1072 | * Set call waiting status. |
michael@0 | 1073 | * |
michael@0 | 1074 | * @param on |
michael@0 | 1075 | * Boolean indicating the desired waiting status. |
michael@0 | 1076 | */ |
michael@0 | 1077 | setCallWaiting: function(options) { |
michael@0 | 1078 | let Buf = this.context.Buf; |
michael@0 | 1079 | Buf.newParcel(REQUEST_SET_CALL_WAITING, options); |
michael@0 | 1080 | Buf.writeInt32(2); |
michael@0 | 1081 | Buf.writeInt32(options.enabled ? 1 : 0); |
michael@0 | 1082 | Buf.writeInt32(options.serviceClass !== undefined ? |
michael@0 | 1083 | options.serviceClass : ICC_SERVICE_CLASS_VOICE); |
michael@0 | 1084 | Buf.sendParcel(); |
michael@0 | 1085 | }, |
michael@0 | 1086 | |
michael@0 | 1087 | /** |
michael@0 | 1088 | * Queries current CLIP status. |
michael@0 | 1089 | * |
michael@0 | 1090 | * (MMI request for code "*#30#") |
michael@0 | 1091 | * |
michael@0 | 1092 | */ |
michael@0 | 1093 | queryCLIP: function(options) { |
michael@0 | 1094 | this.context.Buf.simpleRequest(REQUEST_QUERY_CLIP, options); |
michael@0 | 1095 | }, |
michael@0 | 1096 | |
michael@0 | 1097 | /** |
michael@0 | 1098 | * Queries current CLIR status. |
michael@0 | 1099 | * |
michael@0 | 1100 | */ |
michael@0 | 1101 | getCLIR: function(options) { |
michael@0 | 1102 | this.context.Buf.simpleRequest(REQUEST_GET_CLIR, options); |
michael@0 | 1103 | }, |
michael@0 | 1104 | |
michael@0 | 1105 | /** |
michael@0 | 1106 | * Enables or disables the presentation of the calling line identity (CLI) to |
michael@0 | 1107 | * the called party when originating a call. |
michael@0 | 1108 | * |
michael@0 | 1109 | * @param options.clirMode |
michael@0 | 1110 | * Is one of the CLIR_* constants in |
michael@0 | 1111 | * nsIDOMMozMobileConnection interface. |
michael@0 | 1112 | */ |
michael@0 | 1113 | setCLIR: function(options) { |
michael@0 | 1114 | if (options) { |
michael@0 | 1115 | this.clirMode = options.clirMode; |
michael@0 | 1116 | } |
michael@0 | 1117 | let Buf = this.context.Buf; |
michael@0 | 1118 | Buf.newParcel(REQUEST_SET_CLIR, options); |
michael@0 | 1119 | Buf.writeInt32(1); |
michael@0 | 1120 | Buf.writeInt32(this.clirMode); |
michael@0 | 1121 | Buf.sendParcel(); |
michael@0 | 1122 | }, |
michael@0 | 1123 | |
michael@0 | 1124 | /** |
michael@0 | 1125 | * Set screen state. |
michael@0 | 1126 | * |
michael@0 | 1127 | * @param on |
michael@0 | 1128 | * Boolean indicating whether the screen should be on or off. |
michael@0 | 1129 | */ |
michael@0 | 1130 | setScreenState: function(options) { |
michael@0 | 1131 | let Buf = this.context.Buf; |
michael@0 | 1132 | Buf.newParcel(REQUEST_SCREEN_STATE); |
michael@0 | 1133 | Buf.writeInt32(1); |
michael@0 | 1134 | Buf.writeInt32(options.on ? 1 : 0); |
michael@0 | 1135 | Buf.sendParcel(); |
michael@0 | 1136 | }, |
michael@0 | 1137 | |
michael@0 | 1138 | getVoiceRegistrationState: function() { |
michael@0 | 1139 | this.context.Buf.simpleRequest(REQUEST_VOICE_REGISTRATION_STATE); |
michael@0 | 1140 | }, |
michael@0 | 1141 | |
michael@0 | 1142 | getVoiceRadioTechnology: function() { |
michael@0 | 1143 | this.context.Buf.simpleRequest(REQUEST_VOICE_RADIO_TECH); |
michael@0 | 1144 | }, |
michael@0 | 1145 | |
michael@0 | 1146 | getDataRegistrationState: function() { |
michael@0 | 1147 | this.context.Buf.simpleRequest(REQUEST_DATA_REGISTRATION_STATE); |
michael@0 | 1148 | }, |
michael@0 | 1149 | |
michael@0 | 1150 | getOperator: function() { |
michael@0 | 1151 | this.context.Buf.simpleRequest(REQUEST_OPERATOR); |
michael@0 | 1152 | }, |
michael@0 | 1153 | |
michael@0 | 1154 | /** |
michael@0 | 1155 | * Set the preferred network type. |
michael@0 | 1156 | * |
michael@0 | 1157 | * @param options An object contains a valid index of |
michael@0 | 1158 | * RIL_PREFERRED_NETWORK_TYPE_TO_GECKO as its `networkType` |
michael@0 | 1159 | * attribute, or undefined to set current preferred network |
michael@0 | 1160 | * type. |
michael@0 | 1161 | */ |
michael@0 | 1162 | setPreferredNetworkType: function(options) { |
michael@0 | 1163 | if (options) { |
michael@0 | 1164 | this.preferredNetworkType = options.networkType; |
michael@0 | 1165 | } |
michael@0 | 1166 | if (this.preferredNetworkType == null) { |
michael@0 | 1167 | return; |
michael@0 | 1168 | } |
michael@0 | 1169 | |
michael@0 | 1170 | let Buf = this.context.Buf; |
michael@0 | 1171 | Buf.newParcel(REQUEST_SET_PREFERRED_NETWORK_TYPE, options); |
michael@0 | 1172 | Buf.writeInt32(1); |
michael@0 | 1173 | Buf.writeInt32(this.preferredNetworkType); |
michael@0 | 1174 | Buf.sendParcel(); |
michael@0 | 1175 | }, |
michael@0 | 1176 | |
michael@0 | 1177 | /** |
michael@0 | 1178 | * Get the preferred network type. |
michael@0 | 1179 | */ |
michael@0 | 1180 | getPreferredNetworkType: function(options) { |
michael@0 | 1181 | this.context.Buf.simpleRequest(REQUEST_GET_PREFERRED_NETWORK_TYPE, options); |
michael@0 | 1182 | }, |
michael@0 | 1183 | |
michael@0 | 1184 | /** |
michael@0 | 1185 | * Request various states about the network. |
michael@0 | 1186 | */ |
michael@0 | 1187 | requestNetworkInfo: function() { |
michael@0 | 1188 | if (this._processingNetworkInfo) { |
michael@0 | 1189 | if (DEBUG) { |
michael@0 | 1190 | this.context.debug("Network info requested, but we're already " + |
michael@0 | 1191 | "requesting network info."); |
michael@0 | 1192 | } |
michael@0 | 1193 | this._needRepollNetworkInfo = true; |
michael@0 | 1194 | return; |
michael@0 | 1195 | } |
michael@0 | 1196 | |
michael@0 | 1197 | if (DEBUG) this.context.debug("Requesting network info"); |
michael@0 | 1198 | |
michael@0 | 1199 | this._processingNetworkInfo = true; |
michael@0 | 1200 | this.getVoiceRegistrationState(); |
michael@0 | 1201 | this.getDataRegistrationState(); //TODO only GSM |
michael@0 | 1202 | this.getOperator(); |
michael@0 | 1203 | this.getNetworkSelectionMode(); |
michael@0 | 1204 | this.getSignalStrength(); |
michael@0 | 1205 | }, |
michael@0 | 1206 | |
michael@0 | 1207 | /** |
michael@0 | 1208 | * Get the available networks |
michael@0 | 1209 | */ |
michael@0 | 1210 | getAvailableNetworks: function(options) { |
michael@0 | 1211 | if (DEBUG) this.context.debug("Getting available networks"); |
michael@0 | 1212 | let Buf = this.context.Buf; |
michael@0 | 1213 | Buf.newParcel(REQUEST_QUERY_AVAILABLE_NETWORKS, options); |
michael@0 | 1214 | Buf.sendParcel(); |
michael@0 | 1215 | }, |
michael@0 | 1216 | |
michael@0 | 1217 | /** |
michael@0 | 1218 | * Request the radio's network selection mode |
michael@0 | 1219 | */ |
michael@0 | 1220 | getNetworkSelectionMode: function() { |
michael@0 | 1221 | if (DEBUG) this.context.debug("Getting network selection mode"); |
michael@0 | 1222 | this.context.Buf.simpleRequest(REQUEST_QUERY_NETWORK_SELECTION_MODE); |
michael@0 | 1223 | }, |
michael@0 | 1224 | |
michael@0 | 1225 | /** |
michael@0 | 1226 | * Tell the radio to automatically choose a voice/data network |
michael@0 | 1227 | */ |
michael@0 | 1228 | selectNetworkAuto: function(options) { |
michael@0 | 1229 | if (DEBUG) this.context.debug("Setting automatic network selection"); |
michael@0 | 1230 | this.context.Buf.simpleRequest(REQUEST_SET_NETWORK_SELECTION_AUTOMATIC, options); |
michael@0 | 1231 | }, |
michael@0 | 1232 | |
michael@0 | 1233 | /** |
michael@0 | 1234 | * Set the roaming preference mode |
michael@0 | 1235 | */ |
michael@0 | 1236 | setRoamingPreference: function(options) { |
michael@0 | 1237 | let roamingMode = CDMA_ROAMING_PREFERENCE_TO_GECKO.indexOf(options.mode); |
michael@0 | 1238 | |
michael@0 | 1239 | if (roamingMode === -1) { |
michael@0 | 1240 | options.errorMsg = GECKO_ERROR_INVALID_PARAMETER; |
michael@0 | 1241 | this.sendChromeMessage(options); |
michael@0 | 1242 | return; |
michael@0 | 1243 | } |
michael@0 | 1244 | |
michael@0 | 1245 | let Buf = this.context.Buf; |
michael@0 | 1246 | Buf.newParcel(REQUEST_CDMA_SET_ROAMING_PREFERENCE, options); |
michael@0 | 1247 | Buf.writeInt32(1); |
michael@0 | 1248 | Buf.writeInt32(roamingMode); |
michael@0 | 1249 | Buf.sendParcel(); |
michael@0 | 1250 | }, |
michael@0 | 1251 | |
michael@0 | 1252 | /** |
michael@0 | 1253 | * Get the roaming preference mode |
michael@0 | 1254 | */ |
michael@0 | 1255 | queryRoamingPreference: function(options) { |
michael@0 | 1256 | this.context.Buf.simpleRequest(REQUEST_CDMA_QUERY_ROAMING_PREFERENCE, options); |
michael@0 | 1257 | }, |
michael@0 | 1258 | |
michael@0 | 1259 | /** |
michael@0 | 1260 | * Set the voice privacy mode |
michael@0 | 1261 | */ |
michael@0 | 1262 | setVoicePrivacyMode: function(options) { |
michael@0 | 1263 | let Buf = this.context.Buf; |
michael@0 | 1264 | Buf.newParcel(REQUEST_CDMA_SET_PREFERRED_VOICE_PRIVACY_MODE, options); |
michael@0 | 1265 | Buf.writeInt32(1); |
michael@0 | 1266 | Buf.writeInt32(options.enabled ? 1 : 0); |
michael@0 | 1267 | Buf.sendParcel(); |
michael@0 | 1268 | }, |
michael@0 | 1269 | |
michael@0 | 1270 | /** |
michael@0 | 1271 | * Get the voice privacy mode |
michael@0 | 1272 | */ |
michael@0 | 1273 | queryVoicePrivacyMode: function(options) { |
michael@0 | 1274 | this.context.Buf.simpleRequest(REQUEST_CDMA_QUERY_PREFERRED_VOICE_PRIVACY_MODE, options); |
michael@0 | 1275 | }, |
michael@0 | 1276 | |
michael@0 | 1277 | /** |
michael@0 | 1278 | * Open Logical UICC channel (aid) for Secure Element access |
michael@0 | 1279 | */ |
michael@0 | 1280 | iccOpenChannel: function(options) { |
michael@0 | 1281 | if (DEBUG) { |
michael@0 | 1282 | this.context.debug("iccOpenChannel: " + JSON.stringify(options)); |
michael@0 | 1283 | } |
michael@0 | 1284 | |
michael@0 | 1285 | let Buf = this.context.Buf; |
michael@0 | 1286 | Buf.newParcel(REQUEST_SIM_OPEN_CHANNEL, options); |
michael@0 | 1287 | Buf.writeString(options.aid); |
michael@0 | 1288 | Buf.sendParcel(); |
michael@0 | 1289 | }, |
michael@0 | 1290 | |
michael@0 | 1291 | /** |
michael@0 | 1292 | * Exchange APDU data on an open Logical UICC channel |
michael@0 | 1293 | */ |
michael@0 | 1294 | iccExchangeAPDU: function(options) { |
michael@0 | 1295 | if (DEBUG) this.context.debug("iccExchangeAPDU: " + JSON.stringify(options)); |
michael@0 | 1296 | |
michael@0 | 1297 | let cla = options.apdu.cla; |
michael@0 | 1298 | let command = options.apdu.command; |
michael@0 | 1299 | let channel = options.channel; |
michael@0 | 1300 | let path = options.apdu.path || ""; |
michael@0 | 1301 | let data = options.apdu.data || ""; |
michael@0 | 1302 | let data2 = options.apdu.data2 || ""; |
michael@0 | 1303 | |
michael@0 | 1304 | let p1 = options.apdu.p1; |
michael@0 | 1305 | let p2 = options.apdu.p2; |
michael@0 | 1306 | let p3 = options.apdu.p3; // Extra |
michael@0 | 1307 | |
michael@0 | 1308 | let Buf = this.context.Buf; |
michael@0 | 1309 | Buf.newParcel(REQUEST_SIM_ACCESS_CHANNEL, options); |
michael@0 | 1310 | Buf.writeInt32(cla); |
michael@0 | 1311 | Buf.writeInt32(command); |
michael@0 | 1312 | Buf.writeInt32(channel); |
michael@0 | 1313 | Buf.writeString(path); // path |
michael@0 | 1314 | Buf.writeInt32(p1); |
michael@0 | 1315 | Buf.writeInt32(p2); |
michael@0 | 1316 | Buf.writeInt32(p3); |
michael@0 | 1317 | Buf.writeString(data); // generic data field. |
michael@0 | 1318 | Buf.writeString(data2); |
michael@0 | 1319 | |
michael@0 | 1320 | Buf.sendParcel(); |
michael@0 | 1321 | }, |
michael@0 | 1322 | |
michael@0 | 1323 | /** |
michael@0 | 1324 | * Close Logical UICC channel |
michael@0 | 1325 | */ |
michael@0 | 1326 | iccCloseChannel: function(options) { |
michael@0 | 1327 | if (DEBUG) this.context.debug("iccCloseChannel: " + JSON.stringify(options)); |
michael@0 | 1328 | |
michael@0 | 1329 | let Buf = this.context.Buf; |
michael@0 | 1330 | Buf.newParcel(REQUEST_SIM_CLOSE_CHANNEL, options); |
michael@0 | 1331 | Buf.writeInt32(1); |
michael@0 | 1332 | Buf.writeInt32(options.channel); |
michael@0 | 1333 | Buf.sendParcel(); |
michael@0 | 1334 | }, |
michael@0 | 1335 | |
michael@0 | 1336 | /** |
michael@0 | 1337 | * Tell the radio to choose a specific voice/data network |
michael@0 | 1338 | */ |
michael@0 | 1339 | selectNetwork: function(options) { |
michael@0 | 1340 | if (DEBUG) { |
michael@0 | 1341 | this.context.debug("Setting manual network selection: " + |
michael@0 | 1342 | options.mcc + ", " + options.mnc); |
michael@0 | 1343 | } |
michael@0 | 1344 | |
michael@0 | 1345 | let numeric = (options.mcc && options.mnc) ? options.mcc + options.mnc : null; |
michael@0 | 1346 | let Buf = this.context.Buf; |
michael@0 | 1347 | Buf.newParcel(REQUEST_SET_NETWORK_SELECTION_MANUAL, options); |
michael@0 | 1348 | Buf.writeString(numeric); |
michael@0 | 1349 | Buf.sendParcel(); |
michael@0 | 1350 | }, |
michael@0 | 1351 | |
michael@0 | 1352 | /** |
michael@0 | 1353 | * Get current calls. |
michael@0 | 1354 | */ |
michael@0 | 1355 | getCurrentCalls: function() { |
michael@0 | 1356 | this.context.Buf.simpleRequest(REQUEST_GET_CURRENT_CALLS); |
michael@0 | 1357 | }, |
michael@0 | 1358 | |
michael@0 | 1359 | /** |
michael@0 | 1360 | * Get the signal strength. |
michael@0 | 1361 | */ |
michael@0 | 1362 | getSignalStrength: function() { |
michael@0 | 1363 | this.context.Buf.simpleRequest(REQUEST_SIGNAL_STRENGTH); |
michael@0 | 1364 | }, |
michael@0 | 1365 | |
michael@0 | 1366 | getIMEI: function(options) { |
michael@0 | 1367 | this.context.Buf.simpleRequest(REQUEST_GET_IMEI, options); |
michael@0 | 1368 | }, |
michael@0 | 1369 | |
michael@0 | 1370 | getIMEISV: function() { |
michael@0 | 1371 | this.context.Buf.simpleRequest(REQUEST_GET_IMEISV); |
michael@0 | 1372 | }, |
michael@0 | 1373 | |
michael@0 | 1374 | getDeviceIdentity: function() { |
michael@0 | 1375 | this.context.Buf.simpleRequest(REQUEST_DEVICE_IDENTITY); |
michael@0 | 1376 | }, |
michael@0 | 1377 | |
michael@0 | 1378 | getBasebandVersion: function() { |
michael@0 | 1379 | this.context.Buf.simpleRequest(REQUEST_BASEBAND_VERSION); |
michael@0 | 1380 | }, |
michael@0 | 1381 | |
michael@0 | 1382 | sendExitEmergencyCbModeRequest: function(options) { |
michael@0 | 1383 | this.context.Buf.simpleRequest(REQUEST_EXIT_EMERGENCY_CALLBACK_MODE, options); |
michael@0 | 1384 | }, |
michael@0 | 1385 | |
michael@0 | 1386 | getCdmaSubscription: function() { |
michael@0 | 1387 | this.context.Buf.simpleRequest(REQUEST_CDMA_SUBSCRIPTION); |
michael@0 | 1388 | }, |
michael@0 | 1389 | |
michael@0 | 1390 | exitEmergencyCbMode: function(options) { |
michael@0 | 1391 | // The function could be called by an API from RadioInterfaceLayer or by |
michael@0 | 1392 | // ril_worker itself. From ril_worker, we won't pass the parameter |
michael@0 | 1393 | // 'options'. In this case, it is marked as internal. |
michael@0 | 1394 | if (!options) { |
michael@0 | 1395 | options = {internal: true}; |
michael@0 | 1396 | } |
michael@0 | 1397 | this._cancelEmergencyCbModeTimeout(); |
michael@0 | 1398 | this.sendExitEmergencyCbModeRequest(options); |
michael@0 | 1399 | }, |
michael@0 | 1400 | |
michael@0 | 1401 | /** |
michael@0 | 1402 | * Cache the request for making an emergency call when radio is off. The |
michael@0 | 1403 | * request shall include two types of callback functions. 'callback' is |
michael@0 | 1404 | * called when radio is ready, and 'onerror' is called when turning radio |
michael@0 | 1405 | * on fails. |
michael@0 | 1406 | */ |
michael@0 | 1407 | cachedDialRequest : null, |
michael@0 | 1408 | |
michael@0 | 1409 | /** |
michael@0 | 1410 | * Dial the phone. |
michael@0 | 1411 | * |
michael@0 | 1412 | * @param number |
michael@0 | 1413 | * String containing the number to dial. |
michael@0 | 1414 | * @param clirMode |
michael@0 | 1415 | * Integer for showing/hidding the caller Id to the called party. |
michael@0 | 1416 | * @param uusInfo |
michael@0 | 1417 | * Integer doing something XXX TODO |
michael@0 | 1418 | */ |
michael@0 | 1419 | dial: function(options) { |
michael@0 | 1420 | let onerror = (function onerror(options, errorMsg) { |
michael@0 | 1421 | options.success = false; |
michael@0 | 1422 | options.errorMsg = errorMsg; |
michael@0 | 1423 | this.sendChromeMessage(options); |
michael@0 | 1424 | }).bind(this, options); |
michael@0 | 1425 | |
michael@0 | 1426 | if (this._isEmergencyNumber(options.number)) { |
michael@0 | 1427 | this.dialEmergencyNumber(options, onerror); |
michael@0 | 1428 | } else { |
michael@0 | 1429 | if (!this._isCdma) { |
michael@0 | 1430 | // TODO: Both dial() and sendMMI() functions should be unified at some |
michael@0 | 1431 | // point in the future. In the mean time we handle temporary CLIR MMI |
michael@0 | 1432 | // commands through the dial() function. Please see bug 889737. |
michael@0 | 1433 | let mmi = this._parseMMI(options.number); |
michael@0 | 1434 | if (mmi && this._isTemporaryModeCLIR(mmi)) { |
michael@0 | 1435 | options.number = mmi.dialNumber; |
michael@0 | 1436 | // In temporary mode, MMI_PROCEDURE_ACTIVATION means allowing CLI |
michael@0 | 1437 | // presentation, i.e. CLIR_SUPPRESSION. See TS 22.030, Annex B. |
michael@0 | 1438 | options.clirMode = mmi.procedure == MMI_PROCEDURE_ACTIVATION ? |
michael@0 | 1439 | CLIR_SUPPRESSION : CLIR_INVOCATION; |
michael@0 | 1440 | } |
michael@0 | 1441 | } |
michael@0 | 1442 | this.dialNonEmergencyNumber(options, onerror); |
michael@0 | 1443 | } |
michael@0 | 1444 | }, |
michael@0 | 1445 | |
michael@0 | 1446 | dialNonEmergencyNumber: function(options, onerror) { |
michael@0 | 1447 | if (this.radioState == GECKO_RADIOSTATE_OFF) { |
michael@0 | 1448 | // Notify error in establishing the call without radio. |
michael@0 | 1449 | onerror(GECKO_ERROR_RADIO_NOT_AVAILABLE); |
michael@0 | 1450 | return; |
michael@0 | 1451 | } |
michael@0 | 1452 | |
michael@0 | 1453 | if (this.voiceRegistrationState.emergencyCallsOnly || |
michael@0 | 1454 | options.isDialEmergency) { |
michael@0 | 1455 | onerror(RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[CALL_FAIL_UNOBTAINABLE_NUMBER]); |
michael@0 | 1456 | return; |
michael@0 | 1457 | } |
michael@0 | 1458 | |
michael@0 | 1459 | // Exit emergency callback mode when user dial a non-emergency call. |
michael@0 | 1460 | if (this._isInEmergencyCbMode) { |
michael@0 | 1461 | this.exitEmergencyCbMode(); |
michael@0 | 1462 | } |
michael@0 | 1463 | |
michael@0 | 1464 | if (this._isCdma && Object.keys(this.currentCalls).length == 1) { |
michael@0 | 1465 | // Make a Cdma 3way call. |
michael@0 | 1466 | options.featureStr = options.number; |
michael@0 | 1467 | this.sendCdmaFlashCommand(options); |
michael@0 | 1468 | } else { |
michael@0 | 1469 | options.request = REQUEST_DIAL; |
michael@0 | 1470 | this.sendDialRequest(options); |
michael@0 | 1471 | } |
michael@0 | 1472 | }, |
michael@0 | 1473 | |
michael@0 | 1474 | dialEmergencyNumber: function(options, onerror) { |
michael@0 | 1475 | options.request = RILQUIRKS_REQUEST_USE_DIAL_EMERGENCY_CALL ? |
michael@0 | 1476 | REQUEST_DIAL_EMERGENCY_CALL : REQUEST_DIAL; |
michael@0 | 1477 | if (this.radioState == GECKO_RADIOSTATE_OFF) { |
michael@0 | 1478 | if (DEBUG) { |
michael@0 | 1479 | this.context.debug("Automatically enable radio for an emergency call."); |
michael@0 | 1480 | } |
michael@0 | 1481 | |
michael@0 | 1482 | if (!this.cachedDialRequest) { |
michael@0 | 1483 | this.cachedDialRequest = {}; |
michael@0 | 1484 | } |
michael@0 | 1485 | this.cachedDialRequest.onerror = onerror; |
michael@0 | 1486 | this.cachedDialRequest.callback = this.sendDialRequest.bind(this, options); |
michael@0 | 1487 | this.setRadioEnabled({enabled: true}); |
michael@0 | 1488 | return; |
michael@0 | 1489 | } |
michael@0 | 1490 | |
michael@0 | 1491 | if (this._isCdma && Object.keys(this.currentCalls).length == 1) { |
michael@0 | 1492 | // Make a Cdma 3way call. |
michael@0 | 1493 | options.featureStr = options.number; |
michael@0 | 1494 | this.sendCdmaFlashCommand(options); |
michael@0 | 1495 | } else { |
michael@0 | 1496 | this.sendDialRequest(options); |
michael@0 | 1497 | } |
michael@0 | 1498 | }, |
michael@0 | 1499 | |
michael@0 | 1500 | sendDialRequest: function(options) { |
michael@0 | 1501 | // Always succeed. |
michael@0 | 1502 | options.success = true; |
michael@0 | 1503 | this.sendChromeMessage(options); |
michael@0 | 1504 | this._createPendingOutgoingCall(options); |
michael@0 | 1505 | |
michael@0 | 1506 | let Buf = this.context.Buf; |
michael@0 | 1507 | Buf.newParcel(options.request, options); |
michael@0 | 1508 | Buf.writeString(options.number); |
michael@0 | 1509 | Buf.writeInt32(options.clirMode || 0); |
michael@0 | 1510 | Buf.writeInt32(options.uusInfo || 0); |
michael@0 | 1511 | // TODO Why do we need this extra 0? It was put it in to make this |
michael@0 | 1512 | // match the format of the binary message. |
michael@0 | 1513 | Buf.writeInt32(0); |
michael@0 | 1514 | Buf.sendParcel(); |
michael@0 | 1515 | }, |
michael@0 | 1516 | |
michael@0 | 1517 | sendCdmaFlashCommand: function(options) { |
michael@0 | 1518 | let Buf = this.context.Buf; |
michael@0 | 1519 | options.isCdma = true; |
michael@0 | 1520 | options.request = REQUEST_CDMA_FLASH; |
michael@0 | 1521 | Buf.newParcel(options.request, options); |
michael@0 | 1522 | Buf.writeString(options.featureStr); |
michael@0 | 1523 | Buf.sendParcel(); |
michael@0 | 1524 | }, |
michael@0 | 1525 | |
michael@0 | 1526 | /** |
michael@0 | 1527 | * Hang up all calls |
michael@0 | 1528 | */ |
michael@0 | 1529 | hangUpAll: function() { |
michael@0 | 1530 | for (let callIndex in this.currentCalls) { |
michael@0 | 1531 | this.hangUp({callIndex: callIndex}); |
michael@0 | 1532 | } |
michael@0 | 1533 | }, |
michael@0 | 1534 | |
michael@0 | 1535 | /** |
michael@0 | 1536 | * Hang up the phone. |
michael@0 | 1537 | * |
michael@0 | 1538 | * @param callIndex |
michael@0 | 1539 | * Call index (1-based) as reported by REQUEST_GET_CURRENT_CALLS. |
michael@0 | 1540 | */ |
michael@0 | 1541 | hangUp: function(options) { |
michael@0 | 1542 | let call = this.currentCalls[options.callIndex]; |
michael@0 | 1543 | if (!call) { |
michael@0 | 1544 | return; |
michael@0 | 1545 | } |
michael@0 | 1546 | |
michael@0 | 1547 | let callIndex = call.callIndex; |
michael@0 | 1548 | if (callIndex === OUTGOING_PLACEHOLDER_CALL_INDEX) { |
michael@0 | 1549 | if (DEBUG) this.context.debug("Hang up pending outgoing call."); |
michael@0 | 1550 | this._removeVoiceCall(call, GECKO_CALL_ERROR_NORMAL_CALL_CLEARING); |
michael@0 | 1551 | return; |
michael@0 | 1552 | } |
michael@0 | 1553 | |
michael@0 | 1554 | call.hangUpLocal = true; |
michael@0 | 1555 | |
michael@0 | 1556 | if (call.state === CALL_STATE_HOLDING) { |
michael@0 | 1557 | this.sendHangUpBackgroundRequest(callIndex); |
michael@0 | 1558 | } else { |
michael@0 | 1559 | this.sendHangUpRequest(callIndex); |
michael@0 | 1560 | } |
michael@0 | 1561 | }, |
michael@0 | 1562 | |
michael@0 | 1563 | sendHangUpRequest: function(callIndex) { |
michael@0 | 1564 | let Buf = this.context.Buf; |
michael@0 | 1565 | Buf.newParcel(REQUEST_HANGUP); |
michael@0 | 1566 | Buf.writeInt32(1); |
michael@0 | 1567 | Buf.writeInt32(callIndex); |
michael@0 | 1568 | Buf.sendParcel(); |
michael@0 | 1569 | }, |
michael@0 | 1570 | |
michael@0 | 1571 | sendHangUpBackgroundRequest: function(callIndex) { |
michael@0 | 1572 | let Buf = this.context.Buf; |
michael@0 | 1573 | Buf.simpleRequest(REQUEST_HANGUP_WAITING_OR_BACKGROUND); |
michael@0 | 1574 | }, |
michael@0 | 1575 | |
michael@0 | 1576 | /** |
michael@0 | 1577 | * Mute or unmute the radio. |
michael@0 | 1578 | * |
michael@0 | 1579 | * @param mute |
michael@0 | 1580 | * Boolean to indicate whether to mute or unmute the radio. |
michael@0 | 1581 | */ |
michael@0 | 1582 | setMute: function(options) { |
michael@0 | 1583 | let Buf = this.context.Buf; |
michael@0 | 1584 | Buf.newParcel(REQUEST_SET_MUTE); |
michael@0 | 1585 | Buf.writeInt32(1); |
michael@0 | 1586 | Buf.writeInt32(options.muted ? 1 : 0); |
michael@0 | 1587 | Buf.sendParcel(); |
michael@0 | 1588 | }, |
michael@0 | 1589 | |
michael@0 | 1590 | /** |
michael@0 | 1591 | * Answer an incoming/waiting call. |
michael@0 | 1592 | * |
michael@0 | 1593 | * @param callIndex |
michael@0 | 1594 | * Call index of the call to answer. |
michael@0 | 1595 | */ |
michael@0 | 1596 | answerCall: function(options) { |
michael@0 | 1597 | // Check for races. Since we dispatched the incoming/waiting call |
michael@0 | 1598 | // notification the incoming/waiting call may have changed. The main |
michael@0 | 1599 | // thread thinks that it is answering the call with the given index, |
michael@0 | 1600 | // so only answer if that is still incoming/waiting. |
michael@0 | 1601 | let call = this.currentCalls[options.callIndex]; |
michael@0 | 1602 | if (!call) { |
michael@0 | 1603 | return; |
michael@0 | 1604 | } |
michael@0 | 1605 | |
michael@0 | 1606 | let Buf = this.context.Buf; |
michael@0 | 1607 | switch (call.state) { |
michael@0 | 1608 | case CALL_STATE_INCOMING: |
michael@0 | 1609 | Buf.simpleRequest(REQUEST_ANSWER); |
michael@0 | 1610 | break; |
michael@0 | 1611 | case CALL_STATE_WAITING: |
michael@0 | 1612 | // Answer the waiting (second) call, and hold the first call. |
michael@0 | 1613 | Buf.simpleRequest(REQUEST_SWITCH_WAITING_OR_HOLDING_AND_ACTIVE); |
michael@0 | 1614 | break; |
michael@0 | 1615 | } |
michael@0 | 1616 | }, |
michael@0 | 1617 | |
michael@0 | 1618 | /** |
michael@0 | 1619 | * Reject an incoming/waiting call. |
michael@0 | 1620 | * |
michael@0 | 1621 | * @param callIndex |
michael@0 | 1622 | * Call index of the call to reject. |
michael@0 | 1623 | */ |
michael@0 | 1624 | rejectCall: function(options) { |
michael@0 | 1625 | // Check for races. Since we dispatched the incoming/waiting call |
michael@0 | 1626 | // notification the incoming/waiting call may have changed. The main |
michael@0 | 1627 | // thread thinks that it is rejecting the call with the given index, |
michael@0 | 1628 | // so only reject if that is still incoming/waiting. |
michael@0 | 1629 | let call = this.currentCalls[options.callIndex]; |
michael@0 | 1630 | if (!call) { |
michael@0 | 1631 | return; |
michael@0 | 1632 | } |
michael@0 | 1633 | |
michael@0 | 1634 | call.hangUpLocal = true; |
michael@0 | 1635 | |
michael@0 | 1636 | let Buf = this.context.Buf; |
michael@0 | 1637 | if (this._isCdma) { |
michael@0 | 1638 | // AT+CHLD=0 means "release held or UDUB." |
michael@0 | 1639 | Buf.simpleRequest(REQUEST_HANGUP_WAITING_OR_BACKGROUND); |
michael@0 | 1640 | return; |
michael@0 | 1641 | } |
michael@0 | 1642 | |
michael@0 | 1643 | switch (call.state) { |
michael@0 | 1644 | case CALL_STATE_INCOMING: |
michael@0 | 1645 | Buf.simpleRequest(REQUEST_UDUB); |
michael@0 | 1646 | break; |
michael@0 | 1647 | case CALL_STATE_WAITING: |
michael@0 | 1648 | // Reject the waiting (second) call, and remain the first call. |
michael@0 | 1649 | Buf.simpleRequest(REQUEST_HANGUP_WAITING_OR_BACKGROUND); |
michael@0 | 1650 | break; |
michael@0 | 1651 | } |
michael@0 | 1652 | }, |
michael@0 | 1653 | |
michael@0 | 1654 | holdCall: function(options) { |
michael@0 | 1655 | let call = this.currentCalls[options.callIndex]; |
michael@0 | 1656 | if (!call) { |
michael@0 | 1657 | options.errorMsg = GECKO_ERROR_GENERIC_FAILURE; |
michael@0 | 1658 | options.success = false; |
michael@0 | 1659 | this.sendChromeMessage(options); |
michael@0 | 1660 | return; |
michael@0 | 1661 | } |
michael@0 | 1662 | |
michael@0 | 1663 | let Buf = this.context.Buf; |
michael@0 | 1664 | if (this._isCdma) { |
michael@0 | 1665 | options.featureStr = ""; |
michael@0 | 1666 | this.sendCdmaFlashCommand(options); |
michael@0 | 1667 | } else if (call.state == CALL_STATE_ACTIVE) { |
michael@0 | 1668 | Buf.simpleRequest(REQUEST_SWITCH_WAITING_OR_HOLDING_AND_ACTIVE, options); |
michael@0 | 1669 | } |
michael@0 | 1670 | }, |
michael@0 | 1671 | |
michael@0 | 1672 | resumeCall: function(options) { |
michael@0 | 1673 | let call = this.currentCalls[options.callIndex]; |
michael@0 | 1674 | if (!call) { |
michael@0 | 1675 | options.errorMsg = GECKO_ERROR_GENERIC_FAILURE; |
michael@0 | 1676 | options.success = false; |
michael@0 | 1677 | this.sendChromeMessage(options); |
michael@0 | 1678 | return; |
michael@0 | 1679 | } |
michael@0 | 1680 | |
michael@0 | 1681 | let Buf = this.context.Buf; |
michael@0 | 1682 | if (this._isCdma) { |
michael@0 | 1683 | options.featureStr = ""; |
michael@0 | 1684 | this.sendCdmaFlashCommand(options); |
michael@0 | 1685 | } else if (call.state == CALL_STATE_HOLDING) { |
michael@0 | 1686 | Buf.simpleRequest(REQUEST_SWITCH_WAITING_OR_HOLDING_AND_ACTIVE, options); |
michael@0 | 1687 | } |
michael@0 | 1688 | }, |
michael@0 | 1689 | |
michael@0 | 1690 | // Flag indicating whether user has requested making a conference call. |
michael@0 | 1691 | _hasConferenceRequest: false, |
michael@0 | 1692 | |
michael@0 | 1693 | conferenceCall: function(options) { |
michael@0 | 1694 | let Buf = this.context.Buf; |
michael@0 | 1695 | if (this._isCdma) { |
michael@0 | 1696 | options.featureStr = ""; |
michael@0 | 1697 | this.sendCdmaFlashCommand(options); |
michael@0 | 1698 | } else { |
michael@0 | 1699 | this._hasConferenceRequest = true; |
michael@0 | 1700 | Buf.simpleRequest(REQUEST_CONFERENCE, options); |
michael@0 | 1701 | } |
michael@0 | 1702 | }, |
michael@0 | 1703 | |
michael@0 | 1704 | separateCall: function(options) { |
michael@0 | 1705 | let call = this.currentCalls[options.callIndex]; |
michael@0 | 1706 | if (!call) { |
michael@0 | 1707 | options.errorName = "removeError"; |
michael@0 | 1708 | options.errorMsg = GECKO_ERROR_GENERIC_FAILURE; |
michael@0 | 1709 | options.success = false; |
michael@0 | 1710 | this.sendChromeMessage(options); |
michael@0 | 1711 | return; |
michael@0 | 1712 | } |
michael@0 | 1713 | |
michael@0 | 1714 | let Buf = this.context.Buf; |
michael@0 | 1715 | if (this._isCdma) { |
michael@0 | 1716 | options.featureStr = ""; |
michael@0 | 1717 | this.sendCdmaFlashCommand(options); |
michael@0 | 1718 | } else { |
michael@0 | 1719 | Buf.newParcel(REQUEST_SEPARATE_CONNECTION, options); |
michael@0 | 1720 | Buf.writeInt32(1); |
michael@0 | 1721 | Buf.writeInt32(options.callIndex); |
michael@0 | 1722 | Buf.sendParcel(); |
michael@0 | 1723 | } |
michael@0 | 1724 | }, |
michael@0 | 1725 | |
michael@0 | 1726 | holdConference: function() { |
michael@0 | 1727 | if (this._isCdma) { |
michael@0 | 1728 | return; |
michael@0 | 1729 | } |
michael@0 | 1730 | |
michael@0 | 1731 | this.context.Buf.simpleRequest(REQUEST_SWITCH_WAITING_OR_HOLDING_AND_ACTIVE); |
michael@0 | 1732 | }, |
michael@0 | 1733 | |
michael@0 | 1734 | resumeConference: function() { |
michael@0 | 1735 | if (this._isCdma) { |
michael@0 | 1736 | return; |
michael@0 | 1737 | } |
michael@0 | 1738 | |
michael@0 | 1739 | this.context.Buf.simpleRequest(REQUEST_SWITCH_WAITING_OR_HOLDING_AND_ACTIVE); |
michael@0 | 1740 | }, |
michael@0 | 1741 | |
michael@0 | 1742 | /** |
michael@0 | 1743 | * Send an SMS. |
michael@0 | 1744 | * |
michael@0 | 1745 | * The `options` parameter object should contain the following attributes: |
michael@0 | 1746 | * |
michael@0 | 1747 | * @param number |
michael@0 | 1748 | * String containing the recipient number. |
michael@0 | 1749 | * @param body |
michael@0 | 1750 | * String containing the message text. |
michael@0 | 1751 | * @param envelopeId |
michael@0 | 1752 | * Numeric value identifying the sms request. |
michael@0 | 1753 | */ |
michael@0 | 1754 | sendSMS: function(options) { |
michael@0 | 1755 | options.langIndex = options.langIndex || PDU_NL_IDENTIFIER_DEFAULT; |
michael@0 | 1756 | options.langShiftIndex = options.langShiftIndex || PDU_NL_IDENTIFIER_DEFAULT; |
michael@0 | 1757 | |
michael@0 | 1758 | if (!options.retryCount) { |
michael@0 | 1759 | options.retryCount = 0; |
michael@0 | 1760 | } |
michael@0 | 1761 | |
michael@0 | 1762 | if (!options.segmentSeq) { |
michael@0 | 1763 | // Fist segment to send |
michael@0 | 1764 | options.segmentSeq = 1; |
michael@0 | 1765 | options.body = options.segments[0].body; |
michael@0 | 1766 | options.encodedBodyLength = options.segments[0].encodedBodyLength; |
michael@0 | 1767 | } |
michael@0 | 1768 | |
michael@0 | 1769 | let Buf = this.context.Buf; |
michael@0 | 1770 | if (this._isCdma) { |
michael@0 | 1771 | Buf.newParcel(REQUEST_CDMA_SEND_SMS, options); |
michael@0 | 1772 | this.context.CdmaPDUHelper.writeMessage(options); |
michael@0 | 1773 | } else { |
michael@0 | 1774 | Buf.newParcel(REQUEST_SEND_SMS, options); |
michael@0 | 1775 | Buf.writeInt32(2); |
michael@0 | 1776 | Buf.writeString(options.SMSC); |
michael@0 | 1777 | this.context.GsmPDUHelper.writeMessage(options); |
michael@0 | 1778 | } |
michael@0 | 1779 | Buf.sendParcel(); |
michael@0 | 1780 | }, |
michael@0 | 1781 | |
michael@0 | 1782 | /** |
michael@0 | 1783 | * Acknowledge the receipt and handling of an SMS. |
michael@0 | 1784 | * |
michael@0 | 1785 | * @param success |
michael@0 | 1786 | * Boolean indicating whether the message was successfuly handled. |
michael@0 | 1787 | * @param cause |
michael@0 | 1788 | * SMS_* constant indicating the reason for unsuccessful handling. |
michael@0 | 1789 | */ |
michael@0 | 1790 | acknowledgeGsmSms: function(success, cause) { |
michael@0 | 1791 | let Buf = this.context.Buf; |
michael@0 | 1792 | Buf.newParcel(REQUEST_SMS_ACKNOWLEDGE); |
michael@0 | 1793 | Buf.writeInt32(2); |
michael@0 | 1794 | Buf.writeInt32(success ? 1 : 0); |
michael@0 | 1795 | Buf.writeInt32(cause); |
michael@0 | 1796 | Buf.sendParcel(); |
michael@0 | 1797 | }, |
michael@0 | 1798 | |
michael@0 | 1799 | /** |
michael@0 | 1800 | * Acknowledge the receipt and handling of an SMS. |
michael@0 | 1801 | * |
michael@0 | 1802 | * @param success |
michael@0 | 1803 | * Boolean indicating whether the message was successfuly handled. |
michael@0 | 1804 | */ |
michael@0 | 1805 | ackSMS: function(options) { |
michael@0 | 1806 | if (options.result == PDU_FCS_RESERVED) { |
michael@0 | 1807 | return; |
michael@0 | 1808 | } |
michael@0 | 1809 | if (this._isCdma) { |
michael@0 | 1810 | this.acknowledgeCdmaSms(options.result == PDU_FCS_OK, options.result); |
michael@0 | 1811 | } else { |
michael@0 | 1812 | this.acknowledgeGsmSms(options.result == PDU_FCS_OK, options.result); |
michael@0 | 1813 | } |
michael@0 | 1814 | }, |
michael@0 | 1815 | |
michael@0 | 1816 | /** |
michael@0 | 1817 | * Acknowledge the receipt and handling of a CDMA SMS. |
michael@0 | 1818 | * |
michael@0 | 1819 | * @param success |
michael@0 | 1820 | * Boolean indicating whether the message was successfuly handled. |
michael@0 | 1821 | * @param cause |
michael@0 | 1822 | * SMS_* constant indicating the reason for unsuccessful handling. |
michael@0 | 1823 | */ |
michael@0 | 1824 | acknowledgeCdmaSms: function(success, cause) { |
michael@0 | 1825 | let Buf = this.context.Buf; |
michael@0 | 1826 | Buf.newParcel(REQUEST_CDMA_SMS_ACKNOWLEDGE); |
michael@0 | 1827 | Buf.writeInt32(success ? 0 : 1); |
michael@0 | 1828 | Buf.writeInt32(cause); |
michael@0 | 1829 | Buf.sendParcel(); |
michael@0 | 1830 | }, |
michael@0 | 1831 | |
michael@0 | 1832 | /** |
michael@0 | 1833 | * Update received MWI into EF_MWIS. |
michael@0 | 1834 | */ |
michael@0 | 1835 | updateMwis: function(options) { |
michael@0 | 1836 | if (this.context.ICCUtilsHelper.isICCServiceAvailable("MWIS")) { |
michael@0 | 1837 | this.context.SimRecordHelper.updateMWIS(options.mwi); |
michael@0 | 1838 | } |
michael@0 | 1839 | }, |
michael@0 | 1840 | |
michael@0 | 1841 | setCellBroadcastDisabled: function(options) { |
michael@0 | 1842 | this.cellBroadcastDisabled = options.disabled; |
michael@0 | 1843 | |
michael@0 | 1844 | // If |this.mergedCellBroadcastConfig| is null, either we haven't finished |
michael@0 | 1845 | // reading required SIM files, or no any channel is ever configured. In |
michael@0 | 1846 | // the former case, we'll call |this.updateCellBroadcastConfig()| later |
michael@0 | 1847 | // with correct configs; in the latter case, we don't bother resetting CB |
michael@0 | 1848 | // to disabled again. |
michael@0 | 1849 | if (this.mergedCellBroadcastConfig) { |
michael@0 | 1850 | this.updateCellBroadcastConfig(); |
michael@0 | 1851 | } |
michael@0 | 1852 | }, |
michael@0 | 1853 | |
michael@0 | 1854 | setCellBroadcastSearchList: function(options) { |
michael@0 | 1855 | let getSearchListStr = function(aSearchList) { |
michael@0 | 1856 | if (typeof aSearchList === "string" || aSearchList instanceof String) { |
michael@0 | 1857 | return aSearchList; |
michael@0 | 1858 | } |
michael@0 | 1859 | |
michael@0 | 1860 | // TODO: Set search list for CDMA/GSM individually. Bug 990926 |
michael@0 | 1861 | let prop = this._isCdma ? "cdma" : "gsm"; |
michael@0 | 1862 | |
michael@0 | 1863 | return aSearchList && aSearchList[prop]; |
michael@0 | 1864 | }.bind(this); |
michael@0 | 1865 | |
michael@0 | 1866 | try { |
michael@0 | 1867 | let str = getSearchListStr(options.searchList); |
michael@0 | 1868 | this.cellBroadcastConfigs.MMI = this._convertCellBroadcastSearchList(str); |
michael@0 | 1869 | options.success = true; |
michael@0 | 1870 | } catch (e) { |
michael@0 | 1871 | if (DEBUG) { |
michael@0 | 1872 | this.context.debug("Invalid Cell Broadcast search list: " + e); |
michael@0 | 1873 | } |
michael@0 | 1874 | options.success = false; |
michael@0 | 1875 | } |
michael@0 | 1876 | |
michael@0 | 1877 | this.sendChromeMessage(options); |
michael@0 | 1878 | if (!options.success) { |
michael@0 | 1879 | return; |
michael@0 | 1880 | } |
michael@0 | 1881 | |
michael@0 | 1882 | this._mergeAllCellBroadcastConfigs(); |
michael@0 | 1883 | }, |
michael@0 | 1884 | |
michael@0 | 1885 | updateCellBroadcastConfig: function() { |
michael@0 | 1886 | let activate = !this.cellBroadcastDisabled && |
michael@0 | 1887 | (this.mergedCellBroadcastConfig != null) && |
michael@0 | 1888 | (this.mergedCellBroadcastConfig.length > 0); |
michael@0 | 1889 | if (activate) { |
michael@0 | 1890 | this.setSmsBroadcastConfig(this.mergedCellBroadcastConfig); |
michael@0 | 1891 | } else { |
michael@0 | 1892 | // It's unnecessary to set config first if we're deactivating. |
michael@0 | 1893 | this.setSmsBroadcastActivation(false); |
michael@0 | 1894 | } |
michael@0 | 1895 | }, |
michael@0 | 1896 | |
michael@0 | 1897 | setGsmSmsBroadcastConfig: function(config) { |
michael@0 | 1898 | let Buf = this.context.Buf; |
michael@0 | 1899 | Buf.newParcel(REQUEST_GSM_SET_BROADCAST_SMS_CONFIG); |
michael@0 | 1900 | |
michael@0 | 1901 | let numConfigs = config ? config.length / 2 : 0; |
michael@0 | 1902 | Buf.writeInt32(numConfigs); |
michael@0 | 1903 | for (let i = 0; i < config.length;) { |
michael@0 | 1904 | Buf.writeInt32(config[i++]); |
michael@0 | 1905 | Buf.writeInt32(config[i++]); |
michael@0 | 1906 | Buf.writeInt32(0x00); |
michael@0 | 1907 | Buf.writeInt32(0xFF); |
michael@0 | 1908 | Buf.writeInt32(1); |
michael@0 | 1909 | } |
michael@0 | 1910 | |
michael@0 | 1911 | Buf.sendParcel(); |
michael@0 | 1912 | }, |
michael@0 | 1913 | |
michael@0 | 1914 | /** |
michael@0 | 1915 | * Send CDMA SMS broadcast config. |
michael@0 | 1916 | * |
michael@0 | 1917 | * @see 3GPP2 C.R1001 Sec. 9.2 and 9.3 |
michael@0 | 1918 | */ |
michael@0 | 1919 | setCdmaSmsBroadcastConfig: function(config) { |
michael@0 | 1920 | let Buf = this.context.Buf; |
michael@0 | 1921 | // |config| is an array of half-closed range: [[from, to), [from, to), ...]. |
michael@0 | 1922 | // It will be further decomposed, ex: [1, 4) => 1, 2, 3. |
michael@0 | 1923 | Buf.newParcel(REQUEST_CDMA_SET_BROADCAST_SMS_CONFIG); |
michael@0 | 1924 | |
michael@0 | 1925 | let numConfigs = 0; |
michael@0 | 1926 | for (let i = 0; i < config.length; i += 2) { |
michael@0 | 1927 | numConfigs += (config[i+1] - config[i]); |
michael@0 | 1928 | } |
michael@0 | 1929 | |
michael@0 | 1930 | Buf.writeInt32(numConfigs); |
michael@0 | 1931 | for (let i = 0; i < config.length;) { |
michael@0 | 1932 | let begin = config[i++]; |
michael@0 | 1933 | let end = config[i++]; |
michael@0 | 1934 | |
michael@0 | 1935 | for (let j = begin; j < end; ++j) { |
michael@0 | 1936 | Buf.writeInt32(j); |
michael@0 | 1937 | Buf.writeInt32(0); // Language Indicator: Unknown or unspecified. |
michael@0 | 1938 | Buf.writeInt32(1); |
michael@0 | 1939 | } |
michael@0 | 1940 | } |
michael@0 | 1941 | |
michael@0 | 1942 | Buf.sendParcel(); |
michael@0 | 1943 | }, |
michael@0 | 1944 | |
michael@0 | 1945 | setSmsBroadcastConfig: function(config) { |
michael@0 | 1946 | if (this._isCdma) { |
michael@0 | 1947 | this.setCdmaSmsBroadcastConfig(config); |
michael@0 | 1948 | } else { |
michael@0 | 1949 | this.setGsmSmsBroadcastConfig(config); |
michael@0 | 1950 | } |
michael@0 | 1951 | }, |
michael@0 | 1952 | |
michael@0 | 1953 | setSmsBroadcastActivation: function(activate) { |
michael@0 | 1954 | let parcelType = this._isCdma ? REQUEST_CDMA_SMS_BROADCAST_ACTIVATION : |
michael@0 | 1955 | REQUEST_GSM_SMS_BROADCAST_ACTIVATION; |
michael@0 | 1956 | let Buf = this.context.Buf; |
michael@0 | 1957 | Buf.newParcel(parcelType); |
michael@0 | 1958 | Buf.writeInt32(1); |
michael@0 | 1959 | // See hardware/ril/include/telephony/ril.h, 0 - Activate, 1 - Turn off. |
michael@0 | 1960 | Buf.writeInt32(activate ? 0 : 1); |
michael@0 | 1961 | Buf.sendParcel(); |
michael@0 | 1962 | }, |
michael@0 | 1963 | |
michael@0 | 1964 | /** |
michael@0 | 1965 | * Start a DTMF Tone. |
michael@0 | 1966 | * |
michael@0 | 1967 | * @param dtmfChar |
michael@0 | 1968 | * DTMF signal to send, 0-9, *, + |
michael@0 | 1969 | */ |
michael@0 | 1970 | startTone: function(options) { |
michael@0 | 1971 | let Buf = this.context.Buf; |
michael@0 | 1972 | Buf.newParcel(REQUEST_DTMF_START); |
michael@0 | 1973 | Buf.writeString(options.dtmfChar); |
michael@0 | 1974 | Buf.sendParcel(); |
michael@0 | 1975 | }, |
michael@0 | 1976 | |
michael@0 | 1977 | stopTone: function() { |
michael@0 | 1978 | this.context.Buf.simpleRequest(REQUEST_DTMF_STOP); |
michael@0 | 1979 | }, |
michael@0 | 1980 | |
michael@0 | 1981 | /** |
michael@0 | 1982 | * Send a DTMF tone. |
michael@0 | 1983 | * |
michael@0 | 1984 | * @param dtmfChar |
michael@0 | 1985 | * DTMF signal to send, 0-9, *, + |
michael@0 | 1986 | */ |
michael@0 | 1987 | sendTone: function(options) { |
michael@0 | 1988 | let Buf = this.context.Buf; |
michael@0 | 1989 | Buf.newParcel(REQUEST_DTMF); |
michael@0 | 1990 | Buf.writeString(options.dtmfChar); |
michael@0 | 1991 | Buf.sendParcel(); |
michael@0 | 1992 | }, |
michael@0 | 1993 | |
michael@0 | 1994 | /** |
michael@0 | 1995 | * Get the Short Message Service Center address. |
michael@0 | 1996 | */ |
michael@0 | 1997 | getSmscAddress: function(options) { |
michael@0 | 1998 | if (!this.SMSC) { |
michael@0 | 1999 | this.context.Buf.simpleRequest(REQUEST_GET_SMSC_ADDRESS, options); |
michael@0 | 2000 | return; |
michael@0 | 2001 | } |
michael@0 | 2002 | |
michael@0 | 2003 | if (!options || options.rilMessageType !== "getSmscAddress") { |
michael@0 | 2004 | return; |
michael@0 | 2005 | } |
michael@0 | 2006 | |
michael@0 | 2007 | options.smscAddress = this.SMSC; |
michael@0 | 2008 | options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError]; |
michael@0 | 2009 | this.sendChromeMessage(options); |
michael@0 | 2010 | }, |
michael@0 | 2011 | |
michael@0 | 2012 | /** |
michael@0 | 2013 | * Set the Short Message Service Center address. |
michael@0 | 2014 | * |
michael@0 | 2015 | * @param smscAddress |
michael@0 | 2016 | * Short Message Service Center address in PDU format. |
michael@0 | 2017 | */ |
michael@0 | 2018 | setSmscAddress: function(options) { |
michael@0 | 2019 | let Buf = this.context.Buf; |
michael@0 | 2020 | Buf.newParcel(REQUEST_SET_SMSC_ADDRESS, options); |
michael@0 | 2021 | Buf.writeString(options.smscAddress); |
michael@0 | 2022 | Buf.sendParcel(); |
michael@0 | 2023 | }, |
michael@0 | 2024 | |
michael@0 | 2025 | /** |
michael@0 | 2026 | * Setup a data call. |
michael@0 | 2027 | * |
michael@0 | 2028 | * @param radioTech |
michael@0 | 2029 | * Integer to indicate radio technology. |
michael@0 | 2030 | * DATACALL_RADIOTECHNOLOGY_CDMA => CDMA. |
michael@0 | 2031 | * DATACALL_RADIOTECHNOLOGY_GSM => GSM. |
michael@0 | 2032 | * @param apn |
michael@0 | 2033 | * String containing the name of the APN to connect to. |
michael@0 | 2034 | * @param user |
michael@0 | 2035 | * String containing the username for the APN. |
michael@0 | 2036 | * @param passwd |
michael@0 | 2037 | * String containing the password for the APN. |
michael@0 | 2038 | * @param chappap |
michael@0 | 2039 | * Integer containing CHAP/PAP auth type. |
michael@0 | 2040 | * DATACALL_AUTH_NONE => PAP and CHAP is never performed. |
michael@0 | 2041 | * DATACALL_AUTH_PAP => PAP may be performed. |
michael@0 | 2042 | * DATACALL_AUTH_CHAP => CHAP may be performed. |
michael@0 | 2043 | * DATACALL_AUTH_PAP_OR_CHAP => PAP / CHAP may be performed. |
michael@0 | 2044 | * @param pdptype |
michael@0 | 2045 | * String containing PDP type to request. ("IP", "IPV6", ...) |
michael@0 | 2046 | */ |
michael@0 | 2047 | setupDataCall: function(options) { |
michael@0 | 2048 | // From ./hardware/ril/include/telephony/ril.h: |
michael@0 | 2049 | // ((const char **)data)[0] Radio technology to use: 0-CDMA, 1-GSM/UMTS, 2... |
michael@0 | 2050 | // for values above 2 this is RIL_RadioTechnology + 2. |
michael@0 | 2051 | // |
michael@0 | 2052 | // From frameworks/base/telephony/java/com/android/internal/telephony/DataConnection.java: |
michael@0 | 2053 | // if the mRilVersion < 6, radio technology must be GSM/UMTS or CDMA. |
michael@0 | 2054 | // Otherwise, it must be + 2 |
michael@0 | 2055 | // |
michael@0 | 2056 | // See also bug 901232 and 867873 |
michael@0 | 2057 | let radioTech; |
michael@0 | 2058 | if (this.v5Legacy) { |
michael@0 | 2059 | radioTech = this._isCdma ? DATACALL_RADIOTECHNOLOGY_CDMA |
michael@0 | 2060 | : DATACALL_RADIOTECHNOLOGY_GSM; |
michael@0 | 2061 | } else { |
michael@0 | 2062 | radioTech = options.radioTech + 2; |
michael@0 | 2063 | } |
michael@0 | 2064 | let Buf = this.context.Buf; |
michael@0 | 2065 | let token = Buf.newParcel(REQUEST_SETUP_DATA_CALL, options); |
michael@0 | 2066 | Buf.writeInt32(7); |
michael@0 | 2067 | Buf.writeString(radioTech.toString()); |
michael@0 | 2068 | Buf.writeString(DATACALL_PROFILE_DEFAULT.toString()); |
michael@0 | 2069 | Buf.writeString(options.apn); |
michael@0 | 2070 | Buf.writeString(options.user); |
michael@0 | 2071 | Buf.writeString(options.passwd); |
michael@0 | 2072 | Buf.writeString(options.chappap.toString()); |
michael@0 | 2073 | Buf.writeString(options.pdptype); |
michael@0 | 2074 | Buf.sendParcel(); |
michael@0 | 2075 | return token; |
michael@0 | 2076 | }, |
michael@0 | 2077 | |
michael@0 | 2078 | /** |
michael@0 | 2079 | * Deactivate a data call. |
michael@0 | 2080 | * |
michael@0 | 2081 | * @param cid |
michael@0 | 2082 | * String containing CID. |
michael@0 | 2083 | * @param reason |
michael@0 | 2084 | * One of DATACALL_DEACTIVATE_* constants. |
michael@0 | 2085 | */ |
michael@0 | 2086 | deactivateDataCall: function(options) { |
michael@0 | 2087 | let datacall = this.currentDataCalls[options.cid]; |
michael@0 | 2088 | if (!datacall) { |
michael@0 | 2089 | return; |
michael@0 | 2090 | } |
michael@0 | 2091 | |
michael@0 | 2092 | let Buf = this.context.Buf; |
michael@0 | 2093 | Buf.newParcel(REQUEST_DEACTIVATE_DATA_CALL, options); |
michael@0 | 2094 | Buf.writeInt32(2); |
michael@0 | 2095 | Buf.writeString(options.cid); |
michael@0 | 2096 | Buf.writeString(options.reason || DATACALL_DEACTIVATE_NO_REASON); |
michael@0 | 2097 | Buf.sendParcel(); |
michael@0 | 2098 | |
michael@0 | 2099 | datacall.state = GECKO_NETWORK_STATE_DISCONNECTING; |
michael@0 | 2100 | this.sendChromeMessage(datacall); |
michael@0 | 2101 | }, |
michael@0 | 2102 | |
michael@0 | 2103 | /** |
michael@0 | 2104 | * Get a list of data calls. |
michael@0 | 2105 | */ |
michael@0 | 2106 | getDataCallList: function() { |
michael@0 | 2107 | this.context.Buf.simpleRequest(REQUEST_DATA_CALL_LIST); |
michael@0 | 2108 | }, |
michael@0 | 2109 | |
michael@0 | 2110 | _attachDataRegistration: false, |
michael@0 | 2111 | /** |
michael@0 | 2112 | * Manually attach/detach data registration. |
michael@0 | 2113 | * |
michael@0 | 2114 | * @param attach |
michael@0 | 2115 | * Boolean value indicating attach or detach. |
michael@0 | 2116 | */ |
michael@0 | 2117 | setDataRegistration: function(options) { |
michael@0 | 2118 | let request = options.attach ? RIL_REQUEST_GPRS_ATTACH : |
michael@0 | 2119 | RIL_REQUEST_GPRS_DETACH; |
michael@0 | 2120 | this._attachDataRegistration = options.attach; |
michael@0 | 2121 | this.context.Buf.simpleRequest(request); |
michael@0 | 2122 | }, |
michael@0 | 2123 | |
michael@0 | 2124 | /** |
michael@0 | 2125 | * Get failure casue code for the most recently failed PDP context. |
michael@0 | 2126 | */ |
michael@0 | 2127 | getFailCauseCode: function(callback) { |
michael@0 | 2128 | this.context.Buf.simpleRequest(REQUEST_LAST_CALL_FAIL_CAUSE, |
michael@0 | 2129 | {callback: callback}); |
michael@0 | 2130 | }, |
michael@0 | 2131 | |
michael@0 | 2132 | /** |
michael@0 | 2133 | * Helper to parse MMI/USSD string. TS.22.030 Figure 3.5.3.2. |
michael@0 | 2134 | */ |
michael@0 | 2135 | _parseMMI: function(mmiString) { |
michael@0 | 2136 | if (!mmiString || !mmiString.length) { |
michael@0 | 2137 | return null; |
michael@0 | 2138 | } |
michael@0 | 2139 | |
michael@0 | 2140 | let matches = this._matchMMIRegexp(mmiString); |
michael@0 | 2141 | if (matches) { |
michael@0 | 2142 | // After successfully executing the regular expresion over the MMI string, |
michael@0 | 2143 | // the following match groups should contain: |
michael@0 | 2144 | // 1 = full MMI string that might be used as a USSD request. |
michael@0 | 2145 | // 2 = MMI procedure. |
michael@0 | 2146 | // 3 = Service code. |
michael@0 | 2147 | // 5 = SIA. |
michael@0 | 2148 | // 7 = SIB. |
michael@0 | 2149 | // 9 = SIC. |
michael@0 | 2150 | // 11 = Password registration. |
michael@0 | 2151 | // 12 = Dialing number. |
michael@0 | 2152 | return { |
michael@0 | 2153 | fullMMI: matches[MMI_MATCH_GROUP_FULL_MMI], |
michael@0 | 2154 | procedure: matches[MMI_MATCH_GROUP_MMI_PROCEDURE], |
michael@0 | 2155 | serviceCode: matches[MMI_MATCH_GROUP_SERVICE_CODE], |
michael@0 | 2156 | sia: matches[MMI_MATCH_GROUP_SIA], |
michael@0 | 2157 | sib: matches[MMI_MATCH_GROUP_SIB], |
michael@0 | 2158 | sic: matches[MMI_MATCH_GROUP_SIC], |
michael@0 | 2159 | pwd: matches[MMI_MATCH_GROUP_PWD_CONFIRM], |
michael@0 | 2160 | dialNumber: matches[MMI_MATCH_GROUP_DIALING_NUMBER] |
michael@0 | 2161 | }; |
michael@0 | 2162 | } |
michael@0 | 2163 | |
michael@0 | 2164 | if (this._isPoundString(mmiString) || |
michael@0 | 2165 | this._isMMIShortString(mmiString)) { |
michael@0 | 2166 | return { |
michael@0 | 2167 | fullMMI: mmiString |
michael@0 | 2168 | }; |
michael@0 | 2169 | } |
michael@0 | 2170 | |
michael@0 | 2171 | return null; |
michael@0 | 2172 | }, |
michael@0 | 2173 | |
michael@0 | 2174 | /** |
michael@0 | 2175 | * Helper to parse MMI string via regular expression. TS.22.030 Figure |
michael@0 | 2176 | * 3.5.3.2. |
michael@0 | 2177 | */ |
michael@0 | 2178 | _matchMMIRegexp: function(mmiString) { |
michael@0 | 2179 | // Regexp to parse and process the MMI code. |
michael@0 | 2180 | if (this._mmiRegExp == null) { |
michael@0 | 2181 | // The first group of the regexp takes the whole MMI string. |
michael@0 | 2182 | // The second group takes the MMI procedure that can be: |
michael@0 | 2183 | // - Activation (*SC*SI#). |
michael@0 | 2184 | // - Deactivation (#SC*SI#). |
michael@0 | 2185 | // - Interrogation (*#SC*SI#). |
michael@0 | 2186 | // - Registration (**SC*SI#). |
michael@0 | 2187 | // - Erasure (##SC*SI#). |
michael@0 | 2188 | // where SC = Service Code (2 or 3 digits) and SI = Supplementary Info |
michael@0 | 2189 | // (variable length). |
michael@0 | 2190 | let pattern = "((\\*[*#]?|##?)"; |
michael@0 | 2191 | |
michael@0 | 2192 | // Third group of the regexp looks for the MMI Service code, which is a |
michael@0 | 2193 | // 2 or 3 digits that uniquely specifies the Supplementary Service |
michael@0 | 2194 | // associated with the MMI code. |
michael@0 | 2195 | pattern += "(\\d{2,3})"; |
michael@0 | 2196 | |
michael@0 | 2197 | // Groups from 4 to 9 looks for the MMI Supplementary Information SIA, |
michael@0 | 2198 | // SIB and SIC. SIA may comprise e.g. a PIN code or Directory Number, |
michael@0 | 2199 | // SIB may be used to specify the tele or bearer service and SIC to |
michael@0 | 2200 | // specify the value of the "No Reply Condition Timer". Where a particular |
michael@0 | 2201 | // service request does not require any SI, "*SI" is not entered. The use |
michael@0 | 2202 | // of SIA, SIB and SIC is optional and shall be entered in any of the |
michael@0 | 2203 | // following formats: |
michael@0 | 2204 | // - *SIA*SIB*SIC# |
michael@0 | 2205 | // - *SIA*SIB# |
michael@0 | 2206 | // - *SIA**SIC# |
michael@0 | 2207 | // - *SIA# |
michael@0 | 2208 | // - **SIB*SIC# |
michael@0 | 2209 | // - ***SISC# |
michael@0 | 2210 | pattern += "(\\*([^*#]*)(\\*([^*#]*)(\\*([^*#]*)"; |
michael@0 | 2211 | |
michael@0 | 2212 | // The eleventh group takes the password for the case of a password |
michael@0 | 2213 | // registration procedure. |
michael@0 | 2214 | pattern += "(\\*([^*#]*))?)?)?)?#)"; |
michael@0 | 2215 | |
michael@0 | 2216 | // The last group takes the dial string after the #. |
michael@0 | 2217 | pattern += "([^#]*)"; |
michael@0 | 2218 | |
michael@0 | 2219 | this._mmiRegExp = new RegExp(pattern); |
michael@0 | 2220 | } |
michael@0 | 2221 | |
michael@0 | 2222 | // Regex only applys for those well-defined MMI strings (refer to TS.22.030 |
michael@0 | 2223 | // Annex B), otherwise, null should be the expected return value. |
michael@0 | 2224 | return this._mmiRegExp.exec(mmiString); |
michael@0 | 2225 | }, |
michael@0 | 2226 | |
michael@0 | 2227 | /** |
michael@0 | 2228 | * Helper to parse # string. TS.22.030 Figure 3.5.3.2. |
michael@0 | 2229 | */ |
michael@0 | 2230 | _isPoundString: function(mmiString) { |
michael@0 | 2231 | return (mmiString.charAt(mmiString.length - 1) === MMI_END_OF_USSD); |
michael@0 | 2232 | }, |
michael@0 | 2233 | |
michael@0 | 2234 | /** |
michael@0 | 2235 | * Helper to parse short string. TS.22.030 Figure 3.5.3.2. |
michael@0 | 2236 | */ |
michael@0 | 2237 | _isMMIShortString: function(mmiString) { |
michael@0 | 2238 | if (mmiString.length > 2) { |
michael@0 | 2239 | return false; |
michael@0 | 2240 | } |
michael@0 | 2241 | |
michael@0 | 2242 | if (this._isEmergencyNumber(mmiString)) { |
michael@0 | 2243 | return false; |
michael@0 | 2244 | } |
michael@0 | 2245 | |
michael@0 | 2246 | // In a call case. |
michael@0 | 2247 | if (Object.getOwnPropertyNames(this.currentCalls).length > 0) { |
michael@0 | 2248 | return true; |
michael@0 | 2249 | } |
michael@0 | 2250 | |
michael@0 | 2251 | if ((mmiString.length != 2) || (mmiString.charAt(0) !== '1')) { |
michael@0 | 2252 | return true; |
michael@0 | 2253 | } |
michael@0 | 2254 | |
michael@0 | 2255 | return false; |
michael@0 | 2256 | }, |
michael@0 | 2257 | |
michael@0 | 2258 | sendMMI: function(options) { |
michael@0 | 2259 | if (DEBUG) { |
michael@0 | 2260 | this.context.debug("SendMMI " + JSON.stringify(options)); |
michael@0 | 2261 | } |
michael@0 | 2262 | let mmiString = options.mmi; |
michael@0 | 2263 | let mmi = this._parseMMI(mmiString); |
michael@0 | 2264 | |
michael@0 | 2265 | let _sendMMIError = (function(errorMsg, mmiServiceCode) { |
michael@0 | 2266 | options.success = false; |
michael@0 | 2267 | options.errorMsg = errorMsg; |
michael@0 | 2268 | if (mmiServiceCode) { |
michael@0 | 2269 | options.mmiServiceCode = mmiServiceCode; |
michael@0 | 2270 | } |
michael@0 | 2271 | this.sendChromeMessage(options); |
michael@0 | 2272 | }).bind(this); |
michael@0 | 2273 | |
michael@0 | 2274 | function _isValidPINPUKRequest(mmiServiceCode) { |
michael@0 | 2275 | // The only allowed MMI procedure for ICC PIN, PIN2, PUK and PUK2 handling |
michael@0 | 2276 | // is "Registration" (**). |
michael@0 | 2277 | if (!mmi.procedure || mmi.procedure != MMI_PROCEDURE_REGISTRATION ) { |
michael@0 | 2278 | _sendMMIError(MMI_ERROR_KS_INVALID_ACTION, mmiServiceCode); |
michael@0 | 2279 | return false; |
michael@0 | 2280 | } |
michael@0 | 2281 | |
michael@0 | 2282 | if (!mmi.sia || !mmi.sia.length || !mmi.sib || !mmi.sib.length || |
michael@0 | 2283 | !mmi.sic || !mmi.sic.length) { |
michael@0 | 2284 | _sendMMIError(MMI_ERROR_KS_ERROR, mmiServiceCode); |
michael@0 | 2285 | return false; |
michael@0 | 2286 | } |
michael@0 | 2287 | |
michael@0 | 2288 | if (mmi.sib != mmi.sic) { |
michael@0 | 2289 | _sendMMIError(MMI_ERROR_KS_MISMATCH_PIN, mmiServiceCode); |
michael@0 | 2290 | return false; |
michael@0 | 2291 | } |
michael@0 | 2292 | |
michael@0 | 2293 | if (mmi.sia.length < 4 || mmi.sia.length > 8 || |
michael@0 | 2294 | mmi.sib.length < 4 || mmi.sib.length > 8 || |
michael@0 | 2295 | mmi.sic.length < 4 || mmi.sic.length > 8) { |
michael@0 | 2296 | _sendMMIError(MMI_ERROR_KS_INVALID_PIN, mmiServiceCode); |
michael@0 | 2297 | return false; |
michael@0 | 2298 | } |
michael@0 | 2299 | |
michael@0 | 2300 | return true; |
michael@0 | 2301 | } |
michael@0 | 2302 | |
michael@0 | 2303 | let _isRadioAvailable = (function(mmiServiceCode) { |
michael@0 | 2304 | if (this.radioState !== GECKO_RADIOSTATE_READY) { |
michael@0 | 2305 | _sendMMIError(GECKO_ERROR_RADIO_NOT_AVAILABLE, mmiServiceCode); |
michael@0 | 2306 | return false; |
michael@0 | 2307 | } |
michael@0 | 2308 | return true; |
michael@0 | 2309 | }).bind(this); |
michael@0 | 2310 | |
michael@0 | 2311 | // If we couldn't parse the MMI code, we'll send it as an USSD request. |
michael@0 | 2312 | if (mmi === null) { |
michael@0 | 2313 | if (this._ussdSession) { |
michael@0 | 2314 | if (!_isRadioAvailable(MMI_KS_SC_USSD)) { |
michael@0 | 2315 | return; |
michael@0 | 2316 | } |
michael@0 | 2317 | options.ussd = mmiString; |
michael@0 | 2318 | this.sendUSSD(options); |
michael@0 | 2319 | return; |
michael@0 | 2320 | } |
michael@0 | 2321 | |
michael@0 | 2322 | _sendMMIError(MMI_ERROR_KS_ERROR); |
michael@0 | 2323 | return; |
michael@0 | 2324 | } |
michael@0 | 2325 | |
michael@0 | 2326 | if (DEBUG) { |
michael@0 | 2327 | this.context.debug("MMI " + JSON.stringify(mmi)); |
michael@0 | 2328 | } |
michael@0 | 2329 | |
michael@0 | 2330 | // We check if the MMI service code is supported and in that case we |
michael@0 | 2331 | // trigger the appropriate RIL request if possible. |
michael@0 | 2332 | let sc = mmi.serviceCode; |
michael@0 | 2333 | switch (sc) { |
michael@0 | 2334 | // Call forwarding |
michael@0 | 2335 | case MMI_SC_CFU: |
michael@0 | 2336 | case MMI_SC_CF_BUSY: |
michael@0 | 2337 | case MMI_SC_CF_NO_REPLY: |
michael@0 | 2338 | case MMI_SC_CF_NOT_REACHABLE: |
michael@0 | 2339 | case MMI_SC_CF_ALL: |
michael@0 | 2340 | case MMI_SC_CF_ALL_CONDITIONAL: |
michael@0 | 2341 | if (!_isRadioAvailable(MMI_KS_SC_CALL_FORWARDING)) { |
michael@0 | 2342 | return; |
michael@0 | 2343 | } |
michael@0 | 2344 | // Call forwarding requires at least an action, given by the MMI |
michael@0 | 2345 | // procedure, and a reason, given by the MMI service code, but there |
michael@0 | 2346 | // is no way that we get this far without a valid procedure or service |
michael@0 | 2347 | // code. |
michael@0 | 2348 | options.mmiServiceCode = MMI_KS_SC_CALL_FORWARDING; |
michael@0 | 2349 | options.action = MMI_PROC_TO_CF_ACTION[mmi.procedure]; |
michael@0 | 2350 | options.reason = MMI_SC_TO_CF_REASON[sc]; |
michael@0 | 2351 | options.number = mmi.sia; |
michael@0 | 2352 | options.serviceClass = this._siToServiceClass(mmi.sib); |
michael@0 | 2353 | if (options.action == CALL_FORWARD_ACTION_QUERY_STATUS) { |
michael@0 | 2354 | this.queryCallForwardStatus(options); |
michael@0 | 2355 | return; |
michael@0 | 2356 | } |
michael@0 | 2357 | |
michael@0 | 2358 | options.isSetCallForward = true; |
michael@0 | 2359 | options.timeSeconds = mmi.sic; |
michael@0 | 2360 | this.setCallForward(options); |
michael@0 | 2361 | return; |
michael@0 | 2362 | |
michael@0 | 2363 | // Change the current ICC PIN number. |
michael@0 | 2364 | case MMI_SC_PIN: |
michael@0 | 2365 | // As defined in TS.122.030 6.6.2 to change the ICC PIN we should expect |
michael@0 | 2366 | // an MMI code of the form **04*OLD_PIN*NEW_PIN*NEW_PIN#, where old PIN |
michael@0 | 2367 | // should be entered as the SIA parameter and the new PIN as SIB and |
michael@0 | 2368 | // SIC. |
michael@0 | 2369 | if (!_isRadioAvailable(MMI_KS_SC_PIN) || |
michael@0 | 2370 | !_isValidPINPUKRequest(MMI_KS_SC_PIN)) { |
michael@0 | 2371 | return; |
michael@0 | 2372 | } |
michael@0 | 2373 | |
michael@0 | 2374 | options.mmiServiceCode = MMI_KS_SC_PIN; |
michael@0 | 2375 | options.pin = mmi.sia; |
michael@0 | 2376 | options.newPin = mmi.sib; |
michael@0 | 2377 | this.changeICCPIN(options); |
michael@0 | 2378 | return; |
michael@0 | 2379 | |
michael@0 | 2380 | // Change the current ICC PIN2 number. |
michael@0 | 2381 | case MMI_SC_PIN2: |
michael@0 | 2382 | // As defined in TS.122.030 6.6.2 to change the ICC PIN2 we should |
michael@0 | 2383 | // enter and MMI code of the form **042*OLD_PIN2*NEW_PIN2*NEW_PIN2#, |
michael@0 | 2384 | // where the old PIN2 should be entered as the SIA parameter and the |
michael@0 | 2385 | // new PIN2 as SIB and SIC. |
michael@0 | 2386 | if (!_isRadioAvailable(MMI_KS_SC_PIN2) || |
michael@0 | 2387 | !_isValidPINPUKRequest(MMI_KS_SC_PIN2)) { |
michael@0 | 2388 | return; |
michael@0 | 2389 | } |
michael@0 | 2390 | |
michael@0 | 2391 | options.mmiServiceCode = MMI_KS_SC_PIN2; |
michael@0 | 2392 | options.pin = mmi.sia; |
michael@0 | 2393 | options.newPin = mmi.sib; |
michael@0 | 2394 | this.changeICCPIN2(options); |
michael@0 | 2395 | return; |
michael@0 | 2396 | |
michael@0 | 2397 | // Unblock ICC PIN. |
michael@0 | 2398 | case MMI_SC_PUK: |
michael@0 | 2399 | // As defined in TS.122.030 6.6.3 to unblock the ICC PIN we should |
michael@0 | 2400 | // enter an MMI code of the form **05*PUK*NEW_PIN*NEW_PIN#, where PUK |
michael@0 | 2401 | // should be entered as the SIA parameter and the new PIN as SIB and |
michael@0 | 2402 | // SIC. |
michael@0 | 2403 | if (!_isRadioAvailable(MMI_KS_SC_PUK) || |
michael@0 | 2404 | !_isValidPINPUKRequest(MMI_KS_SC_PUK)) { |
michael@0 | 2405 | return; |
michael@0 | 2406 | } |
michael@0 | 2407 | |
michael@0 | 2408 | options.mmiServiceCode = MMI_KS_SC_PUK; |
michael@0 | 2409 | options.puk = mmi.sia; |
michael@0 | 2410 | options.newPin = mmi.sib; |
michael@0 | 2411 | this.enterICCPUK(options); |
michael@0 | 2412 | return; |
michael@0 | 2413 | |
michael@0 | 2414 | // Unblock ICC PIN2. |
michael@0 | 2415 | case MMI_SC_PUK2: |
michael@0 | 2416 | // As defined in TS.122.030 6.6.3 to unblock the ICC PIN2 we should |
michael@0 | 2417 | // enter an MMI code of the form **052*PUK2*NEW_PIN2*NEW_PIN2#, where |
michael@0 | 2418 | // PUK2 should be entered as the SIA parameter and the new PIN2 as SIB |
michael@0 | 2419 | // and SIC. |
michael@0 | 2420 | if (!_isRadioAvailable(MMI_KS_SC_PUK2) || |
michael@0 | 2421 | !_isValidPINPUKRequest(MMI_KS_SC_PUK2)) { |
michael@0 | 2422 | return; |
michael@0 | 2423 | } |
michael@0 | 2424 | |
michael@0 | 2425 | options.mmiServiceCode = MMI_KS_SC_PUK2; |
michael@0 | 2426 | options.puk = mmi.sia; |
michael@0 | 2427 | options.newPin = mmi.sib; |
michael@0 | 2428 | this.enterICCPUK2(options); |
michael@0 | 2429 | return; |
michael@0 | 2430 | |
michael@0 | 2431 | // IMEI |
michael@0 | 2432 | case MMI_SC_IMEI: |
michael@0 | 2433 | // A device's IMEI can't change, so we only need to request it once. |
michael@0 | 2434 | if (this.IMEI == null) { |
michael@0 | 2435 | this.getIMEI(options); |
michael@0 | 2436 | return; |
michael@0 | 2437 | } |
michael@0 | 2438 | // If we already had the device's IMEI, we just send it to chrome. |
michael@0 | 2439 | options.mmiServiceCode = MMI_KS_SC_IMEI; |
michael@0 | 2440 | options.success = true; |
michael@0 | 2441 | options.statusMessage = this.IMEI; |
michael@0 | 2442 | this.sendChromeMessage(options); |
michael@0 | 2443 | return; |
michael@0 | 2444 | |
michael@0 | 2445 | // CLIP |
michael@0 | 2446 | case MMI_SC_CLIP: |
michael@0 | 2447 | options.mmiServiceCode = MMI_KS_SC_CLIP; |
michael@0 | 2448 | options.procedure = mmi.procedure; |
michael@0 | 2449 | if (options.procedure === MMI_PROCEDURE_INTERROGATION) { |
michael@0 | 2450 | this.queryCLIP(options); |
michael@0 | 2451 | } else { |
michael@0 | 2452 | _sendMMIError(MMI_ERROR_KS_NOT_SUPPORTED, MMI_KS_SC_CLIP); |
michael@0 | 2453 | } |
michael@0 | 2454 | return; |
michael@0 | 2455 | |
michael@0 | 2456 | // CLIR (non-temporary ones) |
michael@0 | 2457 | // TODO: Both dial() and sendMMI() functions should be unified at some |
michael@0 | 2458 | // point in the future. In the mean time we handle temporary CLIR MMI |
michael@0 | 2459 | // commands through the dial() function. Please see bug 889737. |
michael@0 | 2460 | case MMI_SC_CLIR: |
michael@0 | 2461 | options.mmiServiceCode = MMI_KS_SC_CLIR; |
michael@0 | 2462 | options.procedure = mmi.procedure; |
michael@0 | 2463 | switch (options.procedure) { |
michael@0 | 2464 | case MMI_PROCEDURE_INTERROGATION: |
michael@0 | 2465 | this.getCLIR(options); |
michael@0 | 2466 | return; |
michael@0 | 2467 | case MMI_PROCEDURE_ACTIVATION: |
michael@0 | 2468 | options.clirMode = CLIR_INVOCATION; |
michael@0 | 2469 | break; |
michael@0 | 2470 | case MMI_PROCEDURE_DEACTIVATION: |
michael@0 | 2471 | options.clirMode = CLIR_SUPPRESSION; |
michael@0 | 2472 | break; |
michael@0 | 2473 | default: |
michael@0 | 2474 | _sendMMIError(MMI_ERROR_KS_NOT_SUPPORTED, MMI_KS_SC_CLIR); |
michael@0 | 2475 | return; |
michael@0 | 2476 | } |
michael@0 | 2477 | options.isSetCLIR = true; |
michael@0 | 2478 | this.setCLIR(options); |
michael@0 | 2479 | return; |
michael@0 | 2480 | |
michael@0 | 2481 | // Call barring |
michael@0 | 2482 | case MMI_SC_BAOC: |
michael@0 | 2483 | case MMI_SC_BAOIC: |
michael@0 | 2484 | case MMI_SC_BAOICxH: |
michael@0 | 2485 | case MMI_SC_BAIC: |
michael@0 | 2486 | case MMI_SC_BAICr: |
michael@0 | 2487 | case MMI_SC_BA_ALL: |
michael@0 | 2488 | case MMI_SC_BA_MO: |
michael@0 | 2489 | case MMI_SC_BA_MT: |
michael@0 | 2490 | options.mmiServiceCode = MMI_KS_SC_CALL_BARRING; |
michael@0 | 2491 | options.password = mmi.sia || ""; |
michael@0 | 2492 | options.serviceClass = this._siToServiceClass(mmi.sib); |
michael@0 | 2493 | options.facility = MMI_SC_TO_CB_FACILITY[sc]; |
michael@0 | 2494 | options.procedure = mmi.procedure; |
michael@0 | 2495 | if (mmi.procedure === MMI_PROCEDURE_INTERROGATION) { |
michael@0 | 2496 | this.queryICCFacilityLock(options); |
michael@0 | 2497 | return; |
michael@0 | 2498 | } |
michael@0 | 2499 | if (mmi.procedure === MMI_PROCEDURE_ACTIVATION) { |
michael@0 | 2500 | options.enabled = 1; |
michael@0 | 2501 | } else if (mmi.procedure === MMI_PROCEDURE_DEACTIVATION) { |
michael@0 | 2502 | options.enabled = 0; |
michael@0 | 2503 | } else { |
michael@0 | 2504 | _sendMMIError(MMI_ERROR_KS_NOT_SUPPORTED, MMI_KS_SC_CALL_BARRING); |
michael@0 | 2505 | return; |
michael@0 | 2506 | } |
michael@0 | 2507 | this.setICCFacilityLock(options); |
michael@0 | 2508 | return; |
michael@0 | 2509 | |
michael@0 | 2510 | // Call waiting |
michael@0 | 2511 | case MMI_SC_CALL_WAITING: |
michael@0 | 2512 | if (!_isRadioAvailable(MMI_KS_SC_CALL_WAITING)) { |
michael@0 | 2513 | return; |
michael@0 | 2514 | } |
michael@0 | 2515 | |
michael@0 | 2516 | options.mmiServiceCode = MMI_KS_SC_CALL_WAITING; |
michael@0 | 2517 | |
michael@0 | 2518 | if (mmi.procedure === MMI_PROCEDURE_INTERROGATION) { |
michael@0 | 2519 | this._handleQueryMMICallWaiting(options); |
michael@0 | 2520 | return; |
michael@0 | 2521 | } |
michael@0 | 2522 | |
michael@0 | 2523 | if (mmi.procedure === MMI_PROCEDURE_ACTIVATION) { |
michael@0 | 2524 | options.enabled = true; |
michael@0 | 2525 | } else if (mmi.procedure === MMI_PROCEDURE_DEACTIVATION) { |
michael@0 | 2526 | options.enabled = false; |
michael@0 | 2527 | } else { |
michael@0 | 2528 | _sendMMIError(MMI_ERROR_KS_NOT_SUPPORTED, MMI_KS_SC_CALL_WAITING); |
michael@0 | 2529 | return; |
michael@0 | 2530 | } |
michael@0 | 2531 | |
michael@0 | 2532 | options.serviceClass = this._siToServiceClass(mmi.sia); |
michael@0 | 2533 | this._handleSetMMICallWaiting(options); |
michael@0 | 2534 | return; |
michael@0 | 2535 | } |
michael@0 | 2536 | |
michael@0 | 2537 | // If the MMI code is not a known code and is a recognized USSD request, |
michael@0 | 2538 | // it shall still be sent as a USSD request. |
michael@0 | 2539 | if (mmi.fullMMI) { |
michael@0 | 2540 | if (!_isRadioAvailable(MMI_KS_SC_USSD)) { |
michael@0 | 2541 | return; |
michael@0 | 2542 | } |
michael@0 | 2543 | |
michael@0 | 2544 | options.ussd = mmi.fullMMI; |
michael@0 | 2545 | options.mmiServiceCode = MMI_KS_SC_USSD; |
michael@0 | 2546 | this.sendUSSD(options); |
michael@0 | 2547 | return; |
michael@0 | 2548 | } |
michael@0 | 2549 | |
michael@0 | 2550 | // At this point, the MMI string is considered as not valid MMI code and |
michael@0 | 2551 | // not valid USSD code. |
michael@0 | 2552 | _sendMMIError(MMI_ERROR_KS_ERROR); |
michael@0 | 2553 | }, |
michael@0 | 2554 | |
michael@0 | 2555 | /** |
michael@0 | 2556 | * Send USSD. |
michael@0 | 2557 | * |
michael@0 | 2558 | * @param ussd |
michael@0 | 2559 | * String containing the USSD code. |
michael@0 | 2560 | * |
michael@0 | 2561 | */ |
michael@0 | 2562 | sendUSSD: function(options) { |
michael@0 | 2563 | let Buf = this.context.Buf; |
michael@0 | 2564 | Buf.newParcel(REQUEST_SEND_USSD, options); |
michael@0 | 2565 | Buf.writeString(options.ussd); |
michael@0 | 2566 | Buf.sendParcel(); |
michael@0 | 2567 | }, |
michael@0 | 2568 | |
michael@0 | 2569 | /** |
michael@0 | 2570 | * Cancel pending USSD. |
michael@0 | 2571 | */ |
michael@0 | 2572 | cancelUSSD: function(options) { |
michael@0 | 2573 | options.mmiServiceCode = MMI_KS_SC_USSD; |
michael@0 | 2574 | this.context.Buf.simpleRequest(REQUEST_CANCEL_USSD, options); |
michael@0 | 2575 | }, |
michael@0 | 2576 | |
michael@0 | 2577 | /** |
michael@0 | 2578 | * Queries current call forward rules. |
michael@0 | 2579 | * |
michael@0 | 2580 | * @param reason |
michael@0 | 2581 | * One of nsIDOMMozMobileCFInfo.CALL_FORWARD_REASON_* constants. |
michael@0 | 2582 | * @param serviceClass |
michael@0 | 2583 | * One of ICC_SERVICE_CLASS_* constants. |
michael@0 | 2584 | * @param number |
michael@0 | 2585 | * Phone number of forwarding address. |
michael@0 | 2586 | */ |
michael@0 | 2587 | queryCallForwardStatus: function(options) { |
michael@0 | 2588 | let Buf = this.context.Buf; |
michael@0 | 2589 | let number = options.number || ""; |
michael@0 | 2590 | Buf.newParcel(REQUEST_QUERY_CALL_FORWARD_STATUS, options); |
michael@0 | 2591 | Buf.writeInt32(CALL_FORWARD_ACTION_QUERY_STATUS); |
michael@0 | 2592 | Buf.writeInt32(options.reason); |
michael@0 | 2593 | Buf.writeInt32(options.serviceClass || ICC_SERVICE_CLASS_NONE); |
michael@0 | 2594 | Buf.writeInt32(this._toaFromString(number)); |
michael@0 | 2595 | Buf.writeString(number); |
michael@0 | 2596 | Buf.writeInt32(0); |
michael@0 | 2597 | Buf.sendParcel(); |
michael@0 | 2598 | }, |
michael@0 | 2599 | |
michael@0 | 2600 | /** |
michael@0 | 2601 | * Configures call forward rule. |
michael@0 | 2602 | * |
michael@0 | 2603 | * @param action |
michael@0 | 2604 | * One of nsIDOMMozMobileCFInfo.CALL_FORWARD_ACTION_* constants. |
michael@0 | 2605 | * @param reason |
michael@0 | 2606 | * One of nsIDOMMozMobileCFInfo.CALL_FORWARD_REASON_* constants. |
michael@0 | 2607 | * @param serviceClass |
michael@0 | 2608 | * One of ICC_SERVICE_CLASS_* constants. |
michael@0 | 2609 | * @param number |
michael@0 | 2610 | * Phone number of forwarding address. |
michael@0 | 2611 | * @param timeSeconds |
michael@0 | 2612 | * Time in seconds to wait beforec all is forwarded. |
michael@0 | 2613 | */ |
michael@0 | 2614 | setCallForward: function(options) { |
michael@0 | 2615 | let Buf = this.context.Buf; |
michael@0 | 2616 | Buf.newParcel(REQUEST_SET_CALL_FORWARD, options); |
michael@0 | 2617 | Buf.writeInt32(options.action); |
michael@0 | 2618 | Buf.writeInt32(options.reason); |
michael@0 | 2619 | Buf.writeInt32(options.serviceClass); |
michael@0 | 2620 | Buf.writeInt32(this._toaFromString(options.number)); |
michael@0 | 2621 | Buf.writeString(options.number); |
michael@0 | 2622 | Buf.writeInt32(options.timeSeconds); |
michael@0 | 2623 | Buf.sendParcel(); |
michael@0 | 2624 | }, |
michael@0 | 2625 | |
michael@0 | 2626 | /** |
michael@0 | 2627 | * Queries current call barring rules. |
michael@0 | 2628 | * |
michael@0 | 2629 | * @param program |
michael@0 | 2630 | * One of nsIDOMMozMobileConnection.CALL_BARRING_PROGRAM_* constants. |
michael@0 | 2631 | * @param serviceClass |
michael@0 | 2632 | * One of ICC_SERVICE_CLASS_* constants. |
michael@0 | 2633 | */ |
michael@0 | 2634 | queryCallBarringStatus: function(options) { |
michael@0 | 2635 | options.facility = CALL_BARRING_PROGRAM_TO_FACILITY[options.program]; |
michael@0 | 2636 | options.password = ""; // For query no need to provide it. |
michael@0 | 2637 | this.queryICCFacilityLock(options); |
michael@0 | 2638 | }, |
michael@0 | 2639 | |
michael@0 | 2640 | /** |
michael@0 | 2641 | * Configures call barring rule. |
michael@0 | 2642 | * |
michael@0 | 2643 | * @param program |
michael@0 | 2644 | * One of nsIDOMMozMobileConnection.CALL_BARRING_PROGRAM_* constants. |
michael@0 | 2645 | * @param enabled |
michael@0 | 2646 | * Enable or disable the call barring. |
michael@0 | 2647 | * @param password |
michael@0 | 2648 | * Barring password. |
michael@0 | 2649 | * @param serviceClass |
michael@0 | 2650 | * One of ICC_SERVICE_CLASS_* constants. |
michael@0 | 2651 | */ |
michael@0 | 2652 | setCallBarring: function(options) { |
michael@0 | 2653 | options.facility = CALL_BARRING_PROGRAM_TO_FACILITY[options.program]; |
michael@0 | 2654 | this.setICCFacilityLock(options); |
michael@0 | 2655 | }, |
michael@0 | 2656 | |
michael@0 | 2657 | /** |
michael@0 | 2658 | * Change call barring facility password. |
michael@0 | 2659 | * |
michael@0 | 2660 | * @param pin |
michael@0 | 2661 | * Old password. |
michael@0 | 2662 | * @param newPin |
michael@0 | 2663 | * New password. |
michael@0 | 2664 | */ |
michael@0 | 2665 | changeCallBarringPassword: function(options) { |
michael@0 | 2666 | let Buf = this.context.Buf; |
michael@0 | 2667 | Buf.newParcel(REQUEST_CHANGE_BARRING_PASSWORD, options); |
michael@0 | 2668 | Buf.writeInt32(3); |
michael@0 | 2669 | // Set facility to ICC_CB_FACILITY_BA_ALL by following TS.22.030 clause |
michael@0 | 2670 | // 6.5.4 and Table B.1. |
michael@0 | 2671 | Buf.writeString(ICC_CB_FACILITY_BA_ALL); |
michael@0 | 2672 | Buf.writeString(options.pin); |
michael@0 | 2673 | Buf.writeString(options.newPin); |
michael@0 | 2674 | Buf.sendParcel(); |
michael@0 | 2675 | }, |
michael@0 | 2676 | |
michael@0 | 2677 | /** |
michael@0 | 2678 | * Handle STK CALL_SET_UP request. |
michael@0 | 2679 | * |
michael@0 | 2680 | * @param hasConfirmed |
michael@0 | 2681 | * Does use have confirmed the call requested from ICC? |
michael@0 | 2682 | */ |
michael@0 | 2683 | stkHandleCallSetup: function(options) { |
michael@0 | 2684 | let Buf = this.context.Buf; |
michael@0 | 2685 | Buf.newParcel(REQUEST_STK_HANDLE_CALL_SETUP_REQUESTED_FROM_SIM); |
michael@0 | 2686 | Buf.writeInt32(1); |
michael@0 | 2687 | Buf.writeInt32(options.hasConfirmed ? 1 : 0); |
michael@0 | 2688 | Buf.sendParcel(); |
michael@0 | 2689 | }, |
michael@0 | 2690 | |
michael@0 | 2691 | /** |
michael@0 | 2692 | * Send STK Profile Download. |
michael@0 | 2693 | * |
michael@0 | 2694 | * @param profile Profile supported by ME. |
michael@0 | 2695 | */ |
michael@0 | 2696 | sendStkTerminalProfile: function(profile) { |
michael@0 | 2697 | let Buf = this.context.Buf; |
michael@0 | 2698 | let GsmPDUHelper = this.context.GsmPDUHelper; |
michael@0 | 2699 | |
michael@0 | 2700 | Buf.newParcel(REQUEST_STK_SET_PROFILE); |
michael@0 | 2701 | Buf.writeInt32(profile.length * 2); |
michael@0 | 2702 | for (let i = 0; i < profile.length; i++) { |
michael@0 | 2703 | GsmPDUHelper.writeHexOctet(profile[i]); |
michael@0 | 2704 | } |
michael@0 | 2705 | Buf.writeInt32(0); |
michael@0 | 2706 | Buf.sendParcel(); |
michael@0 | 2707 | }, |
michael@0 | 2708 | |
michael@0 | 2709 | /** |
michael@0 | 2710 | * Send STK terminal response. |
michael@0 | 2711 | * |
michael@0 | 2712 | * @param command |
michael@0 | 2713 | * @param deviceIdentities |
michael@0 | 2714 | * @param resultCode |
michael@0 | 2715 | * @param [optional] itemIdentifier |
michael@0 | 2716 | * @param [optional] input |
michael@0 | 2717 | * @param [optional] isYesNo |
michael@0 | 2718 | * @param [optional] hasConfirmed |
michael@0 | 2719 | * @param [optional] localInfo |
michael@0 | 2720 | * @param [optional] timer |
michael@0 | 2721 | */ |
michael@0 | 2722 | sendStkTerminalResponse: function(response) { |
michael@0 | 2723 | if (response.hasConfirmed !== undefined) { |
michael@0 | 2724 | this.stkHandleCallSetup(response); |
michael@0 | 2725 | return; |
michael@0 | 2726 | } |
michael@0 | 2727 | |
michael@0 | 2728 | let Buf = this.context.Buf; |
michael@0 | 2729 | let ComprehensionTlvHelper = this.context.ComprehensionTlvHelper; |
michael@0 | 2730 | let GsmPDUHelper = this.context.GsmPDUHelper; |
michael@0 | 2731 | |
michael@0 | 2732 | let command = response.command; |
michael@0 | 2733 | Buf.newParcel(REQUEST_STK_SEND_TERMINAL_RESPONSE); |
michael@0 | 2734 | |
michael@0 | 2735 | // 1st mark for Parcel size |
michael@0 | 2736 | Buf.startCalOutgoingSize(function(size) { |
michael@0 | 2737 | // Parcel size is in string length, which costs 2 uint8 per char. |
michael@0 | 2738 | Buf.writeInt32(size / 2); |
michael@0 | 2739 | }); |
michael@0 | 2740 | |
michael@0 | 2741 | // Command Details |
michael@0 | 2742 | GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_COMMAND_DETAILS | |
michael@0 | 2743 | COMPREHENSIONTLV_FLAG_CR); |
michael@0 | 2744 | GsmPDUHelper.writeHexOctet(3); |
michael@0 | 2745 | if (response.command) { |
michael@0 | 2746 | GsmPDUHelper.writeHexOctet(command.commandNumber); |
michael@0 | 2747 | GsmPDUHelper.writeHexOctet(command.typeOfCommand); |
michael@0 | 2748 | GsmPDUHelper.writeHexOctet(command.commandQualifier); |
michael@0 | 2749 | } else { |
michael@0 | 2750 | GsmPDUHelper.writeHexOctet(0x00); |
michael@0 | 2751 | GsmPDUHelper.writeHexOctet(0x00); |
michael@0 | 2752 | GsmPDUHelper.writeHexOctet(0x00); |
michael@0 | 2753 | } |
michael@0 | 2754 | |
michael@0 | 2755 | // Device Identifier |
michael@0 | 2756 | // According to TS102.223/TS31.111 section 6.8 Structure of |
michael@0 | 2757 | // TERMINAL RESPONSE, "For all SIMPLE-TLV objects with Min=N, |
michael@0 | 2758 | // the ME should set the CR(comprehension required) flag to |
michael@0 | 2759 | // comprehension not required.(CR=0)" |
michael@0 | 2760 | // Since DEVICE_IDENTITIES and DURATION TLVs have Min=N, |
michael@0 | 2761 | // the CR flag is not set. |
michael@0 | 2762 | GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_DEVICE_ID); |
michael@0 | 2763 | GsmPDUHelper.writeHexOctet(2); |
michael@0 | 2764 | GsmPDUHelper.writeHexOctet(STK_DEVICE_ID_ME); |
michael@0 | 2765 | GsmPDUHelper.writeHexOctet(STK_DEVICE_ID_SIM); |
michael@0 | 2766 | |
michael@0 | 2767 | // Result |
michael@0 | 2768 | GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_RESULT | |
michael@0 | 2769 | COMPREHENSIONTLV_FLAG_CR); |
michael@0 | 2770 | GsmPDUHelper.writeHexOctet(1); |
michael@0 | 2771 | GsmPDUHelper.writeHexOctet(response.resultCode); |
michael@0 | 2772 | |
michael@0 | 2773 | // Item Identifier |
michael@0 | 2774 | if (response.itemIdentifier != null) { |
michael@0 | 2775 | GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_ITEM_ID | |
michael@0 | 2776 | COMPREHENSIONTLV_FLAG_CR); |
michael@0 | 2777 | GsmPDUHelper.writeHexOctet(1); |
michael@0 | 2778 | GsmPDUHelper.writeHexOctet(response.itemIdentifier); |
michael@0 | 2779 | } |
michael@0 | 2780 | |
michael@0 | 2781 | // No need to process Text data if user requests help information. |
michael@0 | 2782 | if (response.resultCode != STK_RESULT_HELP_INFO_REQUIRED) { |
michael@0 | 2783 | let text; |
michael@0 | 2784 | if (response.isYesNo !== undefined) { |
michael@0 | 2785 | // GET_INKEY |
michael@0 | 2786 | // When the ME issues a successful TERMINAL RESPONSE for a GET INKEY |
michael@0 | 2787 | // ("Yes/No") command with command qualifier set to "Yes/No", it shall |
michael@0 | 2788 | // supply the value '01' when the answer is "positive" and the value |
michael@0 | 2789 | // '00' when the answer is "negative" in the Text string data object. |
michael@0 | 2790 | text = response.isYesNo ? 0x01 : 0x00; |
michael@0 | 2791 | } else { |
michael@0 | 2792 | text = response.input; |
michael@0 | 2793 | } |
michael@0 | 2794 | |
michael@0 | 2795 | if (text) { |
michael@0 | 2796 | GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_TEXT_STRING | |
michael@0 | 2797 | COMPREHENSIONTLV_FLAG_CR); |
michael@0 | 2798 | |
michael@0 | 2799 | // 2nd mark for text length |
michael@0 | 2800 | Buf.startCalOutgoingSize(function(size) { |
michael@0 | 2801 | // Text length is in number of hexOctets, which costs 4 uint8 per hexOctet. |
michael@0 | 2802 | GsmPDUHelper.writeHexOctet(size / 4); |
michael@0 | 2803 | }); |
michael@0 | 2804 | |
michael@0 | 2805 | let coding = command.options.isUCS2 ? |
michael@0 | 2806 | STK_TEXT_CODING_UCS2 : |
michael@0 | 2807 | (command.options.isPacked ? |
michael@0 | 2808 | STK_TEXT_CODING_GSM_7BIT_PACKED : |
michael@0 | 2809 | STK_TEXT_CODING_GSM_8BIT); |
michael@0 | 2810 | GsmPDUHelper.writeHexOctet(coding); |
michael@0 | 2811 | |
michael@0 | 2812 | // Write Text String. |
michael@0 | 2813 | switch (coding) { |
michael@0 | 2814 | case STK_TEXT_CODING_UCS2: |
michael@0 | 2815 | GsmPDUHelper.writeUCS2String(text); |
michael@0 | 2816 | break; |
michael@0 | 2817 | case STK_TEXT_CODING_GSM_7BIT_PACKED: |
michael@0 | 2818 | GsmPDUHelper.writeStringAsSeptets(text, 0, 0, 0); |
michael@0 | 2819 | break; |
michael@0 | 2820 | case STK_TEXT_CODING_GSM_8BIT: |
michael@0 | 2821 | for (let i = 0; i < text.length; i++) { |
michael@0 | 2822 | GsmPDUHelper.writeHexOctet(text.charCodeAt(i)); |
michael@0 | 2823 | } |
michael@0 | 2824 | break; |
michael@0 | 2825 | } |
michael@0 | 2826 | |
michael@0 | 2827 | // Calculate and write text length to 2nd mark |
michael@0 | 2828 | Buf.stopCalOutgoingSize(); |
michael@0 | 2829 | } |
michael@0 | 2830 | } |
michael@0 | 2831 | |
michael@0 | 2832 | // Local Information |
michael@0 | 2833 | if (response.localInfo) { |
michael@0 | 2834 | let localInfo = response.localInfo; |
michael@0 | 2835 | |
michael@0 | 2836 | // Location Infomation |
michael@0 | 2837 | if (localInfo.locationInfo) { |
michael@0 | 2838 | ComprehensionTlvHelper.writeLocationInfoTlv(localInfo.locationInfo); |
michael@0 | 2839 | } |
michael@0 | 2840 | |
michael@0 | 2841 | // IMEI |
michael@0 | 2842 | if (localInfo.imei != null) { |
michael@0 | 2843 | let imei = localInfo.imei; |
michael@0 | 2844 | if (imei.length == 15) { |
michael@0 | 2845 | imei = imei + "0"; |
michael@0 | 2846 | } |
michael@0 | 2847 | |
michael@0 | 2848 | GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_IMEI); |
michael@0 | 2849 | GsmPDUHelper.writeHexOctet(8); |
michael@0 | 2850 | for (let i = 0; i < imei.length / 2; i++) { |
michael@0 | 2851 | GsmPDUHelper.writeHexOctet(parseInt(imei.substr(i * 2, 2), 16)); |
michael@0 | 2852 | } |
michael@0 | 2853 | } |
michael@0 | 2854 | |
michael@0 | 2855 | // Date and Time Zone |
michael@0 | 2856 | if (localInfo.date != null) { |
michael@0 | 2857 | ComprehensionTlvHelper.writeDateTimeZoneTlv(localInfo.date); |
michael@0 | 2858 | } |
michael@0 | 2859 | |
michael@0 | 2860 | // Language |
michael@0 | 2861 | if (localInfo.language) { |
michael@0 | 2862 | ComprehensionTlvHelper.writeLanguageTlv(localInfo.language); |
michael@0 | 2863 | } |
michael@0 | 2864 | } |
michael@0 | 2865 | |
michael@0 | 2866 | // Timer |
michael@0 | 2867 | if (response.timer) { |
michael@0 | 2868 | let timer = response.timer; |
michael@0 | 2869 | |
michael@0 | 2870 | if (timer.timerId) { |
michael@0 | 2871 | GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_TIMER_IDENTIFIER); |
michael@0 | 2872 | GsmPDUHelper.writeHexOctet(1); |
michael@0 | 2873 | GsmPDUHelper.writeHexOctet(timer.timerId); |
michael@0 | 2874 | } |
michael@0 | 2875 | |
michael@0 | 2876 | if (timer.timerValue) { |
michael@0 | 2877 | ComprehensionTlvHelper.writeTimerValueTlv(timer.timerValue, false); |
michael@0 | 2878 | } |
michael@0 | 2879 | } |
michael@0 | 2880 | |
michael@0 | 2881 | // Calculate and write Parcel size to 1st mark |
michael@0 | 2882 | Buf.stopCalOutgoingSize(); |
michael@0 | 2883 | |
michael@0 | 2884 | Buf.writeInt32(0); |
michael@0 | 2885 | Buf.sendParcel(); |
michael@0 | 2886 | }, |
michael@0 | 2887 | |
michael@0 | 2888 | /** |
michael@0 | 2889 | * Send STK Envelope(Menu Selection) command. |
michael@0 | 2890 | * |
michael@0 | 2891 | * @param itemIdentifier |
michael@0 | 2892 | * @param helpRequested |
michael@0 | 2893 | */ |
michael@0 | 2894 | sendStkMenuSelection: function(command) { |
michael@0 | 2895 | command.tag = BER_MENU_SELECTION_TAG; |
michael@0 | 2896 | command.deviceId = { |
michael@0 | 2897 | sourceId :STK_DEVICE_ID_KEYPAD, |
michael@0 | 2898 | destinationId: STK_DEVICE_ID_SIM |
michael@0 | 2899 | }; |
michael@0 | 2900 | this.sendICCEnvelopeCommand(command); |
michael@0 | 2901 | }, |
michael@0 | 2902 | |
michael@0 | 2903 | /** |
michael@0 | 2904 | * Send STK Envelope(Timer Expiration) command. |
michael@0 | 2905 | * |
michael@0 | 2906 | * @param timer |
michael@0 | 2907 | */ |
michael@0 | 2908 | sendStkTimerExpiration: function(command) { |
michael@0 | 2909 | command.tag = BER_TIMER_EXPIRATION_TAG; |
michael@0 | 2910 | command.deviceId = { |
michael@0 | 2911 | sourceId: STK_DEVICE_ID_ME, |
michael@0 | 2912 | destinationId: STK_DEVICE_ID_SIM |
michael@0 | 2913 | }; |
michael@0 | 2914 | command.timerId = command.timer.timerId; |
michael@0 | 2915 | command.timerValue = command.timer.timerValue; |
michael@0 | 2916 | this.sendICCEnvelopeCommand(command); |
michael@0 | 2917 | }, |
michael@0 | 2918 | |
michael@0 | 2919 | /** |
michael@0 | 2920 | * Send STK Envelope(Event Download) command. |
michael@0 | 2921 | * @param event |
michael@0 | 2922 | */ |
michael@0 | 2923 | sendStkEventDownload: function(command) { |
michael@0 | 2924 | command.tag = BER_EVENT_DOWNLOAD_TAG; |
michael@0 | 2925 | command.eventList = command.event.eventType; |
michael@0 | 2926 | switch (command.eventList) { |
michael@0 | 2927 | case STK_EVENT_TYPE_LOCATION_STATUS: |
michael@0 | 2928 | command.deviceId = { |
michael@0 | 2929 | sourceId :STK_DEVICE_ID_ME, |
michael@0 | 2930 | destinationId: STK_DEVICE_ID_SIM |
michael@0 | 2931 | }; |
michael@0 | 2932 | command.locationStatus = command.event.locationStatus; |
michael@0 | 2933 | // Location info should only be provided when locationStatus is normal. |
michael@0 | 2934 | if (command.locationStatus == STK_SERVICE_STATE_NORMAL) { |
michael@0 | 2935 | command.locationInfo = command.event.locationInfo; |
michael@0 | 2936 | } |
michael@0 | 2937 | break; |
michael@0 | 2938 | case STK_EVENT_TYPE_MT_CALL: |
michael@0 | 2939 | command.deviceId = { |
michael@0 | 2940 | sourceId: STK_DEVICE_ID_NETWORK, |
michael@0 | 2941 | destinationId: STK_DEVICE_ID_SIM |
michael@0 | 2942 | }; |
michael@0 | 2943 | command.transactionId = 0; |
michael@0 | 2944 | command.address = command.event.number; |
michael@0 | 2945 | break; |
michael@0 | 2946 | case STK_EVENT_TYPE_CALL_DISCONNECTED: |
michael@0 | 2947 | command.cause = command.event.error; |
michael@0 | 2948 | // Fall through. |
michael@0 | 2949 | case STK_EVENT_TYPE_CALL_CONNECTED: |
michael@0 | 2950 | command.deviceId = { |
michael@0 | 2951 | sourceId: (command.event.isIssuedByRemote ? |
michael@0 | 2952 | STK_DEVICE_ID_NETWORK : STK_DEVICE_ID_ME), |
michael@0 | 2953 | destinationId: STK_DEVICE_ID_SIM |
michael@0 | 2954 | }; |
michael@0 | 2955 | command.transactionId = 0; |
michael@0 | 2956 | break; |
michael@0 | 2957 | case STK_EVENT_TYPE_USER_ACTIVITY: |
michael@0 | 2958 | command.deviceId = { |
michael@0 | 2959 | sourceId: STK_DEVICE_ID_ME, |
michael@0 | 2960 | destinationId: STK_DEVICE_ID_SIM |
michael@0 | 2961 | }; |
michael@0 | 2962 | break; |
michael@0 | 2963 | case STK_EVENT_TYPE_IDLE_SCREEN_AVAILABLE: |
michael@0 | 2964 | command.deviceId = { |
michael@0 | 2965 | sourceId: STK_DEVICE_ID_DISPLAY, |
michael@0 | 2966 | destinationId: STK_DEVICE_ID_SIM |
michael@0 | 2967 | }; |
michael@0 | 2968 | break; |
michael@0 | 2969 | case STK_EVENT_TYPE_LANGUAGE_SELECTION: |
michael@0 | 2970 | command.deviceId = { |
michael@0 | 2971 | sourceId: STK_DEVICE_ID_ME, |
michael@0 | 2972 | destinationId: STK_DEVICE_ID_SIM |
michael@0 | 2973 | }; |
michael@0 | 2974 | command.language = command.event.language; |
michael@0 | 2975 | break; |
michael@0 | 2976 | case STK_EVENT_TYPE_BROWSER_TERMINATION: |
michael@0 | 2977 | command.deviceId = { |
michael@0 | 2978 | sourceId: STK_DEVICE_ID_ME, |
michael@0 | 2979 | destinationId: STK_DEVICE_ID_SIM |
michael@0 | 2980 | }; |
michael@0 | 2981 | command.terminationCause = command.event.terminationCause; |
michael@0 | 2982 | break; |
michael@0 | 2983 | } |
michael@0 | 2984 | this.sendICCEnvelopeCommand(command); |
michael@0 | 2985 | }, |
michael@0 | 2986 | |
michael@0 | 2987 | /** |
michael@0 | 2988 | * Send REQUEST_STK_SEND_ENVELOPE_COMMAND to ICC. |
michael@0 | 2989 | * |
michael@0 | 2990 | * @param tag |
michael@0 | 2991 | * @patam deviceId |
michael@0 | 2992 | * @param [optioanl] itemIdentifier |
michael@0 | 2993 | * @param [optional] helpRequested |
michael@0 | 2994 | * @param [optional] eventList |
michael@0 | 2995 | * @param [optional] locationStatus |
michael@0 | 2996 | * @param [optional] locationInfo |
michael@0 | 2997 | * @param [optional] address |
michael@0 | 2998 | * @param [optional] transactionId |
michael@0 | 2999 | * @param [optional] cause |
michael@0 | 3000 | * @param [optional] timerId |
michael@0 | 3001 | * @param [optional] timerValue |
michael@0 | 3002 | * @param [optional] terminationCause |
michael@0 | 3003 | */ |
michael@0 | 3004 | sendICCEnvelopeCommand: function(options) { |
michael@0 | 3005 | if (DEBUG) { |
michael@0 | 3006 | this.context.debug("Stk Envelope " + JSON.stringify(options)); |
michael@0 | 3007 | } |
michael@0 | 3008 | |
michael@0 | 3009 | let Buf = this.context.Buf; |
michael@0 | 3010 | let ComprehensionTlvHelper = this.context.ComprehensionTlvHelper; |
michael@0 | 3011 | let GsmPDUHelper = this.context.GsmPDUHelper; |
michael@0 | 3012 | |
michael@0 | 3013 | Buf.newParcel(REQUEST_STK_SEND_ENVELOPE_COMMAND); |
michael@0 | 3014 | |
michael@0 | 3015 | // 1st mark for Parcel size |
michael@0 | 3016 | Buf.startCalOutgoingSize(function(size) { |
michael@0 | 3017 | // Parcel size is in string length, which costs 2 uint8 per char. |
michael@0 | 3018 | Buf.writeInt32(size / 2); |
michael@0 | 3019 | }); |
michael@0 | 3020 | |
michael@0 | 3021 | // Write a BER-TLV |
michael@0 | 3022 | GsmPDUHelper.writeHexOctet(options.tag); |
michael@0 | 3023 | // 2nd mark for BER length |
michael@0 | 3024 | Buf.startCalOutgoingSize(function(size) { |
michael@0 | 3025 | // BER length is in number of hexOctets, which costs 4 uint8 per hexOctet. |
michael@0 | 3026 | GsmPDUHelper.writeHexOctet(size / 4); |
michael@0 | 3027 | }); |
michael@0 | 3028 | |
michael@0 | 3029 | // Device Identifies |
michael@0 | 3030 | GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_DEVICE_ID | |
michael@0 | 3031 | COMPREHENSIONTLV_FLAG_CR); |
michael@0 | 3032 | GsmPDUHelper.writeHexOctet(2); |
michael@0 | 3033 | GsmPDUHelper.writeHexOctet(options.deviceId.sourceId); |
michael@0 | 3034 | GsmPDUHelper.writeHexOctet(options.deviceId.destinationId); |
michael@0 | 3035 | |
michael@0 | 3036 | // Item Identifier |
michael@0 | 3037 | if (options.itemIdentifier != null) { |
michael@0 | 3038 | GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_ITEM_ID | |
michael@0 | 3039 | COMPREHENSIONTLV_FLAG_CR); |
michael@0 | 3040 | GsmPDUHelper.writeHexOctet(1); |
michael@0 | 3041 | GsmPDUHelper.writeHexOctet(options.itemIdentifier); |
michael@0 | 3042 | } |
michael@0 | 3043 | |
michael@0 | 3044 | // Help Request |
michael@0 | 3045 | if (options.helpRequested) { |
michael@0 | 3046 | GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_HELP_REQUEST | |
michael@0 | 3047 | COMPREHENSIONTLV_FLAG_CR); |
michael@0 | 3048 | GsmPDUHelper.writeHexOctet(0); |
michael@0 | 3049 | // Help Request doesn't have value |
michael@0 | 3050 | } |
michael@0 | 3051 | |
michael@0 | 3052 | // Event List |
michael@0 | 3053 | if (options.eventList != null) { |
michael@0 | 3054 | GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_EVENT_LIST | |
michael@0 | 3055 | COMPREHENSIONTLV_FLAG_CR); |
michael@0 | 3056 | GsmPDUHelper.writeHexOctet(1); |
michael@0 | 3057 | GsmPDUHelper.writeHexOctet(options.eventList); |
michael@0 | 3058 | } |
michael@0 | 3059 | |
michael@0 | 3060 | // Location Status |
michael@0 | 3061 | if (options.locationStatus != null) { |
michael@0 | 3062 | let len = options.locationStatus.length; |
michael@0 | 3063 | GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_LOCATION_STATUS | |
michael@0 | 3064 | COMPREHENSIONTLV_FLAG_CR); |
michael@0 | 3065 | GsmPDUHelper.writeHexOctet(1); |
michael@0 | 3066 | GsmPDUHelper.writeHexOctet(options.locationStatus); |
michael@0 | 3067 | } |
michael@0 | 3068 | |
michael@0 | 3069 | // Location Info |
michael@0 | 3070 | if (options.locationInfo) { |
michael@0 | 3071 | ComprehensionTlvHelper.writeLocationInfoTlv(options.locationInfo); |
michael@0 | 3072 | } |
michael@0 | 3073 | |
michael@0 | 3074 | // Transaction Id |
michael@0 | 3075 | if (options.transactionId != null) { |
michael@0 | 3076 | GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_TRANSACTION_ID | |
michael@0 | 3077 | COMPREHENSIONTLV_FLAG_CR); |
michael@0 | 3078 | GsmPDUHelper.writeHexOctet(1); |
michael@0 | 3079 | GsmPDUHelper.writeHexOctet(options.transactionId); |
michael@0 | 3080 | } |
michael@0 | 3081 | |
michael@0 | 3082 | // Address |
michael@0 | 3083 | if (options.address) { |
michael@0 | 3084 | GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_ADDRESS | |
michael@0 | 3085 | COMPREHENSIONTLV_FLAG_CR); |
michael@0 | 3086 | ComprehensionTlvHelper.writeLength( |
michael@0 | 3087 | Math.ceil(options.address.length/2) + 1 // address BCD + TON |
michael@0 | 3088 | ); |
michael@0 | 3089 | this.context.ICCPDUHelper.writeDiallingNumber(options.address); |
michael@0 | 3090 | } |
michael@0 | 3091 | |
michael@0 | 3092 | // Cause of disconnection. |
michael@0 | 3093 | if (options.cause != null) { |
michael@0 | 3094 | ComprehensionTlvHelper.writeCauseTlv(options.cause); |
michael@0 | 3095 | } |
michael@0 | 3096 | |
michael@0 | 3097 | // Timer Identifier |
michael@0 | 3098 | if (options.timerId != null) { |
michael@0 | 3099 | GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_TIMER_IDENTIFIER | |
michael@0 | 3100 | COMPREHENSIONTLV_FLAG_CR); |
michael@0 | 3101 | GsmPDUHelper.writeHexOctet(1); |
michael@0 | 3102 | GsmPDUHelper.writeHexOctet(options.timerId); |
michael@0 | 3103 | } |
michael@0 | 3104 | |
michael@0 | 3105 | // Timer Value |
michael@0 | 3106 | if (options.timerValue != null) { |
michael@0 | 3107 | ComprehensionTlvHelper.writeTimerValueTlv(options.timerValue, true); |
michael@0 | 3108 | } |
michael@0 | 3109 | |
michael@0 | 3110 | // Language |
michael@0 | 3111 | if (options.language) { |
michael@0 | 3112 | ComprehensionTlvHelper.writeLanguageTlv(options.language); |
michael@0 | 3113 | } |
michael@0 | 3114 | |
michael@0 | 3115 | // Browser Termination |
michael@0 | 3116 | if (options.terminationCause != null) { |
michael@0 | 3117 | GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_BROWSER_TERMINATION_CAUSE | |
michael@0 | 3118 | COMPREHENSIONTLV_FLAG_CR); |
michael@0 | 3119 | GsmPDUHelper.writeHexOctet(1); |
michael@0 | 3120 | GsmPDUHelper.writeHexOctet(options.terminationCause); |
michael@0 | 3121 | } |
michael@0 | 3122 | |
michael@0 | 3123 | // Calculate and write BER length to 2nd mark |
michael@0 | 3124 | Buf.stopCalOutgoingSize(); |
michael@0 | 3125 | |
michael@0 | 3126 | // Calculate and write Parcel size to 1st mark |
michael@0 | 3127 | Buf.stopCalOutgoingSize(); |
michael@0 | 3128 | |
michael@0 | 3129 | Buf.writeInt32(0); |
michael@0 | 3130 | Buf.sendParcel(); |
michael@0 | 3131 | }, |
michael@0 | 3132 | |
michael@0 | 3133 | /** |
michael@0 | 3134 | * Check a given number against the list of emergency numbers provided by the RIL. |
michael@0 | 3135 | * |
michael@0 | 3136 | * @param number |
michael@0 | 3137 | * The number to look up. |
michael@0 | 3138 | */ |
michael@0 | 3139 | _isEmergencyNumber: function(number) { |
michael@0 | 3140 | // Check ril provided numbers first. |
michael@0 | 3141 | let numbers = RIL_EMERGENCY_NUMBERS; |
michael@0 | 3142 | |
michael@0 | 3143 | if (numbers) { |
michael@0 | 3144 | numbers = numbers.split(","); |
michael@0 | 3145 | } else { |
michael@0 | 3146 | // No ecclist system property, so use our own list. |
michael@0 | 3147 | numbers = DEFAULT_EMERGENCY_NUMBERS; |
michael@0 | 3148 | } |
michael@0 | 3149 | |
michael@0 | 3150 | return numbers.indexOf(number) != -1; |
michael@0 | 3151 | }, |
michael@0 | 3152 | |
michael@0 | 3153 | /** |
michael@0 | 3154 | * Checks whether to temporarily suppress caller id for the call. |
michael@0 | 3155 | * |
michael@0 | 3156 | * @param mmi |
michael@0 | 3157 | * MMI full object. |
michael@0 | 3158 | */ |
michael@0 | 3159 | _isTemporaryModeCLIR: function(mmi) { |
michael@0 | 3160 | return (mmi && |
michael@0 | 3161 | mmi.serviceCode == MMI_SC_CLIR && |
michael@0 | 3162 | mmi.dialNumber && |
michael@0 | 3163 | (mmi.procedure == MMI_PROCEDURE_ACTIVATION || |
michael@0 | 3164 | mmi.procedure == MMI_PROCEDURE_DEACTIVATION)); |
michael@0 | 3165 | }, |
michael@0 | 3166 | |
michael@0 | 3167 | /** |
michael@0 | 3168 | * Report STK Service is running. |
michael@0 | 3169 | */ |
michael@0 | 3170 | reportStkServiceIsRunning: function() { |
michael@0 | 3171 | this.context.Buf.simpleRequest(REQUEST_REPORT_STK_SERVICE_IS_RUNNING); |
michael@0 | 3172 | }, |
michael@0 | 3173 | |
michael@0 | 3174 | /** |
michael@0 | 3175 | * Process ICC status. |
michael@0 | 3176 | */ |
michael@0 | 3177 | _processICCStatus: function(iccStatus) { |
michael@0 | 3178 | // If |_waitingRadioTech| is true, we should not get app information because |
michael@0 | 3179 | // the |_isCdma| flag is not ready yet. Otherwise we may use wrong index to |
michael@0 | 3180 | // get app information, especially for the case that icc card has both cdma |
michael@0 | 3181 | // and gsm subscription. |
michael@0 | 3182 | if (this._waitingRadioTech) { |
michael@0 | 3183 | return; |
michael@0 | 3184 | } |
michael@0 | 3185 | |
michael@0 | 3186 | this.iccStatus = iccStatus; |
michael@0 | 3187 | let newCardState; |
michael@0 | 3188 | let index = this._isCdma ? iccStatus.cdmaSubscriptionAppIndex : |
michael@0 | 3189 | iccStatus.gsmUmtsSubscriptionAppIndex; |
michael@0 | 3190 | let app = iccStatus.apps[index]; |
michael@0 | 3191 | |
michael@0 | 3192 | // When |iccStatus.cardState| is not CARD_STATE_PRESENT or have incorrect |
michael@0 | 3193 | // app information, we can not get iccId. So treat ICC as undetected. |
michael@0 | 3194 | if (iccStatus.cardState !== CARD_STATE_PRESENT || !app) { |
michael@0 | 3195 | if (this.cardState !== GECKO_CARDSTATE_UNDETECTED) { |
michael@0 | 3196 | this.operator = null; |
michael@0 | 3197 | // We should send |cardstatechange| before |iccinfochange|, otherwise we |
michael@0 | 3198 | // may lost cardstatechange event when icc card becomes undetected. |
michael@0 | 3199 | this.cardState = GECKO_CARDSTATE_UNDETECTED; |
michael@0 | 3200 | this.sendChromeMessage({rilMessageType: "cardstatechange", |
michael@0 | 3201 | cardState: this.cardState}); |
michael@0 | 3202 | |
michael@0 | 3203 | this.iccInfo = {iccType: null}; |
michael@0 | 3204 | this.context.ICCUtilsHelper.handleICCInfoChange(); |
michael@0 | 3205 | } |
michael@0 | 3206 | return; |
michael@0 | 3207 | } |
michael@0 | 3208 | |
michael@0 | 3209 | let ICCRecordHelper = this.context.ICCRecordHelper; |
michael@0 | 3210 | // fetchICCRecords will need to read aid, so read aid here. |
michael@0 | 3211 | this.aid = app.aid; |
michael@0 | 3212 | this.appType = app.app_type; |
michael@0 | 3213 | this.iccInfo.iccType = GECKO_CARD_TYPE[this.appType]; |
michael@0 | 3214 | // Try to get iccId only when cardState left GECKO_CARDSTATE_UNDETECTED. |
michael@0 | 3215 | if (iccStatus.cardState === CARD_STATE_PRESENT && |
michael@0 | 3216 | (this.cardState === GECKO_CARDSTATE_UNINITIALIZED || |
michael@0 | 3217 | this.cardState === GECKO_CARDSTATE_UNDETECTED)) { |
michael@0 | 3218 | ICCRecordHelper.readICCID(); |
michael@0 | 3219 | } |
michael@0 | 3220 | |
michael@0 | 3221 | switch (app.app_state) { |
michael@0 | 3222 | case CARD_APPSTATE_ILLEGAL: |
michael@0 | 3223 | newCardState = GECKO_CARDSTATE_ILLEGAL; |
michael@0 | 3224 | break; |
michael@0 | 3225 | case CARD_APPSTATE_PIN: |
michael@0 | 3226 | newCardState = GECKO_CARDSTATE_PIN_REQUIRED; |
michael@0 | 3227 | break; |
michael@0 | 3228 | case CARD_APPSTATE_PUK: |
michael@0 | 3229 | newCardState = GECKO_CARDSTATE_PUK_REQUIRED; |
michael@0 | 3230 | break; |
michael@0 | 3231 | case CARD_APPSTATE_SUBSCRIPTION_PERSO: |
michael@0 | 3232 | newCardState = PERSONSUBSTATE[app.perso_substate]; |
michael@0 | 3233 | break; |
michael@0 | 3234 | case CARD_APPSTATE_READY: |
michael@0 | 3235 | newCardState = GECKO_CARDSTATE_READY; |
michael@0 | 3236 | break; |
michael@0 | 3237 | case CARD_APPSTATE_UNKNOWN: |
michael@0 | 3238 | case CARD_APPSTATE_DETECTED: |
michael@0 | 3239 | // Fall through. |
michael@0 | 3240 | default: |
michael@0 | 3241 | newCardState = GECKO_CARDSTATE_UNKNOWN; |
michael@0 | 3242 | } |
michael@0 | 3243 | |
michael@0 | 3244 | let pin1State = app.pin1_replaced ? iccStatus.universalPINState : |
michael@0 | 3245 | app.pin1; |
michael@0 | 3246 | if (pin1State === CARD_PINSTATE_ENABLED_PERM_BLOCKED) { |
michael@0 | 3247 | newCardState = GECKO_CARDSTATE_PERMANENT_BLOCKED; |
michael@0 | 3248 | } |
michael@0 | 3249 | |
michael@0 | 3250 | if (this.cardState == newCardState) { |
michael@0 | 3251 | return; |
michael@0 | 3252 | } |
michael@0 | 3253 | |
michael@0 | 3254 | // This was moved down from CARD_APPSTATE_READY |
michael@0 | 3255 | this.requestNetworkInfo(); |
michael@0 | 3256 | if (newCardState == GECKO_CARDSTATE_READY) { |
michael@0 | 3257 | // For type SIM, we need to check EF_phase first. |
michael@0 | 3258 | // Other types of ICC we can send Terminal_Profile immediately. |
michael@0 | 3259 | if (this.appType == CARD_APPTYPE_SIM) { |
michael@0 | 3260 | this.context.SimRecordHelper.readSimPhase(); |
michael@0 | 3261 | } else if (RILQUIRKS_SEND_STK_PROFILE_DOWNLOAD) { |
michael@0 | 3262 | this.sendStkTerminalProfile(STK_SUPPORTED_TERMINAL_PROFILE); |
michael@0 | 3263 | } |
michael@0 | 3264 | |
michael@0 | 3265 | ICCRecordHelper.fetchICCRecords(); |
michael@0 | 3266 | } |
michael@0 | 3267 | |
michael@0 | 3268 | this.cardState = newCardState; |
michael@0 | 3269 | this.sendChromeMessage({rilMessageType: "cardstatechange", |
michael@0 | 3270 | cardState: this.cardState}); |
michael@0 | 3271 | }, |
michael@0 | 3272 | |
michael@0 | 3273 | /** |
michael@0 | 3274 | * Helper for processing responses of functions such as enterICC* and changeICC*. |
michael@0 | 3275 | */ |
michael@0 | 3276 | _processEnterAndChangeICCResponses: function(length, options) { |
michael@0 | 3277 | options.success = (options.rilRequestError === 0); |
michael@0 | 3278 | if (!options.success) { |
michael@0 | 3279 | options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError]; |
michael@0 | 3280 | } |
michael@0 | 3281 | options.retryCount = length ? this.context.Buf.readInt32List()[0] : -1; |
michael@0 | 3282 | if (options.rilMessageType != "sendMMI") { |
michael@0 | 3283 | this.sendChromeMessage(options); |
michael@0 | 3284 | return; |
michael@0 | 3285 | } |
michael@0 | 3286 | |
michael@0 | 3287 | let mmiServiceCode = options.mmiServiceCode; |
michael@0 | 3288 | |
michael@0 | 3289 | if (options.success) { |
michael@0 | 3290 | switch (mmiServiceCode) { |
michael@0 | 3291 | case MMI_KS_SC_PIN: |
michael@0 | 3292 | options.statusMessage = MMI_SM_KS_PIN_CHANGED; |
michael@0 | 3293 | break; |
michael@0 | 3294 | case MMI_KS_SC_PIN2: |
michael@0 | 3295 | options.statusMessage = MMI_SM_KS_PIN2_CHANGED; |
michael@0 | 3296 | break; |
michael@0 | 3297 | case MMI_KS_SC_PUK: |
michael@0 | 3298 | options.statusMessage = MMI_SM_KS_PIN_UNBLOCKED; |
michael@0 | 3299 | break; |
michael@0 | 3300 | case MMI_KS_SC_PUK2: |
michael@0 | 3301 | options.statusMessage = MMI_SM_KS_PIN2_UNBLOCKED; |
michael@0 | 3302 | break; |
michael@0 | 3303 | } |
michael@0 | 3304 | } else { |
michael@0 | 3305 | if (options.retryCount <= 0) { |
michael@0 | 3306 | if (mmiServiceCode === MMI_KS_SC_PUK) { |
michael@0 | 3307 | options.errorMsg = MMI_ERROR_KS_SIM_BLOCKED; |
michael@0 | 3308 | } else if (mmiServiceCode === MMI_KS_SC_PIN) { |
michael@0 | 3309 | options.errorMsg = MMI_ERROR_KS_NEEDS_PUK; |
michael@0 | 3310 | } |
michael@0 | 3311 | } else { |
michael@0 | 3312 | if (mmiServiceCode === MMI_KS_SC_PIN || |
michael@0 | 3313 | mmiServiceCode === MMI_KS_SC_PIN2) { |
michael@0 | 3314 | options.errorMsg = MMI_ERROR_KS_BAD_PIN; |
michael@0 | 3315 | } else if (mmiServiceCode === MMI_KS_SC_PUK || |
michael@0 | 3316 | mmiServiceCode === MMI_KS_SC_PUK2) { |
michael@0 | 3317 | options.errorMsg = MMI_ERROR_KS_BAD_PUK; |
michael@0 | 3318 | } |
michael@0 | 3319 | if (options.retryCount !== undefined) { |
michael@0 | 3320 | options.additionalInformation = options.retryCount; |
michael@0 | 3321 | } |
michael@0 | 3322 | } |
michael@0 | 3323 | } |
michael@0 | 3324 | |
michael@0 | 3325 | this.sendChromeMessage(options); |
michael@0 | 3326 | }, |
michael@0 | 3327 | |
michael@0 | 3328 | // We combine all of the NETWORK_INFO_MESSAGE_TYPES into one "networkinfochange" |
michael@0 | 3329 | // message to the RadioInterfaceLayer, so we can avoid sending multiple |
michael@0 | 3330 | // VoiceInfoChanged events for both operator / voice_data_registration |
michael@0 | 3331 | // |
michael@0 | 3332 | // State management here is a little tricky. We need to know both: |
michael@0 | 3333 | // 1. Whether or not a response was received for each of the |
michael@0 | 3334 | // NETWORK_INFO_MESSAGE_TYPES |
michael@0 | 3335 | // 2. The outbound message that corresponds with that response -- but this |
michael@0 | 3336 | // only happens when internal state changes (i.e. it isn't guaranteed) |
michael@0 | 3337 | // |
michael@0 | 3338 | // To collect this state, each message response function first calls |
michael@0 | 3339 | // _receivedNetworkInfo, to mark the response as received. When the |
michael@0 | 3340 | // final response is received, a call to _sendPendingNetworkInfo is placed |
michael@0 | 3341 | // on the next tick of the worker thread. |
michael@0 | 3342 | // |
michael@0 | 3343 | // Since the original call to _receivedNetworkInfo happens at the top |
michael@0 | 3344 | // of the response handler, this gives the final handler a chance to |
michael@0 | 3345 | // queue up it's "changed" message by calling _sendNetworkInfoMessage if/when |
michael@0 | 3346 | // the internal state has actually changed. |
michael@0 | 3347 | _sendNetworkInfoMessage: function(type, message) { |
michael@0 | 3348 | if (!this._processingNetworkInfo) { |
michael@0 | 3349 | // We only combine these messages in the case of the combined request |
michael@0 | 3350 | // in requestNetworkInfo() |
michael@0 | 3351 | this.sendChromeMessage(message); |
michael@0 | 3352 | return; |
michael@0 | 3353 | } |
michael@0 | 3354 | |
michael@0 | 3355 | if (DEBUG) { |
michael@0 | 3356 | this.context.debug("Queuing " + type + " network info message: " + |
michael@0 | 3357 | JSON.stringify(message)); |
michael@0 | 3358 | } |
michael@0 | 3359 | this._pendingNetworkInfo[type] = message; |
michael@0 | 3360 | }, |
michael@0 | 3361 | |
michael@0 | 3362 | _receivedNetworkInfo: function(type) { |
michael@0 | 3363 | if (DEBUG) this.context.debug("Received " + type + " network info."); |
michael@0 | 3364 | if (!this._processingNetworkInfo) { |
michael@0 | 3365 | return; |
michael@0 | 3366 | } |
michael@0 | 3367 | |
michael@0 | 3368 | let pending = this._pendingNetworkInfo; |
michael@0 | 3369 | |
michael@0 | 3370 | // We still need to track states for events that aren't fired. |
michael@0 | 3371 | if (!(type in pending)) { |
michael@0 | 3372 | pending[type] = this.pendingNetworkType; |
michael@0 | 3373 | } |
michael@0 | 3374 | |
michael@0 | 3375 | // Pending network info is ready to be sent when no more messages |
michael@0 | 3376 | // are waiting for responses, but the combined payload hasn't been sent. |
michael@0 | 3377 | for (let i = 0; i < NETWORK_INFO_MESSAGE_TYPES.length; i++) { |
michael@0 | 3378 | let msgType = NETWORK_INFO_MESSAGE_TYPES[i]; |
michael@0 | 3379 | if (!(msgType in pending)) { |
michael@0 | 3380 | if (DEBUG) { |
michael@0 | 3381 | this.context.debug("Still missing some more network info, not " + |
michael@0 | 3382 | "notifying main thread."); |
michael@0 | 3383 | } |
michael@0 | 3384 | return; |
michael@0 | 3385 | } |
michael@0 | 3386 | } |
michael@0 | 3387 | |
michael@0 | 3388 | // Do a pass to clean up the processed messages that didn't create |
michael@0 | 3389 | // a response message, so we don't have unused keys in the outbound |
michael@0 | 3390 | // networkinfochanged message. |
michael@0 | 3391 | for (let key in pending) { |
michael@0 | 3392 | if (pending[key] == this.pendingNetworkType) { |
michael@0 | 3393 | delete pending[key]; |
michael@0 | 3394 | } |
michael@0 | 3395 | } |
michael@0 | 3396 | |
michael@0 | 3397 | if (DEBUG) { |
michael@0 | 3398 | this.context.debug("All pending network info has been received: " + |
michael@0 | 3399 | JSON.stringify(pending)); |
michael@0 | 3400 | } |
michael@0 | 3401 | |
michael@0 | 3402 | // Send the message on the next tick of the worker's loop, so we give the |
michael@0 | 3403 | // last message a chance to call _sendNetworkInfoMessage first. |
michael@0 | 3404 | setTimeout(this._sendPendingNetworkInfo.bind(this), 0); |
michael@0 | 3405 | }, |
michael@0 | 3406 | |
michael@0 | 3407 | _sendPendingNetworkInfo: function() { |
michael@0 | 3408 | this.sendChromeMessage(this._pendingNetworkInfo); |
michael@0 | 3409 | |
michael@0 | 3410 | this._processingNetworkInfo = false; |
michael@0 | 3411 | for (let i = 0; i < NETWORK_INFO_MESSAGE_TYPES.length; i++) { |
michael@0 | 3412 | delete this._pendingNetworkInfo[NETWORK_INFO_MESSAGE_TYPES[i]]; |
michael@0 | 3413 | } |
michael@0 | 3414 | |
michael@0 | 3415 | if (this._needRepollNetworkInfo) { |
michael@0 | 3416 | this._needRepollNetworkInfo = false; |
michael@0 | 3417 | this.requestNetworkInfo(); |
michael@0 | 3418 | } |
michael@0 | 3419 | }, |
michael@0 | 3420 | |
michael@0 | 3421 | /** |
michael@0 | 3422 | * Normalize the signal strength in dBm to the signal level from 0 to 100. |
michael@0 | 3423 | * |
michael@0 | 3424 | * @param signal |
michael@0 | 3425 | * The signal strength in dBm to normalize. |
michael@0 | 3426 | * @param min |
michael@0 | 3427 | * The signal strength in dBm maps to level 0. |
michael@0 | 3428 | * @param max |
michael@0 | 3429 | * The signal strength in dBm maps to level 100. |
michael@0 | 3430 | * |
michael@0 | 3431 | * @return level |
michael@0 | 3432 | * The signal level from 0 to 100. |
michael@0 | 3433 | */ |
michael@0 | 3434 | _processSignalLevel: function(signal, min, max) { |
michael@0 | 3435 | if (signal <= min) { |
michael@0 | 3436 | return 0; |
michael@0 | 3437 | } |
michael@0 | 3438 | |
michael@0 | 3439 | if (signal >= max) { |
michael@0 | 3440 | return 100; |
michael@0 | 3441 | } |
michael@0 | 3442 | |
michael@0 | 3443 | return Math.floor((signal - min) * 100 / (max - min)); |
michael@0 | 3444 | }, |
michael@0 | 3445 | |
michael@0 | 3446 | /** |
michael@0 | 3447 | * Process LTE signal strength to the signal info object. |
michael@0 | 3448 | * |
michael@0 | 3449 | * @param signal |
michael@0 | 3450 | * The signal object reported from RIL/modem. |
michael@0 | 3451 | * |
michael@0 | 3452 | * @return The object of signal strength info. |
michael@0 | 3453 | * Or null if invalid signal input. |
michael@0 | 3454 | */ |
michael@0 | 3455 | _processLteSignal: function(signal) { |
michael@0 | 3456 | // Valid values are 0-63 as defined in TS 27.007 clause 8.69. |
michael@0 | 3457 | if (signal.lteSignalStrength === undefined || |
michael@0 | 3458 | signal.lteSignalStrength < 0 || |
michael@0 | 3459 | signal.lteSignalStrength > 63) { |
michael@0 | 3460 | return null; |
michael@0 | 3461 | } |
michael@0 | 3462 | |
michael@0 | 3463 | let info = { |
michael@0 | 3464 | voice: { |
michael@0 | 3465 | signalStrength: null, |
michael@0 | 3466 | relSignalStrength: null |
michael@0 | 3467 | }, |
michael@0 | 3468 | data: { |
michael@0 | 3469 | signalStrength: null, |
michael@0 | 3470 | relSignalStrength: null |
michael@0 | 3471 | } |
michael@0 | 3472 | }; |
michael@0 | 3473 | |
michael@0 | 3474 | // TODO: Bug 982013: reconsider signalStrength/relSignalStrength APIs for |
michael@0 | 3475 | // GSM/CDMA/LTE, and take rsrp/rssnr into account for LTE case then. |
michael@0 | 3476 | let signalStrength = -111 + signal.lteSignalStrength; |
michael@0 | 3477 | info.voice.signalStrength = info.data.signalStrength = signalStrength; |
michael@0 | 3478 | // 0 and 12 are referred to AOSP's implementation. These values are not |
michael@0 | 3479 | // constants and can be customized based on different requirements. |
michael@0 | 3480 | let signalLevel = this._processSignalLevel(signal.lteSignalStrength, 0, 12); |
michael@0 | 3481 | info.voice.relSignalStrength = info.data.relSignalStrength = signalLevel; |
michael@0 | 3482 | |
michael@0 | 3483 | return info; |
michael@0 | 3484 | }, |
michael@0 | 3485 | |
michael@0 | 3486 | _processSignalStrength: function(signal) { |
michael@0 | 3487 | let info = { |
michael@0 | 3488 | voice: { |
michael@0 | 3489 | signalStrength: null, |
michael@0 | 3490 | relSignalStrength: null |
michael@0 | 3491 | }, |
michael@0 | 3492 | data: { |
michael@0 | 3493 | signalStrength: null, |
michael@0 | 3494 | relSignalStrength: null |
michael@0 | 3495 | } |
michael@0 | 3496 | }; |
michael@0 | 3497 | |
michael@0 | 3498 | // During startup, |radioTech| is not yet defined, so we need to |
michael@0 | 3499 | // check it separately. |
michael@0 | 3500 | if (("radioTech" in this.voiceRegistrationState) && |
michael@0 | 3501 | !this._isGsmTechGroup(this.voiceRegistrationState.radioTech)) { |
michael@0 | 3502 | // CDMA RSSI. |
michael@0 | 3503 | // Valid values are positive integers. This value is the actual RSSI value |
michael@0 | 3504 | // multiplied by -1. Example: If the actual RSSI is -75, then this |
michael@0 | 3505 | // response value will be 75. |
michael@0 | 3506 | if (signal.cdmaDBM && signal.cdmaDBM > 0) { |
michael@0 | 3507 | let signalStrength = -1 * signal.cdmaDBM; |
michael@0 | 3508 | info.voice.signalStrength = signalStrength; |
michael@0 | 3509 | |
michael@0 | 3510 | // -105 and -70 are referred to AOSP's implementation. These values are |
michael@0 | 3511 | // not constants and can be customized based on different requirement. |
michael@0 | 3512 | let signalLevel = this._processSignalLevel(signalStrength, -105, -70); |
michael@0 | 3513 | info.voice.relSignalStrength = signalLevel; |
michael@0 | 3514 | } |
michael@0 | 3515 | |
michael@0 | 3516 | // EVDO RSSI. |
michael@0 | 3517 | // Valid values are positive integers. This value is the actual RSSI value |
michael@0 | 3518 | // multiplied by -1. Example: If the actual RSSI is -75, then this |
michael@0 | 3519 | // response value will be 75. |
michael@0 | 3520 | if (signal.evdoDBM && signal.evdoDBM > 0) { |
michael@0 | 3521 | let signalStrength = -1 * signal.evdoDBM; |
michael@0 | 3522 | info.data.signalStrength = signalStrength; |
michael@0 | 3523 | |
michael@0 | 3524 | // -105 and -70 are referred to AOSP's implementation. These values are |
michael@0 | 3525 | // not constants and can be customized based on different requirement. |
michael@0 | 3526 | let signalLevel = this._processSignalLevel(signalStrength, -105, -70); |
michael@0 | 3527 | info.data.relSignalStrength = signalLevel; |
michael@0 | 3528 | } |
michael@0 | 3529 | } else { |
michael@0 | 3530 | // Check LTE level first, and check GSM/UMTS level next if LTE one is not |
michael@0 | 3531 | // valid. |
michael@0 | 3532 | let lteInfo = this._processLteSignal(signal); |
michael@0 | 3533 | if (lteInfo) { |
michael@0 | 3534 | info = lteInfo; |
michael@0 | 3535 | } else { |
michael@0 | 3536 | // GSM signal strength. |
michael@0 | 3537 | // Valid values are 0-31 as defined in TS 27.007 8.5. |
michael@0 | 3538 | // 0 : -113 dBm or less |
michael@0 | 3539 | // 1 : -111 dBm |
michael@0 | 3540 | // 2...30: -109...-53 dBm |
michael@0 | 3541 | // 31 : -51 dBm |
michael@0 | 3542 | if (signal.gsmSignalStrength && |
michael@0 | 3543 | signal.gsmSignalStrength >= 0 && |
michael@0 | 3544 | signal.gsmSignalStrength <= 31) { |
michael@0 | 3545 | let signalStrength = -113 + 2 * signal.gsmSignalStrength; |
michael@0 | 3546 | info.voice.signalStrength = info.data.signalStrength = signalStrength; |
michael@0 | 3547 | |
michael@0 | 3548 | // -115 and -85 are referred to AOSP's implementation. These values are |
michael@0 | 3549 | // not constants and can be customized based on different requirement. |
michael@0 | 3550 | let signalLevel = this._processSignalLevel(signalStrength, -110, -85); |
michael@0 | 3551 | info.voice.relSignalStrength = info.data.relSignalStrength = signalLevel; |
michael@0 | 3552 | } |
michael@0 | 3553 | } |
michael@0 | 3554 | } |
michael@0 | 3555 | |
michael@0 | 3556 | info.rilMessageType = "signalstrengthchange"; |
michael@0 | 3557 | this._sendNetworkInfoMessage(NETWORK_INFO_SIGNAL, info); |
michael@0 | 3558 | |
michael@0 | 3559 | if (this.cachedDialRequest && info.voice.signalStrength) { |
michael@0 | 3560 | // Radio is ready for making the cached emergency call. |
michael@0 | 3561 | this.cachedDialRequest.callback(); |
michael@0 | 3562 | this.cachedDialRequest = null; |
michael@0 | 3563 | } |
michael@0 | 3564 | }, |
michael@0 | 3565 | |
michael@0 | 3566 | /** |
michael@0 | 3567 | * Process the network registration flags. |
michael@0 | 3568 | * |
michael@0 | 3569 | * @return true if the state changed, false otherwise. |
michael@0 | 3570 | */ |
michael@0 | 3571 | _processCREG: function(curState, newState) { |
michael@0 | 3572 | let changed = false; |
michael@0 | 3573 | |
michael@0 | 3574 | let regState = this.parseInt(newState[0], NETWORK_CREG_STATE_UNKNOWN); |
michael@0 | 3575 | if (curState.regState === undefined || curState.regState !== regState) { |
michael@0 | 3576 | changed = true; |
michael@0 | 3577 | curState.regState = regState; |
michael@0 | 3578 | |
michael@0 | 3579 | curState.state = NETWORK_CREG_TO_GECKO_MOBILE_CONNECTION_STATE[regState]; |
michael@0 | 3580 | curState.connected = regState == NETWORK_CREG_STATE_REGISTERED_HOME || |
michael@0 | 3581 | regState == NETWORK_CREG_STATE_REGISTERED_ROAMING; |
michael@0 | 3582 | curState.roaming = regState == NETWORK_CREG_STATE_REGISTERED_ROAMING; |
michael@0 | 3583 | curState.emergencyCallsOnly = !curState.connected; |
michael@0 | 3584 | } |
michael@0 | 3585 | |
michael@0 | 3586 | if (!curState.cell) { |
michael@0 | 3587 | curState.cell = {}; |
michael@0 | 3588 | } |
michael@0 | 3589 | |
michael@0 | 3590 | // From TS 23.003, 0000 and 0xfffe are indicated that no valid LAI exists |
michael@0 | 3591 | // in MS. So we still need to report the '0000' as well. |
michael@0 | 3592 | let lac = this.parseInt(newState[1], -1, 16); |
michael@0 | 3593 | if (curState.cell.gsmLocationAreaCode === undefined || |
michael@0 | 3594 | curState.cell.gsmLocationAreaCode !== lac) { |
michael@0 | 3595 | curState.cell.gsmLocationAreaCode = lac; |
michael@0 | 3596 | changed = true; |
michael@0 | 3597 | } |
michael@0 | 3598 | |
michael@0 | 3599 | let cid = this.parseInt(newState[2], -1, 16); |
michael@0 | 3600 | if (curState.cell.gsmCellId === undefined || |
michael@0 | 3601 | curState.cell.gsmCellId !== cid) { |
michael@0 | 3602 | curState.cell.gsmCellId = cid; |
michael@0 | 3603 | changed = true; |
michael@0 | 3604 | } |
michael@0 | 3605 | |
michael@0 | 3606 | let radioTech = (newState[3] === undefined ? |
michael@0 | 3607 | NETWORK_CREG_TECH_UNKNOWN : |
michael@0 | 3608 | this.parseInt(newState[3], NETWORK_CREG_TECH_UNKNOWN)); |
michael@0 | 3609 | if (curState.radioTech === undefined || curState.radioTech !== radioTech) { |
michael@0 | 3610 | changed = true; |
michael@0 | 3611 | curState.radioTech = radioTech; |
michael@0 | 3612 | curState.type = GECKO_RADIO_TECH[radioTech] || null; |
michael@0 | 3613 | } |
michael@0 | 3614 | return changed; |
michael@0 | 3615 | }, |
michael@0 | 3616 | |
michael@0 | 3617 | _processVoiceRegistrationState: function(state) { |
michael@0 | 3618 | let rs = this.voiceRegistrationState; |
michael@0 | 3619 | let stateChanged = this._processCREG(rs, state); |
michael@0 | 3620 | if (stateChanged && rs.connected) { |
michael@0 | 3621 | this.getSmscAddress(); |
michael@0 | 3622 | } |
michael@0 | 3623 | |
michael@0 | 3624 | let cell = rs.cell; |
michael@0 | 3625 | if (this._isCdma) { |
michael@0 | 3626 | // Some variables below are not used. Comment them instead of removing to |
michael@0 | 3627 | // keep the information about state[x]. |
michael@0 | 3628 | let cdmaBaseStationId = this.parseInt(state[4], -1); |
michael@0 | 3629 | let cdmaBaseStationLatitude = this.parseInt(state[5], -2147483648); |
michael@0 | 3630 | let cdmaBaseStationLongitude = this.parseInt(state[6], -2147483648); |
michael@0 | 3631 | // let cssIndicator = this.parseInt(state[7]); |
michael@0 | 3632 | let cdmaSystemId = this.parseInt(state[8], -1); |
michael@0 | 3633 | let cdmaNetworkId = this.parseInt(state[9], -1); |
michael@0 | 3634 | // let roamingIndicator = this.parseInt(state[10]); |
michael@0 | 3635 | // let systemIsInPRL = this.parseInt(state[11]); |
michael@0 | 3636 | // let defaultRoamingIndicator = this.parseInt(state[12]); |
michael@0 | 3637 | // let reasonForDenial = this.parseInt(state[13]); |
michael@0 | 3638 | |
michael@0 | 3639 | if (cell.cdmaBaseStationId !== cdmaBaseStationId || |
michael@0 | 3640 | cell.cdmaBaseStationLatitude !== cdmaBaseStationLatitude || |
michael@0 | 3641 | cell.cdmaBaseStationLongitude !== cdmaBaseStationLongitude || |
michael@0 | 3642 | cell.cdmaSystemId !== cdmaSystemId || |
michael@0 | 3643 | cell.cdmaNetworkId !== cdmaNetworkId) { |
michael@0 | 3644 | stateChanged = true; |
michael@0 | 3645 | cell.cdmaBaseStationId = cdmaBaseStationId; |
michael@0 | 3646 | cell.cdmaBaseStationLatitude = cdmaBaseStationLatitude; |
michael@0 | 3647 | cell.cdmaBaseStationLongitude = cdmaBaseStationLongitude; |
michael@0 | 3648 | cell.cdmaSystemId = cdmaSystemId; |
michael@0 | 3649 | cell.cdmaNetworkId = cdmaNetworkId; |
michael@0 | 3650 | } |
michael@0 | 3651 | } |
michael@0 | 3652 | |
michael@0 | 3653 | if (stateChanged) { |
michael@0 | 3654 | rs.rilMessageType = "voiceregistrationstatechange"; |
michael@0 | 3655 | this._sendNetworkInfoMessage(NETWORK_INFO_VOICE_REGISTRATION_STATE, rs); |
michael@0 | 3656 | } |
michael@0 | 3657 | }, |
michael@0 | 3658 | |
michael@0 | 3659 | _processDataRegistrationState: function(state) { |
michael@0 | 3660 | let rs = this.dataRegistrationState; |
michael@0 | 3661 | let stateChanged = this._processCREG(rs, state); |
michael@0 | 3662 | if (stateChanged) { |
michael@0 | 3663 | rs.rilMessageType = "dataregistrationstatechange"; |
michael@0 | 3664 | this._sendNetworkInfoMessage(NETWORK_INFO_DATA_REGISTRATION_STATE, rs); |
michael@0 | 3665 | } |
michael@0 | 3666 | }, |
michael@0 | 3667 | |
michael@0 | 3668 | _processOperator: function(operatorData) { |
michael@0 | 3669 | if (operatorData.length < 3) { |
michael@0 | 3670 | if (DEBUG) { |
michael@0 | 3671 | this.context.debug("Expected at least 3 strings for operator."); |
michael@0 | 3672 | } |
michael@0 | 3673 | } |
michael@0 | 3674 | |
michael@0 | 3675 | if (!this.operator) { |
michael@0 | 3676 | this.operator = { |
michael@0 | 3677 | rilMessageType: "operatorchange", |
michael@0 | 3678 | longName: null, |
michael@0 | 3679 | shortName: null |
michael@0 | 3680 | }; |
michael@0 | 3681 | } |
michael@0 | 3682 | |
michael@0 | 3683 | let [longName, shortName, networkTuple] = operatorData; |
michael@0 | 3684 | let thisTuple = (this.operator.mcc || "") + (this.operator.mnc || ""); |
michael@0 | 3685 | |
michael@0 | 3686 | if (this.operator.longName !== longName || |
michael@0 | 3687 | this.operator.shortName !== shortName || |
michael@0 | 3688 | thisTuple !== networkTuple) { |
michael@0 | 3689 | |
michael@0 | 3690 | this.operator.mcc = null; |
michael@0 | 3691 | this.operator.mnc = null; |
michael@0 | 3692 | |
michael@0 | 3693 | if (networkTuple) { |
michael@0 | 3694 | try { |
michael@0 | 3695 | this._processNetworkTuple(networkTuple, this.operator); |
michael@0 | 3696 | } catch (e) { |
michael@0 | 3697 | if (DEBUG) this.context.debug("Error processing operator tuple: " + e); |
michael@0 | 3698 | } |
michael@0 | 3699 | } else { |
michael@0 | 3700 | // According to ril.h, the operator fields will be NULL when the operator |
michael@0 | 3701 | // is not currently registered. We can avoid trying to parse the numeric |
michael@0 | 3702 | // tuple in that case. |
michael@0 | 3703 | if (DEBUG) { |
michael@0 | 3704 | this.context.debug("Operator is currently unregistered"); |
michael@0 | 3705 | } |
michael@0 | 3706 | } |
michael@0 | 3707 | |
michael@0 | 3708 | let ICCUtilsHelper = this.context.ICCUtilsHelper; |
michael@0 | 3709 | let networkName; |
michael@0 | 3710 | // We won't get network name using PNN and OPL if voice registration isn't ready |
michael@0 | 3711 | if (this.voiceRegistrationState.cell && |
michael@0 | 3712 | this.voiceRegistrationState.cell.gsmLocationAreaCode != -1) { |
michael@0 | 3713 | networkName = ICCUtilsHelper.getNetworkNameFromICC( |
michael@0 | 3714 | this.operator.mcc, |
michael@0 | 3715 | this.operator.mnc, |
michael@0 | 3716 | this.voiceRegistrationState.cell.gsmLocationAreaCode); |
michael@0 | 3717 | } |
michael@0 | 3718 | |
michael@0 | 3719 | if (networkName) { |
michael@0 | 3720 | if (DEBUG) { |
michael@0 | 3721 | this.context.debug("Operator names will be overriden: " + |
michael@0 | 3722 | "longName = " + networkName.fullName + ", " + |
michael@0 | 3723 | "shortName = " + networkName.shortName); |
michael@0 | 3724 | } |
michael@0 | 3725 | |
michael@0 | 3726 | this.operator.longName = networkName.fullName; |
michael@0 | 3727 | this.operator.shortName = networkName.shortName; |
michael@0 | 3728 | } else { |
michael@0 | 3729 | this.operator.longName = longName; |
michael@0 | 3730 | this.operator.shortName = shortName; |
michael@0 | 3731 | } |
michael@0 | 3732 | |
michael@0 | 3733 | if (ICCUtilsHelper.updateDisplayCondition()) { |
michael@0 | 3734 | ICCUtilsHelper.handleICCInfoChange(); |
michael@0 | 3735 | } |
michael@0 | 3736 | this._sendNetworkInfoMessage(NETWORK_INFO_OPERATOR, this.operator); |
michael@0 | 3737 | } |
michael@0 | 3738 | }, |
michael@0 | 3739 | |
michael@0 | 3740 | /** |
michael@0 | 3741 | * Helpers for processing call state and handle the active call. |
michael@0 | 3742 | */ |
michael@0 | 3743 | _processCalls: function(newCalls) { |
michael@0 | 3744 | let conferenceChanged = false; |
michael@0 | 3745 | let clearConferenceRequest = false; |
michael@0 | 3746 | let pendingOutgoingCall = null; |
michael@0 | 3747 | |
michael@0 | 3748 | // Go through the calls we currently have on file and see if any of them |
michael@0 | 3749 | // changed state. Remove them from the newCalls map as we deal with them |
michael@0 | 3750 | // so that only new calls remain in the map after we're done. |
michael@0 | 3751 | for each (let currentCall in this.currentCalls) { |
michael@0 | 3752 | if (currentCall.callIndex == OUTGOING_PLACEHOLDER_CALL_INDEX) { |
michael@0 | 3753 | pendingOutgoingCall = currentCall; |
michael@0 | 3754 | continue; |
michael@0 | 3755 | } |
michael@0 | 3756 | |
michael@0 | 3757 | let newCall; |
michael@0 | 3758 | if (newCalls) { |
michael@0 | 3759 | newCall = newCalls[currentCall.callIndex]; |
michael@0 | 3760 | delete newCalls[currentCall.callIndex]; |
michael@0 | 3761 | } |
michael@0 | 3762 | |
michael@0 | 3763 | // Call is no longer reported by the radio. Remove from our map and send |
michael@0 | 3764 | // disconnected state change. |
michael@0 | 3765 | if (!newCall) { |
michael@0 | 3766 | if (this.currentConference.participants[currentCall.callIndex]) { |
michael@0 | 3767 | conferenceChanged = true; |
michael@0 | 3768 | } |
michael@0 | 3769 | this._removeVoiceCall(currentCall, |
michael@0 | 3770 | currentCall.hangUpLocal ? |
michael@0 | 3771 | GECKO_CALL_ERROR_NORMAL_CALL_CLEARING : null); |
michael@0 | 3772 | continue; |
michael@0 | 3773 | } |
michael@0 | 3774 | |
michael@0 | 3775 | // Call is still valid. |
michael@0 | 3776 | if (newCall.state == currentCall.state && |
michael@0 | 3777 | newCall.isMpty == currentCall.isMpty) { |
michael@0 | 3778 | continue; |
michael@0 | 3779 | } |
michael@0 | 3780 | |
michael@0 | 3781 | // State has changed. |
michael@0 | 3782 | if (newCall.state == CALL_STATE_INCOMING && |
michael@0 | 3783 | currentCall.state == CALL_STATE_WAITING) { |
michael@0 | 3784 | // Update the call internally but we don't notify chrome since these two |
michael@0 | 3785 | // states are viewed as the same one there. |
michael@0 | 3786 | currentCall.state = newCall.state; |
michael@0 | 3787 | continue; |
michael@0 | 3788 | } |
michael@0 | 3789 | |
michael@0 | 3790 | if (!currentCall.started && newCall.state == CALL_STATE_ACTIVE) { |
michael@0 | 3791 | currentCall.started = new Date().getTime(); |
michael@0 | 3792 | } |
michael@0 | 3793 | |
michael@0 | 3794 | if (currentCall.isMpty == newCall.isMpty && |
michael@0 | 3795 | newCall.state != currentCall.state) { |
michael@0 | 3796 | currentCall.state = newCall.state; |
michael@0 | 3797 | if (currentCall.isConference) { |
michael@0 | 3798 | conferenceChanged = true; |
michael@0 | 3799 | } |
michael@0 | 3800 | this._handleChangedCallState(currentCall); |
michael@0 | 3801 | continue; |
michael@0 | 3802 | } |
michael@0 | 3803 | |
michael@0 | 3804 | // '.isMpty' becomes false when the conference call is put on hold. |
michael@0 | 3805 | // We need to introduce additional 'isConference' to correctly record the |
michael@0 | 3806 | // real conference status |
michael@0 | 3807 | |
michael@0 | 3808 | // Update a possible conference participant when .isMpty changes. |
michael@0 | 3809 | if (!currentCall.isMpty && newCall.isMpty) { |
michael@0 | 3810 | if (this._hasConferenceRequest) { |
michael@0 | 3811 | conferenceChanged = true; |
michael@0 | 3812 | clearConferenceRequest = true; |
michael@0 | 3813 | currentCall.state = newCall.state; |
michael@0 | 3814 | currentCall.isMpty = newCall.isMpty; |
michael@0 | 3815 | currentCall.isConference = true; |
michael@0 | 3816 | this.currentConference.participants[currentCall.callIndex] = currentCall; |
michael@0 | 3817 | this._handleChangedCallState(currentCall); |
michael@0 | 3818 | } else if (currentCall.isConference) { |
michael@0 | 3819 | // The case happens when resuming a held conference call. |
michael@0 | 3820 | conferenceChanged = true; |
michael@0 | 3821 | currentCall.state = newCall.state; |
michael@0 | 3822 | currentCall.isMpty = newCall.isMpty; |
michael@0 | 3823 | this.currentConference.participants[currentCall.callIndex] = currentCall; |
michael@0 | 3824 | this._handleChangedCallState(currentCall); |
michael@0 | 3825 | } else { |
michael@0 | 3826 | // Weird. This sometimes happens when we switch two calls, but it is |
michael@0 | 3827 | // not a conference call. |
michael@0 | 3828 | currentCall.state = newCall.state; |
michael@0 | 3829 | this._handleChangedCallState(currentCall); |
michael@0 | 3830 | } |
michael@0 | 3831 | } else if (currentCall.isMpty && !newCall.isMpty) { |
michael@0 | 3832 | if (!this.currentConference.participants[newCall.callIndex]) { |
michael@0 | 3833 | continue; |
michael@0 | 3834 | } |
michael@0 | 3835 | |
michael@0 | 3836 | // '.isMpty' of a conference participant is set to false by rild when |
michael@0 | 3837 | // the conference call is put on hold. We don't actually know if the call |
michael@0 | 3838 | // still attends the conference until updating all calls finishes. We |
michael@0 | 3839 | // cache it for further determination. |
michael@0 | 3840 | if (newCall.state != CALL_STATE_HOLDING) { |
michael@0 | 3841 | delete this.currentConference.participants[newCall.callIndex]; |
michael@0 | 3842 | currentCall.state = newCall.state; |
michael@0 | 3843 | currentCall.isMpty = newCall.isMpty; |
michael@0 | 3844 | currentCall.isConference = false; |
michael@0 | 3845 | conferenceChanged = true; |
michael@0 | 3846 | this._handleChangedCallState(currentCall); |
michael@0 | 3847 | continue; |
michael@0 | 3848 | } |
michael@0 | 3849 | |
michael@0 | 3850 | if (!this.currentConference.cache) { |
michael@0 | 3851 | this.currentConference.cache = {}; |
michael@0 | 3852 | } |
michael@0 | 3853 | this.currentConference.cache[currentCall.callIndex] = newCall; |
michael@0 | 3854 | currentCall.state = newCall.state; |
michael@0 | 3855 | currentCall.isMpty = newCall.isMpty; |
michael@0 | 3856 | conferenceChanged = true; |
michael@0 | 3857 | } |
michael@0 | 3858 | } |
michael@0 | 3859 | |
michael@0 | 3860 | if (pendingOutgoingCall) { |
michael@0 | 3861 | if (!newCalls || Object.keys(newCalls).length === 0) { |
michael@0 | 3862 | // We don't get a successful call for pendingOutgoingCall. |
michael@0 | 3863 | this._removePendingOutgoingCall(GECKO_CALL_ERROR_UNSPECIFIED); |
michael@0 | 3864 | } else { |
michael@0 | 3865 | // Only remove it from currentCalls map. Will use the new call to |
michael@0 | 3866 | // replace the placeholder. |
michael@0 | 3867 | delete this.currentCalls[OUTGOING_PLACEHOLDER_CALL_INDEX]; |
michael@0 | 3868 | } |
michael@0 | 3869 | } |
michael@0 | 3870 | |
michael@0 | 3871 | // Go through any remaining calls that are new to us. |
michael@0 | 3872 | for each (let newCall in newCalls) { |
michael@0 | 3873 | if (newCall.isVoice) { |
michael@0 | 3874 | if (newCall.isMpty) { |
michael@0 | 3875 | conferenceChanged = true; |
michael@0 | 3876 | } |
michael@0 | 3877 | if (!pendingOutgoingCall && |
michael@0 | 3878 | (newCall.state === CALL_STATE_DIALING || |
michael@0 | 3879 | newCall.state === CALL_STATE_ALERTING)) { |
michael@0 | 3880 | // Receive a new outgoing call which is already hung up by user. |
michael@0 | 3881 | if (DEBUG) this.context.debug("Pending outgoing call is hung up by user."); |
michael@0 | 3882 | this.sendHangUpRequest(newCall.callIndex); |
michael@0 | 3883 | } else { |
michael@0 | 3884 | this._addNewVoiceCall(newCall); |
michael@0 | 3885 | } |
michael@0 | 3886 | } |
michael@0 | 3887 | } |
michael@0 | 3888 | |
michael@0 | 3889 | if (clearConferenceRequest) { |
michael@0 | 3890 | this._hasConferenceRequest = false; |
michael@0 | 3891 | } |
michael@0 | 3892 | if (conferenceChanged) { |
michael@0 | 3893 | this._ensureConference(); |
michael@0 | 3894 | } |
michael@0 | 3895 | }, |
michael@0 | 3896 | |
michael@0 | 3897 | _addNewVoiceCall: function(newCall) { |
michael@0 | 3898 | // Format international numbers appropriately. |
michael@0 | 3899 | if (newCall.number && newCall.toa == TOA_INTERNATIONAL && |
michael@0 | 3900 | newCall.number[0] != "+") { |
michael@0 | 3901 | newCall.number = "+" + newCall.number; |
michael@0 | 3902 | } |
michael@0 | 3903 | |
michael@0 | 3904 | if (newCall.state == CALL_STATE_INCOMING) { |
michael@0 | 3905 | newCall.isOutgoing = false; |
michael@0 | 3906 | } else if (newCall.state == CALL_STATE_DIALING) { |
michael@0 | 3907 | newCall.isOutgoing = true; |
michael@0 | 3908 | } |
michael@0 | 3909 | |
michael@0 | 3910 | // Set flag for outgoing emergency call. |
michael@0 | 3911 | newCall.isEmergency = newCall.isOutgoing && |
michael@0 | 3912 | this._isEmergencyNumber(newCall.number); |
michael@0 | 3913 | |
michael@0 | 3914 | // Set flag for conference. |
michael@0 | 3915 | newCall.isConference = newCall.isMpty ? true : false; |
michael@0 | 3916 | |
michael@0 | 3917 | // Add to our map. |
michael@0 | 3918 | if (newCall.isMpty) { |
michael@0 | 3919 | this.currentConference.participants[newCall.callIndex] = newCall; |
michael@0 | 3920 | } |
michael@0 | 3921 | this._handleChangedCallState(newCall); |
michael@0 | 3922 | this.currentCalls[newCall.callIndex] = newCall; |
michael@0 | 3923 | }, |
michael@0 | 3924 | |
michael@0 | 3925 | _removeVoiceCall: function(removedCall, failCause) { |
michael@0 | 3926 | if (this.currentConference.participants[removedCall.callIndex]) { |
michael@0 | 3927 | removedCall.isConference = false; |
michael@0 | 3928 | delete this.currentConference.participants[removedCall.callIndex]; |
michael@0 | 3929 | delete this.currentCalls[removedCall.callIndex]; |
michael@0 | 3930 | // We don't query the fail cause here as it triggers another asynchrouns |
michael@0 | 3931 | // request that leads to a problem of updating all conferece participants |
michael@0 | 3932 | // in one task. |
michael@0 | 3933 | this._handleDisconnectedCall(removedCall); |
michael@0 | 3934 | } else { |
michael@0 | 3935 | delete this.currentCalls[removedCall.callIndex]; |
michael@0 | 3936 | if (failCause) { |
michael@0 | 3937 | removedCall.failCause = failCause; |
michael@0 | 3938 | this._handleDisconnectedCall(removedCall); |
michael@0 | 3939 | } else { |
michael@0 | 3940 | this.getFailCauseCode((function(call, failCause) { |
michael@0 | 3941 | call.failCause = failCause; |
michael@0 | 3942 | this._handleDisconnectedCall(call); |
michael@0 | 3943 | }).bind(this, removedCall)); |
michael@0 | 3944 | } |
michael@0 | 3945 | } |
michael@0 | 3946 | }, |
michael@0 | 3947 | |
michael@0 | 3948 | _createPendingOutgoingCall: function(options) { |
michael@0 | 3949 | if (DEBUG) this.context.debug("Create a pending outgoing call."); |
michael@0 | 3950 | this._addNewVoiceCall({ |
michael@0 | 3951 | number: options.number, |
michael@0 | 3952 | state: CALL_STATE_DIALING, |
michael@0 | 3953 | callIndex: OUTGOING_PLACEHOLDER_CALL_INDEX |
michael@0 | 3954 | }); |
michael@0 | 3955 | }, |
michael@0 | 3956 | |
michael@0 | 3957 | _removePendingOutgoingCall: function(failCause) { |
michael@0 | 3958 | let call = this.currentCalls[OUTGOING_PLACEHOLDER_CALL_INDEX]; |
michael@0 | 3959 | if (!call) { |
michael@0 | 3960 | return; |
michael@0 | 3961 | } |
michael@0 | 3962 | |
michael@0 | 3963 | if (DEBUG) this.context.debug("Remove pending outgoing call."); |
michael@0 | 3964 | this._removeVoiceCall(pendingOutgoingCall, failCause); |
michael@0 | 3965 | }, |
michael@0 | 3966 | |
michael@0 | 3967 | _ensureConference: function() { |
michael@0 | 3968 | let oldState = this.currentConference.state; |
michael@0 | 3969 | let remaining = Object.keys(this.currentConference.participants); |
michael@0 | 3970 | |
michael@0 | 3971 | if (remaining.length == 1) { |
michael@0 | 3972 | // Remove that if only does one remain in a conference call. |
michael@0 | 3973 | let call = this.currentCalls[remaining[0]]; |
michael@0 | 3974 | call.isConference = false; |
michael@0 | 3975 | this._handleChangedCallState(call); |
michael@0 | 3976 | delete this.currentConference.participants[call.callIndex]; |
michael@0 | 3977 | } else if (remaining.length > 1) { |
michael@0 | 3978 | for each (let call in this.currentConference.cache) { |
michael@0 | 3979 | call.isConference = true; |
michael@0 | 3980 | this.currentConference.participants[call.callIndex] = call; |
michael@0 | 3981 | this.currentCalls[call.callIndex] = call; |
michael@0 | 3982 | this._handleChangedCallState(call); |
michael@0 | 3983 | } |
michael@0 | 3984 | } |
michael@0 | 3985 | delete this.currentConference.cache; |
michael@0 | 3986 | |
michael@0 | 3987 | // Update the conference call's state. |
michael@0 | 3988 | let state = CALL_STATE_UNKNOWN; |
michael@0 | 3989 | for each (let call in this.currentConference.participants) { |
michael@0 | 3990 | if (state != CALL_STATE_UNKNOWN && state != call.state) { |
michael@0 | 3991 | // Each participant should have the same state, otherwise something |
michael@0 | 3992 | // wrong happens. |
michael@0 | 3993 | state = CALL_STATE_UNKNOWN; |
michael@0 | 3994 | break; |
michael@0 | 3995 | } |
michael@0 | 3996 | state = call.state; |
michael@0 | 3997 | } |
michael@0 | 3998 | if (oldState != state) { |
michael@0 | 3999 | this.currentConference.state = state; |
michael@0 | 4000 | let message = {rilMessageType: "conferenceCallStateChanged", |
michael@0 | 4001 | state: state}; |
michael@0 | 4002 | this.sendChromeMessage(message); |
michael@0 | 4003 | } |
michael@0 | 4004 | }, |
michael@0 | 4005 | |
michael@0 | 4006 | _handleChangedCallState: function(changedCall) { |
michael@0 | 4007 | let message = {rilMessageType: "callStateChange", |
michael@0 | 4008 | call: changedCall}; |
michael@0 | 4009 | this.sendChromeMessage(message); |
michael@0 | 4010 | }, |
michael@0 | 4011 | |
michael@0 | 4012 | _handleDisconnectedCall: function(disconnectedCall) { |
michael@0 | 4013 | let message = {rilMessageType: "callDisconnected", |
michael@0 | 4014 | call: disconnectedCall}; |
michael@0 | 4015 | this.sendChromeMessage(message); |
michael@0 | 4016 | }, |
michael@0 | 4017 | |
michael@0 | 4018 | _sendDataCallError: function(message, errorCode) { |
michael@0 | 4019 | // Should not include token for unsolicited response. |
michael@0 | 4020 | delete message.rilMessageToken; |
michael@0 | 4021 | message.rilMessageType = "datacallerror"; |
michael@0 | 4022 | if (errorCode == ERROR_GENERIC_FAILURE) { |
michael@0 | 4023 | message.errorMsg = RIL_ERROR_TO_GECKO_ERROR[errorCode]; |
michael@0 | 4024 | } else { |
michael@0 | 4025 | message.errorMsg = RIL_DATACALL_FAILCAUSE_TO_GECKO_DATACALL_ERROR[errorCode]; |
michael@0 | 4026 | } |
michael@0 | 4027 | this.sendChromeMessage(message); |
michael@0 | 4028 | }, |
michael@0 | 4029 | |
michael@0 | 4030 | /** |
michael@0 | 4031 | * @return "deactivate" if <ifname> changes or one of the currentDataCall |
michael@0 | 4032 | * addresses is missing in updatedDataCall, or "identical" if no |
michael@0 | 4033 | * changes found, or "changed" otherwise. |
michael@0 | 4034 | */ |
michael@0 | 4035 | _compareDataCallLink: function(updatedDataCall, currentDataCall) { |
michael@0 | 4036 | // If network interface is changed, report as "deactivate". |
michael@0 | 4037 | if (updatedDataCall.ifname != currentDataCall.ifname) { |
michael@0 | 4038 | return "deactivate"; |
michael@0 | 4039 | } |
michael@0 | 4040 | |
michael@0 | 4041 | // If any existing address is missing, report as "deactivate". |
michael@0 | 4042 | for (let i = 0; i < currentDataCall.addresses.length; i++) { |
michael@0 | 4043 | let address = currentDataCall.addresses[i]; |
michael@0 | 4044 | if (updatedDataCall.addresses.indexOf(address) < 0) { |
michael@0 | 4045 | return "deactivate"; |
michael@0 | 4046 | } |
michael@0 | 4047 | } |
michael@0 | 4048 | |
michael@0 | 4049 | if (currentDataCall.addresses.length != updatedDataCall.addresses.length) { |
michael@0 | 4050 | // Since now all |currentDataCall.addresses| are found in |
michael@0 | 4051 | // |updatedDataCall.addresses|, this means one or more new addresses are |
michael@0 | 4052 | // reported. |
michael@0 | 4053 | return "changed"; |
michael@0 | 4054 | } |
michael@0 | 4055 | |
michael@0 | 4056 | let fields = ["gateways", "dnses"]; |
michael@0 | 4057 | for (let i = 0; i < fields.length; i++) { |
michael@0 | 4058 | // Compare <datacall>.<field>. |
michael@0 | 4059 | let field = fields[i]; |
michael@0 | 4060 | let lhs = updatedDataCall[field], rhs = currentDataCall[field]; |
michael@0 | 4061 | if (lhs.length != rhs.length) { |
michael@0 | 4062 | return "changed"; |
michael@0 | 4063 | } |
michael@0 | 4064 | for (let i = 0; i < lhs.length; i++) { |
michael@0 | 4065 | if (lhs[i] != rhs[i]) { |
michael@0 | 4066 | return "changed"; |
michael@0 | 4067 | } |
michael@0 | 4068 | } |
michael@0 | 4069 | } |
michael@0 | 4070 | |
michael@0 | 4071 | return "identical"; |
michael@0 | 4072 | }, |
michael@0 | 4073 | |
michael@0 | 4074 | _processDataCallList: function(datacalls, newDataCallOptions) { |
michael@0 | 4075 | // Check for possible PDP errors: We check earlier because the datacall |
michael@0 | 4076 | // can be removed if is the same as the current one. |
michael@0 | 4077 | for each (let newDataCall in datacalls) { |
michael@0 | 4078 | if (newDataCall.status != DATACALL_FAIL_NONE) { |
michael@0 | 4079 | if (newDataCallOptions) { |
michael@0 | 4080 | newDataCall.apn = newDataCallOptions.apn; |
michael@0 | 4081 | } |
michael@0 | 4082 | this._sendDataCallError(newDataCall, newDataCall.status); |
michael@0 | 4083 | } |
michael@0 | 4084 | } |
michael@0 | 4085 | |
michael@0 | 4086 | for each (let currentDataCall in this.currentDataCalls) { |
michael@0 | 4087 | let updatedDataCall; |
michael@0 | 4088 | if (datacalls) { |
michael@0 | 4089 | updatedDataCall = datacalls[currentDataCall.cid]; |
michael@0 | 4090 | delete datacalls[currentDataCall.cid]; |
michael@0 | 4091 | } |
michael@0 | 4092 | |
michael@0 | 4093 | if (!updatedDataCall) { |
michael@0 | 4094 | // If datacalls list is coming from REQUEST_SETUP_DATA_CALL response, |
michael@0 | 4095 | // we do not change state for any currentDataCalls not in datacalls list. |
michael@0 | 4096 | if (!newDataCallOptions) { |
michael@0 | 4097 | delete this.currentDataCalls[currentDataCall.cid]; |
michael@0 | 4098 | currentDataCall.state = GECKO_NETWORK_STATE_DISCONNECTED; |
michael@0 | 4099 | currentDataCall.rilMessageType = "datacallstatechange"; |
michael@0 | 4100 | this.sendChromeMessage(currentDataCall); |
michael@0 | 4101 | } |
michael@0 | 4102 | continue; |
michael@0 | 4103 | } |
michael@0 | 4104 | |
michael@0 | 4105 | if (updatedDataCall && !updatedDataCall.ifname) { |
michael@0 | 4106 | delete this.currentDataCalls[currentDataCall.cid]; |
michael@0 | 4107 | currentDataCall.state = GECKO_NETWORK_STATE_UNKNOWN; |
michael@0 | 4108 | currentDataCall.rilMessageType = "datacallstatechange"; |
michael@0 | 4109 | this.sendChromeMessage(currentDataCall); |
michael@0 | 4110 | continue; |
michael@0 | 4111 | } |
michael@0 | 4112 | |
michael@0 | 4113 | this._setDataCallGeckoState(updatedDataCall); |
michael@0 | 4114 | if (updatedDataCall.state != currentDataCall.state) { |
michael@0 | 4115 | if (updatedDataCall.state == GECKO_NETWORK_STATE_DISCONNECTED) { |
michael@0 | 4116 | delete this.currentDataCalls[currentDataCall.cid]; |
michael@0 | 4117 | } |
michael@0 | 4118 | currentDataCall.status = updatedDataCall.status; |
michael@0 | 4119 | currentDataCall.active = updatedDataCall.active; |
michael@0 | 4120 | currentDataCall.state = updatedDataCall.state; |
michael@0 | 4121 | currentDataCall.rilMessageType = "datacallstatechange"; |
michael@0 | 4122 | this.sendChromeMessage(currentDataCall); |
michael@0 | 4123 | continue; |
michael@0 | 4124 | } |
michael@0 | 4125 | |
michael@0 | 4126 | // State not changed, now check links. |
michael@0 | 4127 | let result = |
michael@0 | 4128 | this._compareDataCallLink(updatedDataCall, currentDataCall); |
michael@0 | 4129 | if (result == "identical") { |
michael@0 | 4130 | if (DEBUG) this.context.debug("No changes in data call."); |
michael@0 | 4131 | continue; |
michael@0 | 4132 | } |
michael@0 | 4133 | if (result == "deactivate") { |
michael@0 | 4134 | if (DEBUG) this.context.debug("Data link changed, cleanup."); |
michael@0 | 4135 | this.deactivateDataCall(currentDataCall); |
michael@0 | 4136 | continue; |
michael@0 | 4137 | } |
michael@0 | 4138 | // Minor change, just update and notify. |
michael@0 | 4139 | if (DEBUG) { |
michael@0 | 4140 | this.context.debug("Data link minor change, just update and notify."); |
michael@0 | 4141 | } |
michael@0 | 4142 | currentDataCall.addresses = updatedDataCall.addresses.slice(); |
michael@0 | 4143 | currentDataCall.dnses = updatedDataCall.dnses.slice(); |
michael@0 | 4144 | currentDataCall.gateways = updatedDataCall.gateways.slice(); |
michael@0 | 4145 | currentDataCall.rilMessageType = "datacallstatechange"; |
michael@0 | 4146 | this.sendChromeMessage(currentDataCall); |
michael@0 | 4147 | } |
michael@0 | 4148 | |
michael@0 | 4149 | for each (let newDataCall in datacalls) { |
michael@0 | 4150 | if (!newDataCall.ifname) { |
michael@0 | 4151 | continue; |
michael@0 | 4152 | } |
michael@0 | 4153 | |
michael@0 | 4154 | if (!newDataCallOptions) { |
michael@0 | 4155 | if (DEBUG) { |
michael@0 | 4156 | this.context.debug("Unexpected new data call: " + |
michael@0 | 4157 | JSON.stringify(newDataCall)); |
michael@0 | 4158 | } |
michael@0 | 4159 | continue; |
michael@0 | 4160 | } |
michael@0 | 4161 | |
michael@0 | 4162 | this.currentDataCalls[newDataCall.cid] = newDataCall; |
michael@0 | 4163 | this._setDataCallGeckoState(newDataCall); |
michael@0 | 4164 | |
michael@0 | 4165 | newDataCall.radioTech = newDataCallOptions.radioTech; |
michael@0 | 4166 | newDataCall.apn = newDataCallOptions.apn; |
michael@0 | 4167 | newDataCall.user = newDataCallOptions.user; |
michael@0 | 4168 | newDataCall.passwd = newDataCallOptions.passwd; |
michael@0 | 4169 | newDataCall.chappap = newDataCallOptions.chappap; |
michael@0 | 4170 | newDataCall.pdptype = newDataCallOptions.pdptype; |
michael@0 | 4171 | newDataCallOptions = null; |
michael@0 | 4172 | |
michael@0 | 4173 | newDataCall.rilMessageType = "datacallstatechange"; |
michael@0 | 4174 | this.sendChromeMessage(newDataCall); |
michael@0 | 4175 | } |
michael@0 | 4176 | }, |
michael@0 | 4177 | |
michael@0 | 4178 | _setDataCallGeckoState: function(datacall) { |
michael@0 | 4179 | switch (datacall.active) { |
michael@0 | 4180 | case DATACALL_INACTIVE: |
michael@0 | 4181 | datacall.state = GECKO_NETWORK_STATE_DISCONNECTED; |
michael@0 | 4182 | break; |
michael@0 | 4183 | case DATACALL_ACTIVE_DOWN: |
michael@0 | 4184 | case DATACALL_ACTIVE_UP: |
michael@0 | 4185 | datacall.state = GECKO_NETWORK_STATE_CONNECTED; |
michael@0 | 4186 | break; |
michael@0 | 4187 | } |
michael@0 | 4188 | }, |
michael@0 | 4189 | |
michael@0 | 4190 | _processSuppSvcNotification: function(info) { |
michael@0 | 4191 | if (DEBUG) { |
michael@0 | 4192 | this.context.debug("handle supp svc notification: " + JSON.stringify(info)); |
michael@0 | 4193 | } |
michael@0 | 4194 | |
michael@0 | 4195 | if (info.notificationType !== 1) { |
michael@0 | 4196 | // We haven't supported MO intermediate result code, i.e. |
michael@0 | 4197 | // info.notificationType === 0, which refers to code1 defined in 3GPP |
michael@0 | 4198 | // 27.007 7.17. We only support partial MT unsolicited result code, |
michael@0 | 4199 | // referring to code2, for now. |
michael@0 | 4200 | return; |
michael@0 | 4201 | } |
michael@0 | 4202 | |
michael@0 | 4203 | let notification = null; |
michael@0 | 4204 | let callIndex = -1; |
michael@0 | 4205 | |
michael@0 | 4206 | switch (info.code) { |
michael@0 | 4207 | case SUPP_SVC_NOTIFICATION_CODE2_PUT_ON_HOLD: |
michael@0 | 4208 | case SUPP_SVC_NOTIFICATION_CODE2_RETRIEVED: |
michael@0 | 4209 | notification = GECKO_SUPP_SVC_NOTIFICATION_FROM_CODE2[info.code]; |
michael@0 | 4210 | break; |
michael@0 | 4211 | default: |
michael@0 | 4212 | // Notification type not supported. |
michael@0 | 4213 | return; |
michael@0 | 4214 | } |
michael@0 | 4215 | |
michael@0 | 4216 | // Get the target call object for this notification. |
michael@0 | 4217 | let currentCallIndexes = Object.keys(this.currentCalls); |
michael@0 | 4218 | if (currentCallIndexes.length === 1) { |
michael@0 | 4219 | // Only one call exists. This should be the target. |
michael@0 | 4220 | callIndex = currentCallIndexes[0]; |
michael@0 | 4221 | } else { |
michael@0 | 4222 | // Find the call in |currentCalls| by the given number. |
michael@0 | 4223 | if (info.number) { |
michael@0 | 4224 | for each (let currentCall in this.currentCalls) { |
michael@0 | 4225 | if (currentCall.number == info.number) { |
michael@0 | 4226 | callIndex = currentCall.callIndex; |
michael@0 | 4227 | break; |
michael@0 | 4228 | } |
michael@0 | 4229 | } |
michael@0 | 4230 | } |
michael@0 | 4231 | } |
michael@0 | 4232 | |
michael@0 | 4233 | let message = {rilMessageType: "suppSvcNotification", |
michael@0 | 4234 | notification: notification, |
michael@0 | 4235 | callIndex: callIndex}; |
michael@0 | 4236 | this.sendChromeMessage(message); |
michael@0 | 4237 | }, |
michael@0 | 4238 | |
michael@0 | 4239 | _cancelEmergencyCbModeTimeout: function() { |
michael@0 | 4240 | if (this._exitEmergencyCbModeTimeoutID) { |
michael@0 | 4241 | clearTimeout(this._exitEmergencyCbModeTimeoutID); |
michael@0 | 4242 | this._exitEmergencyCbModeTimeoutID = null; |
michael@0 | 4243 | } |
michael@0 | 4244 | }, |
michael@0 | 4245 | |
michael@0 | 4246 | _handleChangedEmergencyCbMode: function(active) { |
michael@0 | 4247 | this._isInEmergencyCbMode = active; |
michael@0 | 4248 | |
michael@0 | 4249 | // Clear the existed timeout event. |
michael@0 | 4250 | this._cancelEmergencyCbModeTimeout(); |
michael@0 | 4251 | |
michael@0 | 4252 | // Start a new timeout event when entering the mode. |
michael@0 | 4253 | if (active) { |
michael@0 | 4254 | this._exitEmergencyCbModeTimeoutID = setTimeout( |
michael@0 | 4255 | this.exitEmergencyCbMode.bind(this), EMERGENCY_CB_MODE_TIMEOUT_MS); |
michael@0 | 4256 | } |
michael@0 | 4257 | |
michael@0 | 4258 | let message = {rilMessageType: "emergencyCbModeChange", |
michael@0 | 4259 | active: active, |
michael@0 | 4260 | timeoutMs: EMERGENCY_CB_MODE_TIMEOUT_MS}; |
michael@0 | 4261 | this.sendChromeMessage(message); |
michael@0 | 4262 | }, |
michael@0 | 4263 | |
michael@0 | 4264 | _processNetworks: function() { |
michael@0 | 4265 | let strings = this.context.Buf.readStringList(); |
michael@0 | 4266 | let networks = []; |
michael@0 | 4267 | |
michael@0 | 4268 | for (let i = 0; i < strings.length; i += 4) { |
michael@0 | 4269 | let network = { |
michael@0 | 4270 | longName: strings[i], |
michael@0 | 4271 | shortName: strings[i + 1], |
michael@0 | 4272 | mcc: null, |
michael@0 | 4273 | mnc: null, |
michael@0 | 4274 | state: null |
michael@0 | 4275 | }; |
michael@0 | 4276 | |
michael@0 | 4277 | let networkTuple = strings[i + 2]; |
michael@0 | 4278 | try { |
michael@0 | 4279 | this._processNetworkTuple(networkTuple, network); |
michael@0 | 4280 | } catch (e) { |
michael@0 | 4281 | if (DEBUG) this.context.debug("Error processing operator tuple: " + e); |
michael@0 | 4282 | } |
michael@0 | 4283 | |
michael@0 | 4284 | let state = strings[i + 3]; |
michael@0 | 4285 | if (state === NETWORK_STATE_UNKNOWN) { |
michael@0 | 4286 | // TODO: looks like this might conflict in style with |
michael@0 | 4287 | // GECKO_NETWORK_STYLE_UNKNOWN / nsINetworkManager |
michael@0 | 4288 | state = GECKO_QAN_STATE_UNKNOWN; |
michael@0 | 4289 | } |
michael@0 | 4290 | |
michael@0 | 4291 | network.state = state; |
michael@0 | 4292 | networks.push(network); |
michael@0 | 4293 | } |
michael@0 | 4294 | return networks; |
michael@0 | 4295 | }, |
michael@0 | 4296 | |
michael@0 | 4297 | /** |
michael@0 | 4298 | * The "numeric" portion of the operator info is a tuple |
michael@0 | 4299 | * containing MCC (country code) and MNC (network code). |
michael@0 | 4300 | * AFAICT, MCC should always be 3 digits, making the remaining |
michael@0 | 4301 | * portion the MNC. |
michael@0 | 4302 | */ |
michael@0 | 4303 | _processNetworkTuple: function(networkTuple, network) { |
michael@0 | 4304 | let tupleLen = networkTuple.length; |
michael@0 | 4305 | |
michael@0 | 4306 | if (tupleLen == 5 || tupleLen == 6) { |
michael@0 | 4307 | network.mcc = networkTuple.substr(0, 3); |
michael@0 | 4308 | network.mnc = networkTuple.substr(3); |
michael@0 | 4309 | } else { |
michael@0 | 4310 | network.mcc = null; |
michael@0 | 4311 | network.mnc = null; |
michael@0 | 4312 | |
michael@0 | 4313 | throw new Error("Invalid network tuple (should be 5 or 6 digits): " + networkTuple); |
michael@0 | 4314 | } |
michael@0 | 4315 | }, |
michael@0 | 4316 | |
michael@0 | 4317 | /** |
michael@0 | 4318 | * Check if GSM radio access technology group. |
michael@0 | 4319 | */ |
michael@0 | 4320 | _isGsmTechGroup: function(radioTech) { |
michael@0 | 4321 | if (!radioTech) { |
michael@0 | 4322 | return true; |
michael@0 | 4323 | } |
michael@0 | 4324 | |
michael@0 | 4325 | switch(radioTech) { |
michael@0 | 4326 | case NETWORK_CREG_TECH_GPRS: |
michael@0 | 4327 | case NETWORK_CREG_TECH_EDGE: |
michael@0 | 4328 | case NETWORK_CREG_TECH_UMTS: |
michael@0 | 4329 | case NETWORK_CREG_TECH_HSDPA: |
michael@0 | 4330 | case NETWORK_CREG_TECH_HSUPA: |
michael@0 | 4331 | case NETWORK_CREG_TECH_HSPA: |
michael@0 | 4332 | case NETWORK_CREG_TECH_LTE: |
michael@0 | 4333 | case NETWORK_CREG_TECH_HSPAP: |
michael@0 | 4334 | case NETWORK_CREG_TECH_GSM: |
michael@0 | 4335 | return true; |
michael@0 | 4336 | } |
michael@0 | 4337 | |
michael@0 | 4338 | return false; |
michael@0 | 4339 | }, |
michael@0 | 4340 | |
michael@0 | 4341 | /** |
michael@0 | 4342 | * Process radio technology change. |
michael@0 | 4343 | */ |
michael@0 | 4344 | _processRadioTech: function(radioTech) { |
michael@0 | 4345 | let isCdma = !this._isGsmTechGroup(radioTech); |
michael@0 | 4346 | this.radioTech = radioTech; |
michael@0 | 4347 | |
michael@0 | 4348 | if (DEBUG) { |
michael@0 | 4349 | this.context.debug("Radio tech is set to: " + GECKO_RADIO_TECH[radioTech] + |
michael@0 | 4350 | ", it is a " + (isCdma?"cdma":"gsm") + " technology"); |
michael@0 | 4351 | } |
michael@0 | 4352 | |
michael@0 | 4353 | // We should request SIM information when |
michael@0 | 4354 | // 1. Radio state has been changed, so we are waiting for radioTech or |
michael@0 | 4355 | // 2. isCdma is different from this._isCdma. |
michael@0 | 4356 | if (this._waitingRadioTech || isCdma != this._isCdma) { |
michael@0 | 4357 | this._isCdma = isCdma; |
michael@0 | 4358 | this._waitingRadioTech = false; |
michael@0 | 4359 | if (this._isCdma) { |
michael@0 | 4360 | this.getDeviceIdentity(); |
michael@0 | 4361 | } else { |
michael@0 | 4362 | this.getIMEI(); |
michael@0 | 4363 | this.getIMEISV(); |
michael@0 | 4364 | } |
michael@0 | 4365 | this.getICCStatus(); |
michael@0 | 4366 | } |
michael@0 | 4367 | }, |
michael@0 | 4368 | |
michael@0 | 4369 | /** |
michael@0 | 4370 | * Helper for returning the TOA for the given dial string. |
michael@0 | 4371 | */ |
michael@0 | 4372 | _toaFromString: function(number) { |
michael@0 | 4373 | let toa = TOA_UNKNOWN; |
michael@0 | 4374 | if (number && number.length > 0 && number[0] == '+') { |
michael@0 | 4375 | toa = TOA_INTERNATIONAL; |
michael@0 | 4376 | } |
michael@0 | 4377 | return toa; |
michael@0 | 4378 | }, |
michael@0 | 4379 | |
michael@0 | 4380 | /** |
michael@0 | 4381 | * Helper for translating basic service group to call forwarding service class |
michael@0 | 4382 | * parameter. |
michael@0 | 4383 | */ |
michael@0 | 4384 | _siToServiceClass: function(si) { |
michael@0 | 4385 | if (!si) { |
michael@0 | 4386 | return ICC_SERVICE_CLASS_NONE; |
michael@0 | 4387 | } |
michael@0 | 4388 | |
michael@0 | 4389 | let serviceCode = parseInt(si, 10); |
michael@0 | 4390 | switch (serviceCode) { |
michael@0 | 4391 | case 10: |
michael@0 | 4392 | return ICC_SERVICE_CLASS_SMS + ICC_SERVICE_CLASS_FAX + ICC_SERVICE_CLASS_VOICE; |
michael@0 | 4393 | case 11: |
michael@0 | 4394 | return ICC_SERVICE_CLASS_VOICE; |
michael@0 | 4395 | case 12: |
michael@0 | 4396 | return ICC_SERVICE_CLASS_SMS + ICC_SERVICE_CLASS_FAX; |
michael@0 | 4397 | case 13: |
michael@0 | 4398 | return ICC_SERVICE_CLASS_FAX; |
michael@0 | 4399 | case 16: |
michael@0 | 4400 | return ICC_SERVICE_CLASS_SMS; |
michael@0 | 4401 | case 19: |
michael@0 | 4402 | return ICC_SERVICE_CLASS_FAX + ICC_SERVICE_CLASS_VOICE; |
michael@0 | 4403 | case 21: |
michael@0 | 4404 | return ICC_SERVICE_CLASS_PAD + ICC_SERVICE_CLASS_DATA_ASYNC; |
michael@0 | 4405 | case 22: |
michael@0 | 4406 | return ICC_SERVICE_CLASS_PACKET + ICC_SERVICE_CLASS_DATA_SYNC; |
michael@0 | 4407 | case 25: |
michael@0 | 4408 | return ICC_SERVICE_CLASS_DATA_ASYNC; |
michael@0 | 4409 | case 26: |
michael@0 | 4410 | return ICC_SERVICE_CLASS_DATA_SYNC + SERVICE_CLASS_VOICE; |
michael@0 | 4411 | case 99: |
michael@0 | 4412 | return ICC_SERVICE_CLASS_PACKET; |
michael@0 | 4413 | default: |
michael@0 | 4414 | return ICC_SERVICE_CLASS_NONE; |
michael@0 | 4415 | } |
michael@0 | 4416 | }, |
michael@0 | 4417 | |
michael@0 | 4418 | /** |
michael@0 | 4419 | * @param message A decoded SMS-DELIVER message. |
michael@0 | 4420 | * |
michael@0 | 4421 | * @see 3GPP TS 31.111 section 7.1.1 |
michael@0 | 4422 | */ |
michael@0 | 4423 | dataDownloadViaSMSPP: function(message) { |
michael@0 | 4424 | let Buf = this.context.Buf; |
michael@0 | 4425 | let GsmPDUHelper = this.context.GsmPDUHelper; |
michael@0 | 4426 | |
michael@0 | 4427 | let options = { |
michael@0 | 4428 | pid: message.pid, |
michael@0 | 4429 | dcs: message.dcs, |
michael@0 | 4430 | encoding: message.encoding, |
michael@0 | 4431 | }; |
michael@0 | 4432 | Buf.newParcel(REQUEST_STK_SEND_ENVELOPE_WITH_STATUS, options); |
michael@0 | 4433 | |
michael@0 | 4434 | Buf.seekIncoming(-1 * (Buf.getCurrentParcelSize() - Buf.getReadAvailable() |
michael@0 | 4435 | - 2 * Buf.UINT32_SIZE)); // Skip response_type & request_type. |
michael@0 | 4436 | let messageStringLength = Buf.readInt32(); // In semi-octets |
michael@0 | 4437 | let smscLength = GsmPDUHelper.readHexOctet(); // In octets, inclusive of TOA |
michael@0 | 4438 | let tpduLength = (messageStringLength / 2) - (smscLength + 1); // In octets |
michael@0 | 4439 | |
michael@0 | 4440 | // Device identities: 4 bytes |
michael@0 | 4441 | // Address: 0 or (2 + smscLength) |
michael@0 | 4442 | // SMS TPDU: (2 or 3) + tpduLength |
michael@0 | 4443 | let berLen = 4 + |
michael@0 | 4444 | (smscLength ? (2 + smscLength) : 0) + |
michael@0 | 4445 | (tpduLength <= 127 ? 2 : 3) + tpduLength; // In octets |
michael@0 | 4446 | |
michael@0 | 4447 | let parcelLength = (berLen <= 127 ? 2 : 3) + berLen; // In octets |
michael@0 | 4448 | Buf.writeInt32(parcelLength * 2); // In semi-octets |
michael@0 | 4449 | |
michael@0 | 4450 | // Write a BER-TLV |
michael@0 | 4451 | GsmPDUHelper.writeHexOctet(BER_SMS_PP_DOWNLOAD_TAG); |
michael@0 | 4452 | if (berLen > 127) { |
michael@0 | 4453 | GsmPDUHelper.writeHexOctet(0x81); |
michael@0 | 4454 | } |
michael@0 | 4455 | GsmPDUHelper.writeHexOctet(berLen); |
michael@0 | 4456 | |
michael@0 | 4457 | // Device Identifies-TLV |
michael@0 | 4458 | GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_DEVICE_ID | |
michael@0 | 4459 | COMPREHENSIONTLV_FLAG_CR); |
michael@0 | 4460 | GsmPDUHelper.writeHexOctet(0x02); |
michael@0 | 4461 | GsmPDUHelper.writeHexOctet(STK_DEVICE_ID_NETWORK); |
michael@0 | 4462 | GsmPDUHelper.writeHexOctet(STK_DEVICE_ID_SIM); |
michael@0 | 4463 | |
michael@0 | 4464 | // Address-TLV |
michael@0 | 4465 | if (smscLength) { |
michael@0 | 4466 | GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_ADDRESS); |
michael@0 | 4467 | GsmPDUHelper.writeHexOctet(smscLength); |
michael@0 | 4468 | Buf.copyIncomingToOutgoing(Buf.PDU_HEX_OCTET_SIZE * smscLength); |
michael@0 | 4469 | } |
michael@0 | 4470 | |
michael@0 | 4471 | // SMS TPDU-TLV |
michael@0 | 4472 | GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_SMS_TPDU | |
michael@0 | 4473 | COMPREHENSIONTLV_FLAG_CR); |
michael@0 | 4474 | if (tpduLength > 127) { |
michael@0 | 4475 | GsmPDUHelper.writeHexOctet(0x81); |
michael@0 | 4476 | } |
michael@0 | 4477 | GsmPDUHelper.writeHexOctet(tpduLength); |
michael@0 | 4478 | Buf.copyIncomingToOutgoing(Buf.PDU_HEX_OCTET_SIZE * tpduLength); |
michael@0 | 4479 | |
michael@0 | 4480 | // Write 2 string delimitors for the total string length must be even. |
michael@0 | 4481 | Buf.writeStringDelimiter(0); |
michael@0 | 4482 | |
michael@0 | 4483 | Buf.sendParcel(); |
michael@0 | 4484 | }, |
michael@0 | 4485 | |
michael@0 | 4486 | /** |
michael@0 | 4487 | * @param success A boolean value indicating the result of previous |
michael@0 | 4488 | * SMS-DELIVER message handling. |
michael@0 | 4489 | * @param responsePduLen ICC IO response PDU length in octets. |
michael@0 | 4490 | * @param options An object that contains four attributes: `pid`, `dcs`, |
michael@0 | 4491 | * `encoding` and `responsePduLen`. |
michael@0 | 4492 | * |
michael@0 | 4493 | * @see 3GPP TS 23.040 section 9.2.2.1a |
michael@0 | 4494 | */ |
michael@0 | 4495 | acknowledgeIncomingGsmSmsWithPDU: function(success, responsePduLen, options) { |
michael@0 | 4496 | let Buf = this.context.Buf; |
michael@0 | 4497 | let GsmPDUHelper = this.context.GsmPDUHelper; |
michael@0 | 4498 | |
michael@0 | 4499 | Buf.newParcel(REQUEST_ACKNOWLEDGE_INCOMING_GSM_SMS_WITH_PDU); |
michael@0 | 4500 | |
michael@0 | 4501 | // Two strings. |
michael@0 | 4502 | Buf.writeInt32(2); |
michael@0 | 4503 | |
michael@0 | 4504 | // String 1: Success |
michael@0 | 4505 | Buf.writeString(success ? "1" : "0"); |
michael@0 | 4506 | |
michael@0 | 4507 | // String 2: RP-ACK/RP-ERROR PDU |
michael@0 | 4508 | Buf.writeInt32(2 * (responsePduLen + (success ? 5 : 6))); // In semi-octet |
michael@0 | 4509 | // 1. TP-MTI & TP-UDHI |
michael@0 | 4510 | GsmPDUHelper.writeHexOctet(PDU_MTI_SMS_DELIVER); |
michael@0 | 4511 | if (!success) { |
michael@0 | 4512 | // 2. TP-FCS |
michael@0 | 4513 | GsmPDUHelper.writeHexOctet(PDU_FCS_USIM_DATA_DOWNLOAD_ERROR); |
michael@0 | 4514 | } |
michael@0 | 4515 | // 3. TP-PI |
michael@0 | 4516 | GsmPDUHelper.writeHexOctet(PDU_PI_USER_DATA_LENGTH | |
michael@0 | 4517 | PDU_PI_DATA_CODING_SCHEME | |
michael@0 | 4518 | PDU_PI_PROTOCOL_IDENTIFIER); |
michael@0 | 4519 | // 4. TP-PID |
michael@0 | 4520 | GsmPDUHelper.writeHexOctet(options.pid); |
michael@0 | 4521 | // 5. TP-DCS |
michael@0 | 4522 | GsmPDUHelper.writeHexOctet(options.dcs); |
michael@0 | 4523 | // 6. TP-UDL |
michael@0 | 4524 | if (options.encoding == PDU_DCS_MSG_CODING_7BITS_ALPHABET) { |
michael@0 | 4525 | GsmPDUHelper.writeHexOctet(Math.floor(responsePduLen * 8 / 7)); |
michael@0 | 4526 | } else { |
michael@0 | 4527 | GsmPDUHelper.writeHexOctet(responsePduLen); |
michael@0 | 4528 | } |
michael@0 | 4529 | // TP-UD |
michael@0 | 4530 | Buf.copyIncomingToOutgoing(Buf.PDU_HEX_OCTET_SIZE * responsePduLen); |
michael@0 | 4531 | // Write 2 string delimitors for the total string length must be even. |
michael@0 | 4532 | Buf.writeStringDelimiter(0); |
michael@0 | 4533 | |
michael@0 | 4534 | Buf.sendParcel(); |
michael@0 | 4535 | }, |
michael@0 | 4536 | |
michael@0 | 4537 | /** |
michael@0 | 4538 | * @param message A decoded SMS-DELIVER message. |
michael@0 | 4539 | */ |
michael@0 | 4540 | writeSmsToSIM: function(message) { |
michael@0 | 4541 | let Buf = this.context.Buf; |
michael@0 | 4542 | let GsmPDUHelper = this.context.GsmPDUHelper; |
michael@0 | 4543 | |
michael@0 | 4544 | Buf.newParcel(REQUEST_WRITE_SMS_TO_SIM); |
michael@0 | 4545 | |
michael@0 | 4546 | // Write EFsms Status |
michael@0 | 4547 | Buf.writeInt32(EFSMS_STATUS_FREE); |
michael@0 | 4548 | |
michael@0 | 4549 | Buf.seekIncoming(-1 * (Buf.getCurrentParcelSize() - Buf.getReadAvailable() |
michael@0 | 4550 | - 2 * Buf.UINT32_SIZE)); // Skip response_type & request_type. |
michael@0 | 4551 | let messageStringLength = Buf.readInt32(); // In semi-octets |
michael@0 | 4552 | let smscLength = GsmPDUHelper.readHexOctet(); // In octets, inclusive of TOA |
michael@0 | 4553 | let pduLength = (messageStringLength / 2) - (smscLength + 1); // In octets |
michael@0 | 4554 | |
michael@0 | 4555 | // 1. Write PDU first. |
michael@0 | 4556 | if (smscLength > 0) { |
michael@0 | 4557 | Buf.seekIncoming(smscLength * Buf.PDU_HEX_OCTET_SIZE); |
michael@0 | 4558 | } |
michael@0 | 4559 | // Write EFsms PDU string length |
michael@0 | 4560 | Buf.writeInt32(2 * pduLength); // In semi-octets |
michael@0 | 4561 | if (pduLength) { |
michael@0 | 4562 | Buf.copyIncomingToOutgoing(Buf.PDU_HEX_OCTET_SIZE * pduLength); |
michael@0 | 4563 | } |
michael@0 | 4564 | // Write 2 string delimitors for the total string length must be even. |
michael@0 | 4565 | Buf.writeStringDelimiter(0); |
michael@0 | 4566 | |
michael@0 | 4567 | // 2. Write SMSC |
michael@0 | 4568 | // Write EFsms SMSC string length |
michael@0 | 4569 | Buf.writeInt32(2 * (smscLength + 1)); // Plus smscLength itself, in semi-octets |
michael@0 | 4570 | // Write smscLength |
michael@0 | 4571 | GsmPDUHelper.writeHexOctet(smscLength); |
michael@0 | 4572 | // Write TOA & SMSC Address |
michael@0 | 4573 | if (smscLength) { |
michael@0 | 4574 | Buf.seekIncoming(-1 * (Buf.getCurrentParcelSize() - Buf.getReadAvailable() |
michael@0 | 4575 | - 2 * Buf.UINT32_SIZE // Skip response_type, request_type. |
michael@0 | 4576 | - 2 * Buf.PDU_HEX_OCTET_SIZE)); // Skip messageStringLength & smscLength. |
michael@0 | 4577 | Buf.copyIncomingToOutgoing(Buf.PDU_HEX_OCTET_SIZE * smscLength); |
michael@0 | 4578 | } |
michael@0 | 4579 | // Write 2 string delimitors for the total string length must be even. |
michael@0 | 4580 | Buf.writeStringDelimiter(0); |
michael@0 | 4581 | |
michael@0 | 4582 | Buf.sendParcel(); |
michael@0 | 4583 | }, |
michael@0 | 4584 | |
michael@0 | 4585 | /** |
michael@0 | 4586 | * Helper to delegate the received sms segment to RadioInterface to process. |
michael@0 | 4587 | * |
michael@0 | 4588 | * @param message |
michael@0 | 4589 | * Received sms message. |
michael@0 | 4590 | * |
michael@0 | 4591 | * @return MOZ_FCS_WAIT_FOR_EXPLICIT_ACK |
michael@0 | 4592 | */ |
michael@0 | 4593 | _processSmsMultipart: function(message) { |
michael@0 | 4594 | message.rilMessageType = "sms-received"; |
michael@0 | 4595 | |
michael@0 | 4596 | this.sendChromeMessage(message); |
michael@0 | 4597 | |
michael@0 | 4598 | return MOZ_FCS_WAIT_FOR_EXPLICIT_ACK; |
michael@0 | 4599 | }, |
michael@0 | 4600 | |
michael@0 | 4601 | /** |
michael@0 | 4602 | * Helper for processing SMS-STATUS-REPORT PDUs. |
michael@0 | 4603 | * |
michael@0 | 4604 | * @param length |
michael@0 | 4605 | * Length of SMS string in the incoming parcel. |
michael@0 | 4606 | * |
michael@0 | 4607 | * @return A failure cause defined in 3GPP 23.040 clause 9.2.3.22. |
michael@0 | 4608 | */ |
michael@0 | 4609 | _processSmsStatusReport: function(length) { |
michael@0 | 4610 | let [message, result] = this.context.GsmPDUHelper.processReceivedSms(length); |
michael@0 | 4611 | if (!message) { |
michael@0 | 4612 | if (DEBUG) this.context.debug("invalid SMS-STATUS-REPORT"); |
michael@0 | 4613 | return PDU_FCS_UNSPECIFIED; |
michael@0 | 4614 | } |
michael@0 | 4615 | |
michael@0 | 4616 | let options = this._pendingSentSmsMap[message.messageRef]; |
michael@0 | 4617 | if (!options) { |
michael@0 | 4618 | if (DEBUG) this.context.debug("no pending SMS-SUBMIT message"); |
michael@0 | 4619 | return PDU_FCS_OK; |
michael@0 | 4620 | } |
michael@0 | 4621 | |
michael@0 | 4622 | let status = message.status; |
michael@0 | 4623 | |
michael@0 | 4624 | // 3GPP TS 23.040 9.2.3.15 `The MS shall interpret any reserved values as |
michael@0 | 4625 | // "Service Rejected"(01100011) but shall store them exactly as received.` |
michael@0 | 4626 | if ((status >= 0x80) |
michael@0 | 4627 | || ((status >= PDU_ST_0_RESERVED_BEGIN) |
michael@0 | 4628 | && (status < PDU_ST_0_SC_SPECIFIC_BEGIN)) |
michael@0 | 4629 | || ((status >= PDU_ST_1_RESERVED_BEGIN) |
michael@0 | 4630 | && (status < PDU_ST_1_SC_SPECIFIC_BEGIN)) |
michael@0 | 4631 | || ((status >= PDU_ST_2_RESERVED_BEGIN) |
michael@0 | 4632 | && (status < PDU_ST_2_SC_SPECIFIC_BEGIN)) |
michael@0 | 4633 | || ((status >= PDU_ST_3_RESERVED_BEGIN) |
michael@0 | 4634 | && (status < PDU_ST_3_SC_SPECIFIC_BEGIN)) |
michael@0 | 4635 | ) { |
michael@0 | 4636 | status = PDU_ST_3_SERVICE_REJECTED; |
michael@0 | 4637 | } |
michael@0 | 4638 | |
michael@0 | 4639 | // Pending. Waiting for next status report. |
michael@0 | 4640 | if ((status >>> 5) == 0x01) { |
michael@0 | 4641 | if (DEBUG) this.context.debug("SMS-STATUS-REPORT: delivery still pending"); |
michael@0 | 4642 | return PDU_FCS_OK; |
michael@0 | 4643 | } |
michael@0 | 4644 | |
michael@0 | 4645 | delete this._pendingSentSmsMap[message.messageRef]; |
michael@0 | 4646 | |
michael@0 | 4647 | let deliveryStatus = ((status >>> 5) === 0x00) |
michael@0 | 4648 | ? GECKO_SMS_DELIVERY_STATUS_SUCCESS |
michael@0 | 4649 | : GECKO_SMS_DELIVERY_STATUS_ERROR; |
michael@0 | 4650 | this.sendChromeMessage({ |
michael@0 | 4651 | rilMessageType: options.rilMessageType, |
michael@0 | 4652 | rilMessageToken: options.rilMessageToken, |
michael@0 | 4653 | deliveryStatus: deliveryStatus |
michael@0 | 4654 | }); |
michael@0 | 4655 | |
michael@0 | 4656 | return PDU_FCS_OK; |
michael@0 | 4657 | }, |
michael@0 | 4658 | |
michael@0 | 4659 | /** |
michael@0 | 4660 | * Helper for processing CDMA SMS Delivery Acknowledgment Message |
michael@0 | 4661 | * |
michael@0 | 4662 | * @param message |
michael@0 | 4663 | * decoded SMS Delivery ACK message from CdmaPDUHelper. |
michael@0 | 4664 | * |
michael@0 | 4665 | * @return A failure cause defined in 3GPP 23.040 clause 9.2.3.22. |
michael@0 | 4666 | */ |
michael@0 | 4667 | _processCdmaSmsStatusReport: function(message) { |
michael@0 | 4668 | let options = this._pendingSentSmsMap[message.msgId]; |
michael@0 | 4669 | if (!options) { |
michael@0 | 4670 | if (DEBUG) this.context.debug("no pending SMS-SUBMIT message"); |
michael@0 | 4671 | return PDU_FCS_OK; |
michael@0 | 4672 | } |
michael@0 | 4673 | |
michael@0 | 4674 | if (message.errorClass === 2) { |
michael@0 | 4675 | if (DEBUG) { |
michael@0 | 4676 | this.context.debug("SMS-STATUS-REPORT: delivery still pending, " + |
michael@0 | 4677 | "msgStatus: " + message.msgStatus); |
michael@0 | 4678 | } |
michael@0 | 4679 | return PDU_FCS_OK; |
michael@0 | 4680 | } |
michael@0 | 4681 | |
michael@0 | 4682 | delete this._pendingSentSmsMap[message.msgId]; |
michael@0 | 4683 | |
michael@0 | 4684 | if (message.errorClass === -1 && message.body) { |
michael@0 | 4685 | // Process as normal incoming SMS, if errorClass is invalid |
michael@0 | 4686 | // but message body is available. |
michael@0 | 4687 | return this._processSmsMultipart(message); |
michael@0 | 4688 | } |
michael@0 | 4689 | |
michael@0 | 4690 | let deliveryStatus = (message.errorClass === 0) |
michael@0 | 4691 | ? GECKO_SMS_DELIVERY_STATUS_SUCCESS |
michael@0 | 4692 | : GECKO_SMS_DELIVERY_STATUS_ERROR; |
michael@0 | 4693 | this.sendChromeMessage({ |
michael@0 | 4694 | rilMessageType: options.rilMessageType, |
michael@0 | 4695 | rilMessageToken: options.rilMessageToken, |
michael@0 | 4696 | deliveryStatus: deliveryStatus |
michael@0 | 4697 | }); |
michael@0 | 4698 | |
michael@0 | 4699 | return PDU_FCS_OK; |
michael@0 | 4700 | }, |
michael@0 | 4701 | |
michael@0 | 4702 | /** |
michael@0 | 4703 | * Helper for processing CDMA SMS WAP Push Message |
michael@0 | 4704 | * |
michael@0 | 4705 | * @param message |
michael@0 | 4706 | * decoded WAP message from CdmaPDUHelper. |
michael@0 | 4707 | * |
michael@0 | 4708 | * @return A failure cause defined in 3GPP 23.040 clause 9.2.3.22. |
michael@0 | 4709 | */ |
michael@0 | 4710 | _processCdmaSmsWapPush: function(message) { |
michael@0 | 4711 | if (!message.data) { |
michael@0 | 4712 | if (DEBUG) this.context.debug("no data inside WAP Push message."); |
michael@0 | 4713 | return PDU_FCS_OK; |
michael@0 | 4714 | } |
michael@0 | 4715 | |
michael@0 | 4716 | // See 6.5. MAPPING OF WDP TO CDMA SMS in WAP-295-WDP. |
michael@0 | 4717 | // |
michael@0 | 4718 | // Field | Length (bits) |
michael@0 | 4719 | // ----------------------------------------- |
michael@0 | 4720 | // MSG_TYPE | 8 |
michael@0 | 4721 | // TOTAL_SEGMENTS | 8 |
michael@0 | 4722 | // SEGMENT_NUMBER | 8 |
michael@0 | 4723 | // DATAGRAM | (NUM_FIELDS – 3) * 8 |
michael@0 | 4724 | let index = 0; |
michael@0 | 4725 | if (message.data[index++] !== 0) { |
michael@0 | 4726 | if (DEBUG) this.context.debug("Ignore a WAP Message which is not WDP."); |
michael@0 | 4727 | return PDU_FCS_OK; |
michael@0 | 4728 | } |
michael@0 | 4729 | |
michael@0 | 4730 | // 1. Originator Address in SMS-TL + Message_Id in SMS-TS are used to identify a unique WDP datagram. |
michael@0 | 4731 | // 2. TOTAL_SEGMENTS, SEGMENT_NUMBER are used to verify that a complete |
michael@0 | 4732 | // datagram has been received and is ready to be passed to a higher layer. |
michael@0 | 4733 | message.header = { |
michael@0 | 4734 | segmentRef: message.msgId, |
michael@0 | 4735 | segmentMaxSeq: message.data[index++], |
michael@0 | 4736 | segmentSeq: message.data[index++] + 1 // It's zero-based in CDMA WAP Push. |
michael@0 | 4737 | }; |
michael@0 | 4738 | |
michael@0 | 4739 | if (message.header.segmentSeq > message.header.segmentMaxSeq) { |
michael@0 | 4740 | if (DEBUG) this.context.debug("Wrong WDP segment info."); |
michael@0 | 4741 | return PDU_FCS_OK; |
michael@0 | 4742 | } |
michael@0 | 4743 | |
michael@0 | 4744 | // Ports are only specified in 1st segment. |
michael@0 | 4745 | if (message.header.segmentSeq == 1) { |
michael@0 | 4746 | message.header.originatorPort = message.data[index++] << 8; |
michael@0 | 4747 | message.header.originatorPort |= message.data[index++]; |
michael@0 | 4748 | message.header.destinationPort = message.data[index++] << 8; |
michael@0 | 4749 | message.header.destinationPort |= message.data[index++]; |
michael@0 | 4750 | } |
michael@0 | 4751 | |
michael@0 | 4752 | message.data = message.data.subarray(index); |
michael@0 | 4753 | |
michael@0 | 4754 | return this._processSmsMultipart(message); |
michael@0 | 4755 | }, |
michael@0 | 4756 | |
michael@0 | 4757 | /** |
michael@0 | 4758 | * Helper for processing sent multipart SMS. |
michael@0 | 4759 | */ |
michael@0 | 4760 | _processSentSmsSegment: function(options) { |
michael@0 | 4761 | // Setup attributes for sending next segment |
michael@0 | 4762 | let next = options.segmentSeq; |
michael@0 | 4763 | options.body = options.segments[next].body; |
michael@0 | 4764 | options.encodedBodyLength = options.segments[next].encodedBodyLength; |
michael@0 | 4765 | options.segmentSeq = next + 1; |
michael@0 | 4766 | |
michael@0 | 4767 | this.sendSMS(options); |
michael@0 | 4768 | }, |
michael@0 | 4769 | |
michael@0 | 4770 | /** |
michael@0 | 4771 | * Helper for processing result of send SMS. |
michael@0 | 4772 | * |
michael@0 | 4773 | * @param length |
michael@0 | 4774 | * Length of SMS string in the incoming parcel. |
michael@0 | 4775 | * @param options |
michael@0 | 4776 | * Sms information. |
michael@0 | 4777 | */ |
michael@0 | 4778 | _processSmsSendResult: function(length, options) { |
michael@0 | 4779 | if (options.rilRequestError) { |
michael@0 | 4780 | if (DEBUG) { |
michael@0 | 4781 | this.context.debug("_processSmsSendResult: rilRequestError = " + |
michael@0 | 4782 | options.rilRequestError); |
michael@0 | 4783 | } |
michael@0 | 4784 | switch (options.rilRequestError) { |
michael@0 | 4785 | case ERROR_SMS_SEND_FAIL_RETRY: |
michael@0 | 4786 | if (options.retryCount < SMS_RETRY_MAX) { |
michael@0 | 4787 | options.retryCount++; |
michael@0 | 4788 | // TODO: bug 736702 TP-MR, retry interval, retry timeout |
michael@0 | 4789 | this.sendSMS(options); |
michael@0 | 4790 | break; |
michael@0 | 4791 | } |
michael@0 | 4792 | // Fallback to default error handling if it meets max retry count. |
michael@0 | 4793 | // Fall through. |
michael@0 | 4794 | default: |
michael@0 | 4795 | this.sendChromeMessage({ |
michael@0 | 4796 | rilMessageType: options.rilMessageType, |
michael@0 | 4797 | rilMessageToken: options.rilMessageToken, |
michael@0 | 4798 | errorMsg: options.rilRequestError, |
michael@0 | 4799 | }); |
michael@0 | 4800 | break; |
michael@0 | 4801 | } |
michael@0 | 4802 | return; |
michael@0 | 4803 | } |
michael@0 | 4804 | |
michael@0 | 4805 | let Buf = this.context.Buf; |
michael@0 | 4806 | options.messageRef = Buf.readInt32(); |
michael@0 | 4807 | options.ackPDU = Buf.readString(); |
michael@0 | 4808 | options.errorCode = Buf.readInt32(); |
michael@0 | 4809 | |
michael@0 | 4810 | if ((options.segmentMaxSeq > 1) |
michael@0 | 4811 | && (options.segmentSeq < options.segmentMaxSeq)) { |
michael@0 | 4812 | // Not last segment |
michael@0 | 4813 | this._processSentSmsSegment(options); |
michael@0 | 4814 | } else { |
michael@0 | 4815 | // Last segment sent with success. |
michael@0 | 4816 | if (options.requestStatusReport) { |
michael@0 | 4817 | if (DEBUG) { |
michael@0 | 4818 | this.context.debug("waiting SMS-STATUS-REPORT for messageRef " + |
michael@0 | 4819 | options.messageRef); |
michael@0 | 4820 | } |
michael@0 | 4821 | this._pendingSentSmsMap[options.messageRef] = options; |
michael@0 | 4822 | } |
michael@0 | 4823 | |
michael@0 | 4824 | this.sendChromeMessage({ |
michael@0 | 4825 | rilMessageType: options.rilMessageType, |
michael@0 | 4826 | rilMessageToken: options.rilMessageToken, |
michael@0 | 4827 | }); |
michael@0 | 4828 | } |
michael@0 | 4829 | }, |
michael@0 | 4830 | |
michael@0 | 4831 | _processReceivedSmsCbPage: function(original) { |
michael@0 | 4832 | if (original.numPages <= 1) { |
michael@0 | 4833 | if (original.body) { |
michael@0 | 4834 | original.fullBody = original.body; |
michael@0 | 4835 | delete original.body; |
michael@0 | 4836 | } else if (original.data) { |
michael@0 | 4837 | original.fullData = original.data; |
michael@0 | 4838 | delete original.data; |
michael@0 | 4839 | } |
michael@0 | 4840 | return original; |
michael@0 | 4841 | } |
michael@0 | 4842 | |
michael@0 | 4843 | // Hash = <serial>:<mcc>:<mnc>:<lac>:<cid> |
michael@0 | 4844 | let hash = original.serial + ":" + this.iccInfo.mcc + ":" |
michael@0 | 4845 | + this.iccInfo.mnc + ":"; |
michael@0 | 4846 | switch (original.geographicalScope) { |
michael@0 | 4847 | case CB_GSM_GEOGRAPHICAL_SCOPE_CELL_WIDE_IMMEDIATE: |
michael@0 | 4848 | case CB_GSM_GEOGRAPHICAL_SCOPE_CELL_WIDE: |
michael@0 | 4849 | hash += this.voiceRegistrationState.cell.gsmLocationAreaCode + ":" |
michael@0 | 4850 | + this.voiceRegistrationState.cell.gsmCellId; |
michael@0 | 4851 | break; |
michael@0 | 4852 | case CB_GSM_GEOGRAPHICAL_SCOPE_LOCATION_AREA_WIDE: |
michael@0 | 4853 | hash += this.voiceRegistrationState.cell.gsmLocationAreaCode + ":"; |
michael@0 | 4854 | break; |
michael@0 | 4855 | default: |
michael@0 | 4856 | hash += ":"; |
michael@0 | 4857 | break; |
michael@0 | 4858 | } |
michael@0 | 4859 | |
michael@0 | 4860 | let index = original.pageIndex; |
michael@0 | 4861 | |
michael@0 | 4862 | let options = this._receivedSmsCbPagesMap[hash]; |
michael@0 | 4863 | if (!options) { |
michael@0 | 4864 | options = original; |
michael@0 | 4865 | this._receivedSmsCbPagesMap[hash] = options; |
michael@0 | 4866 | |
michael@0 | 4867 | options.receivedPages = 0; |
michael@0 | 4868 | options.pages = []; |
michael@0 | 4869 | } else if (options.pages[index]) { |
michael@0 | 4870 | // Duplicated page? |
michael@0 | 4871 | if (DEBUG) { |
michael@0 | 4872 | this.context.debug("Got duplicated page no." + index + |
michael@0 | 4873 | " of a multipage SMSCB: " + JSON.stringify(original)); |
michael@0 | 4874 | } |
michael@0 | 4875 | return null; |
michael@0 | 4876 | } |
michael@0 | 4877 | |
michael@0 | 4878 | if (options.encoding == PDU_DCS_MSG_CODING_8BITS_ALPHABET) { |
michael@0 | 4879 | options.pages[index] = original.data; |
michael@0 | 4880 | delete original.data; |
michael@0 | 4881 | } else { |
michael@0 | 4882 | options.pages[index] = original.body; |
michael@0 | 4883 | delete original.body; |
michael@0 | 4884 | } |
michael@0 | 4885 | options.receivedPages++; |
michael@0 | 4886 | if (options.receivedPages < options.numPages) { |
michael@0 | 4887 | if (DEBUG) { |
michael@0 | 4888 | this.context.debug("Got page no." + index + " of a multipage SMSCB: " + |
michael@0 | 4889 | JSON.stringify(options)); |
michael@0 | 4890 | } |
michael@0 | 4891 | return null; |
michael@0 | 4892 | } |
michael@0 | 4893 | |
michael@0 | 4894 | // Remove from map |
michael@0 | 4895 | delete this._receivedSmsCbPagesMap[hash]; |
michael@0 | 4896 | |
michael@0 | 4897 | // Rebuild full body |
michael@0 | 4898 | if (options.encoding == PDU_DCS_MSG_CODING_8BITS_ALPHABET) { |
michael@0 | 4899 | // Uint8Array doesn't have `concat`, so we have to merge all pages by hand. |
michael@0 | 4900 | let fullDataLen = 0; |
michael@0 | 4901 | for (let i = 1; i <= options.numPages; i++) { |
michael@0 | 4902 | fullDataLen += options.pages[i].length; |
michael@0 | 4903 | } |
michael@0 | 4904 | |
michael@0 | 4905 | options.fullData = new Uint8Array(fullDataLen); |
michael@0 | 4906 | for (let d= 0, i = 1; i <= options.numPages; i++) { |
michael@0 | 4907 | let data = options.pages[i]; |
michael@0 | 4908 | for (let j = 0; j < data.length; j++) { |
michael@0 | 4909 | options.fullData[d++] = data[j]; |
michael@0 | 4910 | } |
michael@0 | 4911 | } |
michael@0 | 4912 | } else { |
michael@0 | 4913 | options.fullBody = options.pages.join(""); |
michael@0 | 4914 | } |
michael@0 | 4915 | |
michael@0 | 4916 | if (DEBUG) { |
michael@0 | 4917 | this.context.debug("Got full multipage SMSCB: " + JSON.stringify(options)); |
michael@0 | 4918 | } |
michael@0 | 4919 | |
michael@0 | 4920 | return options; |
michael@0 | 4921 | }, |
michael@0 | 4922 | |
michael@0 | 4923 | _mergeCellBroadcastConfigs: function(list, from, to) { |
michael@0 | 4924 | if (!list) { |
michael@0 | 4925 | return [from, to]; |
michael@0 | 4926 | } |
michael@0 | 4927 | |
michael@0 | 4928 | for (let i = 0, f1, t1; i < list.length;) { |
michael@0 | 4929 | f1 = list[i++]; |
michael@0 | 4930 | t1 = list[i++]; |
michael@0 | 4931 | if (to == f1) { |
michael@0 | 4932 | // ...[from]...[to|f1]...(t1) |
michael@0 | 4933 | list[i - 2] = from; |
michael@0 | 4934 | return list; |
michael@0 | 4935 | } |
michael@0 | 4936 | |
michael@0 | 4937 | if (to < f1) { |
michael@0 | 4938 | // ...[from]...(to)...[f1] or ...[from]...(to)[f1] |
michael@0 | 4939 | if (i > 2) { |
michael@0 | 4940 | // Not the first range pair, merge three arrays. |
michael@0 | 4941 | return list.slice(0, i - 2).concat([from, to]).concat(list.slice(i - 2)); |
michael@0 | 4942 | } else { |
michael@0 | 4943 | return [from, to].concat(list); |
michael@0 | 4944 | } |
michael@0 | 4945 | } |
michael@0 | 4946 | |
michael@0 | 4947 | if (from > t1) { |
michael@0 | 4948 | // ...[f1]...(t1)[from] or ...[f1]...(t1)...[from] |
michael@0 | 4949 | continue; |
michael@0 | 4950 | } |
michael@0 | 4951 | |
michael@0 | 4952 | // Have overlap or merge-able adjacency with [f1]...(t1). Replace it |
michael@0 | 4953 | // with [min(from, f1)]...(max(to, t1)). |
michael@0 | 4954 | |
michael@0 | 4955 | let changed = false; |
michael@0 | 4956 | if (from < f1) { |
michael@0 | 4957 | // [from]...[f1]...(t1) or [from][f1]...(t1) |
michael@0 | 4958 | // Save minimum from value. |
michael@0 | 4959 | list[i - 2] = from; |
michael@0 | 4960 | changed = true; |
michael@0 | 4961 | } |
michael@0 | 4962 | |
michael@0 | 4963 | if (to <= t1) { |
michael@0 | 4964 | // [from]...[to](t1) or [from]...(to|t1) |
michael@0 | 4965 | // Can't have further merge-able adjacency. Return. |
michael@0 | 4966 | return list; |
michael@0 | 4967 | } |
michael@0 | 4968 | |
michael@0 | 4969 | // Try merging possible next adjacent range. |
michael@0 | 4970 | let j = i; |
michael@0 | 4971 | for (let f2, t2; j < list.length;) { |
michael@0 | 4972 | f2 = list[j++]; |
michael@0 | 4973 | t2 = list[j++]; |
michael@0 | 4974 | if (to > t2) { |
michael@0 | 4975 | // [from]...[f2]...[t2]...(to) or [from]...[f2]...[t2](to) |
michael@0 | 4976 | // Merge next adjacent range again. |
michael@0 | 4977 | continue; |
michael@0 | 4978 | } |
michael@0 | 4979 | |
michael@0 | 4980 | if (to < t2) { |
michael@0 | 4981 | if (to < f2) { |
michael@0 | 4982 | // [from]...(to)[f2] or [from]...(to)...[f2] |
michael@0 | 4983 | // Roll back and give up. |
michael@0 | 4984 | j -= 2; |
michael@0 | 4985 | } else if (to < t2) { |
michael@0 | 4986 | // [from]...[to|f2]...(t2), or [from]...[f2]...[to](t2) |
michael@0 | 4987 | // Merge to [from]...(t2) and give up. |
michael@0 | 4988 | to = t2; |
michael@0 | 4989 | } |
michael@0 | 4990 | } |
michael@0 | 4991 | |
michael@0 | 4992 | break; |
michael@0 | 4993 | } |
michael@0 | 4994 | |
michael@0 | 4995 | // Save maximum to value. |
michael@0 | 4996 | list[i - 1] = to; |
michael@0 | 4997 | |
michael@0 | 4998 | if (j != i) { |
michael@0 | 4999 | // Remove merged adjacent ranges. |
michael@0 | 5000 | let ret = list.slice(0, i); |
michael@0 | 5001 | if (j < list.length) { |
michael@0 | 5002 | ret = ret.concat(list.slice(j)); |
michael@0 | 5003 | } |
michael@0 | 5004 | return ret; |
michael@0 | 5005 | } |
michael@0 | 5006 | |
michael@0 | 5007 | return list; |
michael@0 | 5008 | } |
michael@0 | 5009 | |
michael@0 | 5010 | // Append to the end. |
michael@0 | 5011 | list.push(from); |
michael@0 | 5012 | list.push(to); |
michael@0 | 5013 | |
michael@0 | 5014 | return list; |
michael@0 | 5015 | }, |
michael@0 | 5016 | |
michael@0 | 5017 | _isCellBroadcastConfigReady: function() { |
michael@0 | 5018 | if (!("MMI" in this.cellBroadcastConfigs)) { |
michael@0 | 5019 | return false; |
michael@0 | 5020 | } |
michael@0 | 5021 | |
michael@0 | 5022 | // CBMI should be ready in GSM. |
michael@0 | 5023 | if (!this._isCdma && |
michael@0 | 5024 | (!("CBMI" in this.cellBroadcastConfigs) || |
michael@0 | 5025 | !("CBMID" in this.cellBroadcastConfigs) || |
michael@0 | 5026 | !("CBMIR" in this.cellBroadcastConfigs))) { |
michael@0 | 5027 | return false; |
michael@0 | 5028 | } |
michael@0 | 5029 | |
michael@0 | 5030 | return true; |
michael@0 | 5031 | }, |
michael@0 | 5032 | |
michael@0 | 5033 | /** |
michael@0 | 5034 | * Merge all members of cellBroadcastConfigs into mergedCellBroadcastConfig. |
michael@0 | 5035 | */ |
michael@0 | 5036 | _mergeAllCellBroadcastConfigs: function() { |
michael@0 | 5037 | if (!this._isCellBroadcastConfigReady()) { |
michael@0 | 5038 | if (DEBUG) { |
michael@0 | 5039 | this.context.debug("cell broadcast configs not ready, waiting ..."); |
michael@0 | 5040 | } |
michael@0 | 5041 | return; |
michael@0 | 5042 | } |
michael@0 | 5043 | |
michael@0 | 5044 | // Prepare cell broadcast config. CBMI* are only used in GSM. |
michael@0 | 5045 | let usedCellBroadcastConfigs = {MMI: this.cellBroadcastConfigs.MMI}; |
michael@0 | 5046 | if (!this._isCdma) { |
michael@0 | 5047 | usedCellBroadcastConfigs.CBMI = this.cellBroadcastConfigs.CBMI; |
michael@0 | 5048 | usedCellBroadcastConfigs.CBMID = this.cellBroadcastConfigs.CBMID; |
michael@0 | 5049 | usedCellBroadcastConfigs.CBMIR = this.cellBroadcastConfigs.CBMIR; |
michael@0 | 5050 | } |
michael@0 | 5051 | |
michael@0 | 5052 | if (DEBUG) { |
michael@0 | 5053 | this.context.debug("Cell Broadcast search lists: " + |
michael@0 | 5054 | JSON.stringify(usedCellBroadcastConfigs)); |
michael@0 | 5055 | } |
michael@0 | 5056 | |
michael@0 | 5057 | let list = null; |
michael@0 | 5058 | for each (let ll in usedCellBroadcastConfigs) { |
michael@0 | 5059 | if (ll == null) { |
michael@0 | 5060 | continue; |
michael@0 | 5061 | } |
michael@0 | 5062 | |
michael@0 | 5063 | for (let i = 0; i < ll.length; i += 2) { |
michael@0 | 5064 | list = this._mergeCellBroadcastConfigs(list, ll[i], ll[i + 1]); |
michael@0 | 5065 | } |
michael@0 | 5066 | } |
michael@0 | 5067 | |
michael@0 | 5068 | if (DEBUG) { |
michael@0 | 5069 | this.context.debug("Cell Broadcast search lists(merged): " + |
michael@0 | 5070 | JSON.stringify(list)); |
michael@0 | 5071 | } |
michael@0 | 5072 | this.mergedCellBroadcastConfig = list; |
michael@0 | 5073 | this.updateCellBroadcastConfig(); |
michael@0 | 5074 | }, |
michael@0 | 5075 | |
michael@0 | 5076 | /** |
michael@0 | 5077 | * Check whether search list from settings is settable by MMI, that is, |
michael@0 | 5078 | * whether the range is bounded in any entries of CB_NON_MMI_SETTABLE_RANGES. |
michael@0 | 5079 | */ |
michael@0 | 5080 | _checkCellBroadcastMMISettable: function(from, to) { |
michael@0 | 5081 | if ((to <= from) || (from >= 65536) || (from < 0)) { |
michael@0 | 5082 | return false; |
michael@0 | 5083 | } |
michael@0 | 5084 | |
michael@0 | 5085 | if (!this._isCdma) { |
michael@0 | 5086 | // GSM not settable ranges. |
michael@0 | 5087 | for (let i = 0, f, t; i < CB_NON_MMI_SETTABLE_RANGES.length;) { |
michael@0 | 5088 | f = CB_NON_MMI_SETTABLE_RANGES[i++]; |
michael@0 | 5089 | t = CB_NON_MMI_SETTABLE_RANGES[i++]; |
michael@0 | 5090 | if ((from < t) && (to > f)) { |
michael@0 | 5091 | // Have overlap. |
michael@0 | 5092 | return false; |
michael@0 | 5093 | } |
michael@0 | 5094 | } |
michael@0 | 5095 | } |
michael@0 | 5096 | |
michael@0 | 5097 | return true; |
michael@0 | 5098 | }, |
michael@0 | 5099 | |
michael@0 | 5100 | /** |
michael@0 | 5101 | * Convert Cell Broadcast settings string into search list. |
michael@0 | 5102 | */ |
michael@0 | 5103 | _convertCellBroadcastSearchList: function(searchListStr) { |
michael@0 | 5104 | let parts = searchListStr && searchListStr.split(","); |
michael@0 | 5105 | if (!parts) { |
michael@0 | 5106 | return null; |
michael@0 | 5107 | } |
michael@0 | 5108 | |
michael@0 | 5109 | let list = null; |
michael@0 | 5110 | let result, from, to; |
michael@0 | 5111 | for (let range of parts) { |
michael@0 | 5112 | // Match "12" or "12-34". The result will be ["12", "12", null] or |
michael@0 | 5113 | // ["12-34", "12", "34"]. |
michael@0 | 5114 | result = range.match(/^(\d+)(?:-(\d+))?$/); |
michael@0 | 5115 | if (!result) { |
michael@0 | 5116 | throw "Invalid format"; |
michael@0 | 5117 | } |
michael@0 | 5118 | |
michael@0 | 5119 | from = parseInt(result[1], 10); |
michael@0 | 5120 | to = (result[2]) ? parseInt(result[2], 10) + 1 : from + 1; |
michael@0 | 5121 | if (!this._checkCellBroadcastMMISettable(from, to)) { |
michael@0 | 5122 | throw "Invalid range"; |
michael@0 | 5123 | } |
michael@0 | 5124 | |
michael@0 | 5125 | if (list == null) { |
michael@0 | 5126 | list = []; |
michael@0 | 5127 | } |
michael@0 | 5128 | list.push(from); |
michael@0 | 5129 | list.push(to); |
michael@0 | 5130 | } |
michael@0 | 5131 | |
michael@0 | 5132 | return list; |
michael@0 | 5133 | }, |
michael@0 | 5134 | |
michael@0 | 5135 | /** |
michael@0 | 5136 | * Handle incoming messages from the main UI thread. |
michael@0 | 5137 | * |
michael@0 | 5138 | * @param message |
michael@0 | 5139 | * Object containing the message. Messages are supposed |
michael@0 | 5140 | */ |
michael@0 | 5141 | handleChromeMessage: function(message) { |
michael@0 | 5142 | if (DEBUG) { |
michael@0 | 5143 | this.context.debug("Received chrome message " + JSON.stringify(message)); |
michael@0 | 5144 | } |
michael@0 | 5145 | let method = this[message.rilMessageType]; |
michael@0 | 5146 | if (typeof method != "function") { |
michael@0 | 5147 | if (DEBUG) { |
michael@0 | 5148 | this.context.debug("Don't know what to do with message " + |
michael@0 | 5149 | JSON.stringify(message)); |
michael@0 | 5150 | } |
michael@0 | 5151 | return; |
michael@0 | 5152 | } |
michael@0 | 5153 | method.call(this, message); |
michael@0 | 5154 | }, |
michael@0 | 5155 | |
michael@0 | 5156 | /** |
michael@0 | 5157 | * Get a list of current voice calls. |
michael@0 | 5158 | */ |
michael@0 | 5159 | enumerateCalls: function(options) { |
michael@0 | 5160 | if (DEBUG) this.context.debug("Sending all current calls"); |
michael@0 | 5161 | let calls = []; |
michael@0 | 5162 | for each (let call in this.currentCalls) { |
michael@0 | 5163 | calls.push(call); |
michael@0 | 5164 | } |
michael@0 | 5165 | options.calls = calls; |
michael@0 | 5166 | this.sendChromeMessage(options); |
michael@0 | 5167 | }, |
michael@0 | 5168 | |
michael@0 | 5169 | /** |
michael@0 | 5170 | * Process STK Proactive Command. |
michael@0 | 5171 | */ |
michael@0 | 5172 | processStkProactiveCommand: function() { |
michael@0 | 5173 | let Buf = this.context.Buf; |
michael@0 | 5174 | let length = Buf.readInt32(); |
michael@0 | 5175 | let berTlv; |
michael@0 | 5176 | try { |
michael@0 | 5177 | berTlv = this.context.BerTlvHelper.decode(length / 2); |
michael@0 | 5178 | } catch (e) { |
michael@0 | 5179 | if (DEBUG) this.context.debug("processStkProactiveCommand : " + e); |
michael@0 | 5180 | this.sendStkTerminalResponse({ |
michael@0 | 5181 | resultCode: STK_RESULT_CMD_DATA_NOT_UNDERSTOOD}); |
michael@0 | 5182 | return; |
michael@0 | 5183 | } |
michael@0 | 5184 | |
michael@0 | 5185 | Buf.readStringDelimiter(length); |
michael@0 | 5186 | |
michael@0 | 5187 | let ctlvs = berTlv.value; |
michael@0 | 5188 | let ctlv = this.context.StkProactiveCmdHelper.searchForTag( |
michael@0 | 5189 | COMPREHENSIONTLV_TAG_COMMAND_DETAILS, ctlvs); |
michael@0 | 5190 | if (!ctlv) { |
michael@0 | 5191 | this.sendStkTerminalResponse({ |
michael@0 | 5192 | resultCode: STK_RESULT_CMD_DATA_NOT_UNDERSTOOD}); |
michael@0 | 5193 | throw new Error("Can't find COMMAND_DETAILS ComprehensionTlv"); |
michael@0 | 5194 | } |
michael@0 | 5195 | |
michael@0 | 5196 | let cmdDetails = ctlv.value; |
michael@0 | 5197 | if (DEBUG) { |
michael@0 | 5198 | this.context.debug("commandNumber = " + cmdDetails.commandNumber + |
michael@0 | 5199 | " typeOfCommand = " + cmdDetails.typeOfCommand.toString(16) + |
michael@0 | 5200 | " commandQualifier = " + cmdDetails.commandQualifier); |
michael@0 | 5201 | } |
michael@0 | 5202 | |
michael@0 | 5203 | // STK_CMD_MORE_TIME need not to propagate event to chrome. |
michael@0 | 5204 | if (cmdDetails.typeOfCommand == STK_CMD_MORE_TIME) { |
michael@0 | 5205 | this.sendStkTerminalResponse({ |
michael@0 | 5206 | command: cmdDetails, |
michael@0 | 5207 | resultCode: STK_RESULT_OK}); |
michael@0 | 5208 | return; |
michael@0 | 5209 | } |
michael@0 | 5210 | |
michael@0 | 5211 | cmdDetails.rilMessageType = "stkcommand"; |
michael@0 | 5212 | cmdDetails.options = |
michael@0 | 5213 | this.context.StkCommandParamsFactory.createParam(cmdDetails, ctlvs); |
michael@0 | 5214 | this.sendChromeMessage(cmdDetails); |
michael@0 | 5215 | }, |
michael@0 | 5216 | |
michael@0 | 5217 | /** |
michael@0 | 5218 | * Send messages to the main thread. |
michael@0 | 5219 | */ |
michael@0 | 5220 | sendChromeMessage: function(message) { |
michael@0 | 5221 | message.rilMessageClientId = this.context.clientId; |
michael@0 | 5222 | postMessage(message); |
michael@0 | 5223 | }, |
michael@0 | 5224 | |
michael@0 | 5225 | /** |
michael@0 | 5226 | * Handle incoming requests from the RIL. We find the method that |
michael@0 | 5227 | * corresponds to the request type. Incidentally, the request type |
michael@0 | 5228 | * _is_ the method name, so that's easy. |
michael@0 | 5229 | */ |
michael@0 | 5230 | |
michael@0 | 5231 | handleParcel: function(request_type, length, options) { |
michael@0 | 5232 | let method = this[request_type]; |
michael@0 | 5233 | if (typeof method == "function") { |
michael@0 | 5234 | if (DEBUG) this.context.debug("Handling parcel as " + method.name); |
michael@0 | 5235 | method.call(this, length, options); |
michael@0 | 5236 | } |
michael@0 | 5237 | } |
michael@0 | 5238 | }; |
michael@0 | 5239 | |
michael@0 | 5240 | RilObject.prototype[REQUEST_GET_SIM_STATUS] = function REQUEST_GET_SIM_STATUS(length, options) { |
michael@0 | 5241 | if (options.rilRequestError) { |
michael@0 | 5242 | return; |
michael@0 | 5243 | } |
michael@0 | 5244 | |
michael@0 | 5245 | let iccStatus = {}; |
michael@0 | 5246 | let Buf = this.context.Buf; |
michael@0 | 5247 | iccStatus.cardState = Buf.readInt32(); // CARD_STATE_* |
michael@0 | 5248 | iccStatus.universalPINState = Buf.readInt32(); // CARD_PINSTATE_* |
michael@0 | 5249 | iccStatus.gsmUmtsSubscriptionAppIndex = Buf.readInt32(); |
michael@0 | 5250 | iccStatus.cdmaSubscriptionAppIndex = Buf.readInt32(); |
michael@0 | 5251 | if (!this.v5Legacy) { |
michael@0 | 5252 | iccStatus.imsSubscriptionAppIndex = Buf.readInt32(); |
michael@0 | 5253 | } |
michael@0 | 5254 | |
michael@0 | 5255 | let apps_length = Buf.readInt32(); |
michael@0 | 5256 | if (apps_length > CARD_MAX_APPS) { |
michael@0 | 5257 | apps_length = CARD_MAX_APPS; |
michael@0 | 5258 | } |
michael@0 | 5259 | |
michael@0 | 5260 | iccStatus.apps = []; |
michael@0 | 5261 | for (let i = 0 ; i < apps_length ; i++) { |
michael@0 | 5262 | iccStatus.apps.push({ |
michael@0 | 5263 | app_type: Buf.readInt32(), // CARD_APPTYPE_* |
michael@0 | 5264 | app_state: Buf.readInt32(), // CARD_APPSTATE_* |
michael@0 | 5265 | perso_substate: Buf.readInt32(), // CARD_PERSOSUBSTATE_* |
michael@0 | 5266 | aid: Buf.readString(), |
michael@0 | 5267 | app_label: Buf.readString(), |
michael@0 | 5268 | pin1_replaced: Buf.readInt32(), |
michael@0 | 5269 | pin1: Buf.readInt32(), |
michael@0 | 5270 | pin2: Buf.readInt32() |
michael@0 | 5271 | }); |
michael@0 | 5272 | if (RILQUIRKS_SIM_APP_STATE_EXTRA_FIELDS) { |
michael@0 | 5273 | Buf.readInt32(); |
michael@0 | 5274 | Buf.readInt32(); |
michael@0 | 5275 | Buf.readInt32(); |
michael@0 | 5276 | Buf.readInt32(); |
michael@0 | 5277 | } |
michael@0 | 5278 | } |
michael@0 | 5279 | |
michael@0 | 5280 | if (DEBUG) this.context.debug("iccStatus: " + JSON.stringify(iccStatus)); |
michael@0 | 5281 | this._processICCStatus(iccStatus); |
michael@0 | 5282 | }; |
michael@0 | 5283 | RilObject.prototype[REQUEST_ENTER_SIM_PIN] = function REQUEST_ENTER_SIM_PIN(length, options) { |
michael@0 | 5284 | this._processEnterAndChangeICCResponses(length, options); |
michael@0 | 5285 | }; |
michael@0 | 5286 | RilObject.prototype[REQUEST_ENTER_SIM_PUK] = function REQUEST_ENTER_SIM_PUK(length, options) { |
michael@0 | 5287 | this._processEnterAndChangeICCResponses(length, options); |
michael@0 | 5288 | }; |
michael@0 | 5289 | RilObject.prototype[REQUEST_ENTER_SIM_PIN2] = function REQUEST_ENTER_SIM_PIN2(length, options) { |
michael@0 | 5290 | this._processEnterAndChangeICCResponses(length, options); |
michael@0 | 5291 | }; |
michael@0 | 5292 | RilObject.prototype[REQUEST_ENTER_SIM_PUK2] = function REQUEST_ENTER_SIM_PUK(length, options) { |
michael@0 | 5293 | this._processEnterAndChangeICCResponses(length, options); |
michael@0 | 5294 | }; |
michael@0 | 5295 | RilObject.prototype[REQUEST_CHANGE_SIM_PIN] = function REQUEST_CHANGE_SIM_PIN(length, options) { |
michael@0 | 5296 | this._processEnterAndChangeICCResponses(length, options); |
michael@0 | 5297 | }; |
michael@0 | 5298 | RilObject.prototype[REQUEST_CHANGE_SIM_PIN2] = function REQUEST_CHANGE_SIM_PIN2(length, options) { |
michael@0 | 5299 | this._processEnterAndChangeICCResponses(length, options); |
michael@0 | 5300 | }; |
michael@0 | 5301 | RilObject.prototype[REQUEST_ENTER_NETWORK_DEPERSONALIZATION_CODE] = |
michael@0 | 5302 | function REQUEST_ENTER_NETWORK_DEPERSONALIZATION_CODE(length, options) { |
michael@0 | 5303 | this._processEnterAndChangeICCResponses(length, options); |
michael@0 | 5304 | }; |
michael@0 | 5305 | RilObject.prototype[REQUEST_GET_CURRENT_CALLS] = function REQUEST_GET_CURRENT_CALLS(length, options) { |
michael@0 | 5306 | if (options.rilRequestError) { |
michael@0 | 5307 | return; |
michael@0 | 5308 | } |
michael@0 | 5309 | |
michael@0 | 5310 | let Buf = this.context.Buf; |
michael@0 | 5311 | let calls_length = 0; |
michael@0 | 5312 | // The RIL won't even send us the length integer if there are no active calls. |
michael@0 | 5313 | // So only read this integer if the parcel actually has it. |
michael@0 | 5314 | if (length) { |
michael@0 | 5315 | calls_length = Buf.readInt32(); |
michael@0 | 5316 | } |
michael@0 | 5317 | if (!calls_length) { |
michael@0 | 5318 | this._processCalls(null); |
michael@0 | 5319 | return; |
michael@0 | 5320 | } |
michael@0 | 5321 | |
michael@0 | 5322 | let calls = {}; |
michael@0 | 5323 | for (let i = 0; i < calls_length; i++) { |
michael@0 | 5324 | let call = {}; |
michael@0 | 5325 | |
michael@0 | 5326 | // Extra uint32 field to get correct callIndex and rest of call data for |
michael@0 | 5327 | // call waiting feature. |
michael@0 | 5328 | if (RILQUIRKS_EXTRA_UINT32_2ND_CALL && i > 0) { |
michael@0 | 5329 | Buf.readInt32(); |
michael@0 | 5330 | } |
michael@0 | 5331 | |
michael@0 | 5332 | call.state = Buf.readInt32(); // CALL_STATE_* |
michael@0 | 5333 | call.callIndex = Buf.readInt32(); // GSM index (1-based) |
michael@0 | 5334 | call.toa = Buf.readInt32(); |
michael@0 | 5335 | call.isMpty = Boolean(Buf.readInt32()); |
michael@0 | 5336 | call.isMT = Boolean(Buf.readInt32()); |
michael@0 | 5337 | call.als = Buf.readInt32(); |
michael@0 | 5338 | call.isVoice = Boolean(Buf.readInt32()); |
michael@0 | 5339 | call.isVoicePrivacy = Boolean(Buf.readInt32()); |
michael@0 | 5340 | if (RILQUIRKS_CALLSTATE_EXTRA_UINT32) { |
michael@0 | 5341 | Buf.readInt32(); |
michael@0 | 5342 | } |
michael@0 | 5343 | call.number = Buf.readString(); //TODO munge with TOA |
michael@0 | 5344 | call.numberPresentation = Buf.readInt32(); // CALL_PRESENTATION_* |
michael@0 | 5345 | call.name = Buf.readString(); |
michael@0 | 5346 | call.namePresentation = Buf.readInt32(); |
michael@0 | 5347 | |
michael@0 | 5348 | call.uusInfo = null; |
michael@0 | 5349 | let uusInfoPresent = Buf.readInt32(); |
michael@0 | 5350 | if (uusInfoPresent == 1) { |
michael@0 | 5351 | call.uusInfo = { |
michael@0 | 5352 | type: Buf.readInt32(), |
michael@0 | 5353 | dcs: Buf.readInt32(), |
michael@0 | 5354 | userData: null //XXX TODO byte array?!? |
michael@0 | 5355 | }; |
michael@0 | 5356 | } |
michael@0 | 5357 | |
michael@0 | 5358 | calls[call.callIndex] = call; |
michael@0 | 5359 | } |
michael@0 | 5360 | this._processCalls(calls); |
michael@0 | 5361 | }; |
michael@0 | 5362 | RilObject.prototype[REQUEST_DIAL] = function REQUEST_DIAL(length, options) { |
michael@0 | 5363 | // We already return a successful response before. Don't respond it again! |
michael@0 | 5364 | if (options.rilRequestError) { |
michael@0 | 5365 | this.getFailCauseCode((function(failCause) { |
michael@0 | 5366 | this._removePendingOutgoingCall(failCause); |
michael@0 | 5367 | }).bind(this)); |
michael@0 | 5368 | } |
michael@0 | 5369 | }; |
michael@0 | 5370 | RilObject.prototype[REQUEST_DIAL_EMERGENCY_CALL] = RilObject.prototype[REQUEST_DIAL]; |
michael@0 | 5371 | RilObject.prototype[REQUEST_GET_IMSI] = function REQUEST_GET_IMSI(length, options) { |
michael@0 | 5372 | if (options.rilRequestError) { |
michael@0 | 5373 | return; |
michael@0 | 5374 | } |
michael@0 | 5375 | |
michael@0 | 5376 | this.iccInfoPrivate.imsi = this.context.Buf.readString(); |
michael@0 | 5377 | if (DEBUG) { |
michael@0 | 5378 | this.context.debug("IMSI: " + this.iccInfoPrivate.imsi); |
michael@0 | 5379 | } |
michael@0 | 5380 | |
michael@0 | 5381 | options.rilMessageType = "iccimsi"; |
michael@0 | 5382 | options.imsi = this.iccInfoPrivate.imsi; |
michael@0 | 5383 | this.sendChromeMessage(options); |
michael@0 | 5384 | }; |
michael@0 | 5385 | RilObject.prototype[REQUEST_HANGUP] = function REQUEST_HANGUP(length, options) { |
michael@0 | 5386 | if (options.rilRequestError) { |
michael@0 | 5387 | return; |
michael@0 | 5388 | } |
michael@0 | 5389 | |
michael@0 | 5390 | this.getCurrentCalls(); |
michael@0 | 5391 | }; |
michael@0 | 5392 | RilObject.prototype[REQUEST_HANGUP_WAITING_OR_BACKGROUND] = function REQUEST_HANGUP_WAITING_OR_BACKGROUND(length, options) { |
michael@0 | 5393 | if (options.rilRequestError) { |
michael@0 | 5394 | return; |
michael@0 | 5395 | } |
michael@0 | 5396 | |
michael@0 | 5397 | this.getCurrentCalls(); |
michael@0 | 5398 | }; |
michael@0 | 5399 | RilObject.prototype[REQUEST_HANGUP_FOREGROUND_RESUME_BACKGROUND] = function REQUEST_HANGUP_FOREGROUND_RESUME_BACKGROUND(length, options) { |
michael@0 | 5400 | if (options.rilRequestError) { |
michael@0 | 5401 | return; |
michael@0 | 5402 | } |
michael@0 | 5403 | |
michael@0 | 5404 | this.getCurrentCalls(); |
michael@0 | 5405 | }; |
michael@0 | 5406 | RilObject.prototype[REQUEST_SWITCH_WAITING_OR_HOLDING_AND_ACTIVE] = function REQUEST_SWITCH_WAITING_OR_HOLDING_AND_ACTIVE(length, options) { |
michael@0 | 5407 | options.success = (options.rilRequestError === 0); |
michael@0 | 5408 | if (!options.success) { |
michael@0 | 5409 | options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError]; |
michael@0 | 5410 | this.sendChromeMessage(options); |
michael@0 | 5411 | return; |
michael@0 | 5412 | } |
michael@0 | 5413 | |
michael@0 | 5414 | this.sendChromeMessage(options); |
michael@0 | 5415 | this.getCurrentCalls(); |
michael@0 | 5416 | }; |
michael@0 | 5417 | RilObject.prototype[REQUEST_CONFERENCE] = function REQUEST_CONFERENCE(length, options) { |
michael@0 | 5418 | options.success = (options.rilRequestError === 0); |
michael@0 | 5419 | if (!options.success) { |
michael@0 | 5420 | this._hasConferenceRequest = false; |
michael@0 | 5421 | options.errorName = "addError"; |
michael@0 | 5422 | options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError]; |
michael@0 | 5423 | this.sendChromeMessage(options); |
michael@0 | 5424 | return; |
michael@0 | 5425 | } |
michael@0 | 5426 | |
michael@0 | 5427 | this.sendChromeMessage(options); |
michael@0 | 5428 | }; |
michael@0 | 5429 | RilObject.prototype[REQUEST_UDUB] = null; |
michael@0 | 5430 | RilObject.prototype[REQUEST_LAST_CALL_FAIL_CAUSE] = function REQUEST_LAST_CALL_FAIL_CAUSE(length, options) { |
michael@0 | 5431 | let Buf = this.context.Buf; |
michael@0 | 5432 | let num = length ? Buf.readInt32() : 0; |
michael@0 | 5433 | let failCause = num ? RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[Buf.readInt32()] : null; |
michael@0 | 5434 | if (options.callback) { |
michael@0 | 5435 | options.callback(failCause); |
michael@0 | 5436 | } |
michael@0 | 5437 | }; |
michael@0 | 5438 | RilObject.prototype[REQUEST_SIGNAL_STRENGTH] = function REQUEST_SIGNAL_STRENGTH(length, options) { |
michael@0 | 5439 | this._receivedNetworkInfo(NETWORK_INFO_SIGNAL); |
michael@0 | 5440 | |
michael@0 | 5441 | if (options.rilRequestError) { |
michael@0 | 5442 | return; |
michael@0 | 5443 | } |
michael@0 | 5444 | |
michael@0 | 5445 | let Buf = this.context.Buf; |
michael@0 | 5446 | let signal = { |
michael@0 | 5447 | gsmSignalStrength: Buf.readInt32(), |
michael@0 | 5448 | gsmBitErrorRate: Buf.readInt32(), |
michael@0 | 5449 | cdmaDBM: Buf.readInt32(), |
michael@0 | 5450 | cdmaECIO: Buf.readInt32(), |
michael@0 | 5451 | evdoDBM: Buf.readInt32(), |
michael@0 | 5452 | evdoECIO: Buf.readInt32(), |
michael@0 | 5453 | evdoSNR: Buf.readInt32() |
michael@0 | 5454 | }; |
michael@0 | 5455 | |
michael@0 | 5456 | if (!this.v5Legacy) { |
michael@0 | 5457 | signal.lteSignalStrength = Buf.readInt32(); |
michael@0 | 5458 | signal.lteRSRP = Buf.readInt32(); |
michael@0 | 5459 | signal.lteRSRQ = Buf.readInt32(); |
michael@0 | 5460 | signal.lteRSSNR = Buf.readInt32(); |
michael@0 | 5461 | signal.lteCQI = Buf.readInt32(); |
michael@0 | 5462 | } |
michael@0 | 5463 | |
michael@0 | 5464 | if (DEBUG) this.context.debug("signal strength: " + JSON.stringify(signal)); |
michael@0 | 5465 | |
michael@0 | 5466 | this._processSignalStrength(signal); |
michael@0 | 5467 | }; |
michael@0 | 5468 | RilObject.prototype[REQUEST_VOICE_REGISTRATION_STATE] = function REQUEST_VOICE_REGISTRATION_STATE(length, options) { |
michael@0 | 5469 | this._receivedNetworkInfo(NETWORK_INFO_VOICE_REGISTRATION_STATE); |
michael@0 | 5470 | |
michael@0 | 5471 | if (options.rilRequestError) { |
michael@0 | 5472 | return; |
michael@0 | 5473 | } |
michael@0 | 5474 | |
michael@0 | 5475 | let state = this.context.Buf.readStringList(); |
michael@0 | 5476 | if (DEBUG) this.context.debug("voice registration state: " + state); |
michael@0 | 5477 | |
michael@0 | 5478 | this._processVoiceRegistrationState(state); |
michael@0 | 5479 | |
michael@0 | 5480 | if (this.cachedDialRequest && |
michael@0 | 5481 | (this.voiceRegistrationState.emergencyCallsOnly || |
michael@0 | 5482 | this.voiceRegistrationState.connected) && |
michael@0 | 5483 | this.voiceRegistrationState.radioTech != NETWORK_CREG_TECH_UNKNOWN) { |
michael@0 | 5484 | // Radio is ready for making the cached emergency call. |
michael@0 | 5485 | this.cachedDialRequest.callback(); |
michael@0 | 5486 | this.cachedDialRequest = null; |
michael@0 | 5487 | } |
michael@0 | 5488 | }; |
michael@0 | 5489 | RilObject.prototype[REQUEST_DATA_REGISTRATION_STATE] = function REQUEST_DATA_REGISTRATION_STATE(length, options) { |
michael@0 | 5490 | this._receivedNetworkInfo(NETWORK_INFO_DATA_REGISTRATION_STATE); |
michael@0 | 5491 | |
michael@0 | 5492 | if (options.rilRequestError) { |
michael@0 | 5493 | return; |
michael@0 | 5494 | } |
michael@0 | 5495 | |
michael@0 | 5496 | let state = this.context.Buf.readStringList(); |
michael@0 | 5497 | this._processDataRegistrationState(state); |
michael@0 | 5498 | }; |
michael@0 | 5499 | RilObject.prototype[REQUEST_OPERATOR] = function REQUEST_OPERATOR(length, options) { |
michael@0 | 5500 | this._receivedNetworkInfo(NETWORK_INFO_OPERATOR); |
michael@0 | 5501 | |
michael@0 | 5502 | if (options.rilRequestError) { |
michael@0 | 5503 | return; |
michael@0 | 5504 | } |
michael@0 | 5505 | |
michael@0 | 5506 | let operatorData = this.context.Buf.readStringList(); |
michael@0 | 5507 | if (DEBUG) this.context.debug("Operator: " + operatorData); |
michael@0 | 5508 | this._processOperator(operatorData); |
michael@0 | 5509 | }; |
michael@0 | 5510 | RilObject.prototype[REQUEST_RADIO_POWER] = function REQUEST_RADIO_POWER(length, options) { |
michael@0 | 5511 | if (options.rilMessageType == null) { |
michael@0 | 5512 | // The request was made by ril_worker itself. |
michael@0 | 5513 | if (options.rilRequestError) { |
michael@0 | 5514 | if (this.cachedDialRequest && options.enabled) { |
michael@0 | 5515 | // Turning on radio fails. Notify the error of making an emergency call. |
michael@0 | 5516 | this.cachedDialRequest.onerror(GECKO_ERROR_RADIO_NOT_AVAILABLE); |
michael@0 | 5517 | this.cachedDialRequest = null; |
michael@0 | 5518 | } |
michael@0 | 5519 | } |
michael@0 | 5520 | return; |
michael@0 | 5521 | } |
michael@0 | 5522 | |
michael@0 | 5523 | options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError]; |
michael@0 | 5524 | this.sendChromeMessage(options); |
michael@0 | 5525 | }; |
michael@0 | 5526 | RilObject.prototype[REQUEST_DTMF] = null; |
michael@0 | 5527 | RilObject.prototype[REQUEST_SEND_SMS] = function REQUEST_SEND_SMS(length, options) { |
michael@0 | 5528 | this._processSmsSendResult(length, options); |
michael@0 | 5529 | }; |
michael@0 | 5530 | RilObject.prototype[REQUEST_SEND_SMS_EXPECT_MORE] = null; |
michael@0 | 5531 | |
michael@0 | 5532 | RilObject.prototype.readSetupDataCall_v5 = function readSetupDataCall_v5(options) { |
michael@0 | 5533 | if (!options) { |
michael@0 | 5534 | options = {}; |
michael@0 | 5535 | } |
michael@0 | 5536 | let [cid, ifname, addresses, dnses, gateways] = this.context.Buf.readStringList(); |
michael@0 | 5537 | options.cid = cid; |
michael@0 | 5538 | options.ifname = ifname; |
michael@0 | 5539 | options.addresses = addresses ? [addresses] : []; |
michael@0 | 5540 | options.dnses = dnses ? [dnses] : []; |
michael@0 | 5541 | options.gateways = gateways ? [gateways] : []; |
michael@0 | 5542 | options.active = DATACALL_ACTIVE_UNKNOWN; |
michael@0 | 5543 | options.state = GECKO_NETWORK_STATE_CONNECTING; |
michael@0 | 5544 | return options; |
michael@0 | 5545 | }; |
michael@0 | 5546 | |
michael@0 | 5547 | RilObject.prototype[REQUEST_SETUP_DATA_CALL] = function REQUEST_SETUP_DATA_CALL(length, options) { |
michael@0 | 5548 | if (options.rilRequestError) { |
michael@0 | 5549 | // On Data Call generic errors, we shall notify caller |
michael@0 | 5550 | this._sendDataCallError(options, options.rilRequestError); |
michael@0 | 5551 | return; |
michael@0 | 5552 | } |
michael@0 | 5553 | |
michael@0 | 5554 | if (this.v5Legacy) { |
michael@0 | 5555 | // Populate the `options` object with the data call information. That way |
michael@0 | 5556 | // we retain the APN and other info about how the data call was set up. |
michael@0 | 5557 | this.readSetupDataCall_v5(options); |
michael@0 | 5558 | this.currentDataCalls[options.cid] = options; |
michael@0 | 5559 | options.rilMessageType = "datacallstatechange"; |
michael@0 | 5560 | this.sendChromeMessage(options); |
michael@0 | 5561 | // Let's get the list of data calls to ensure we know whether it's active |
michael@0 | 5562 | // or not. |
michael@0 | 5563 | this.getDataCallList(); |
michael@0 | 5564 | return; |
michael@0 | 5565 | } |
michael@0 | 5566 | // Pass `options` along. That way we retain the APN and other info about |
michael@0 | 5567 | // how the data call was set up. |
michael@0 | 5568 | this[REQUEST_DATA_CALL_LIST](length, options); |
michael@0 | 5569 | }; |
michael@0 | 5570 | RilObject.prototype[REQUEST_SIM_IO] = function REQUEST_SIM_IO(length, options) { |
michael@0 | 5571 | let ICCIOHelper = this.context.ICCIOHelper; |
michael@0 | 5572 | if (!length) { |
michael@0 | 5573 | ICCIOHelper.processICCIOError(options); |
michael@0 | 5574 | return; |
michael@0 | 5575 | } |
michael@0 | 5576 | |
michael@0 | 5577 | // Don't need to read rilRequestError since we can know error status from |
michael@0 | 5578 | // sw1 and sw2. |
michael@0 | 5579 | let Buf = this.context.Buf; |
michael@0 | 5580 | options.sw1 = Buf.readInt32(); |
michael@0 | 5581 | options.sw2 = Buf.readInt32(); |
michael@0 | 5582 | if (options.sw1 != ICC_STATUS_NORMAL_ENDING) { |
michael@0 | 5583 | ICCIOHelper.processICCIOError(options); |
michael@0 | 5584 | return; |
michael@0 | 5585 | } |
michael@0 | 5586 | ICCIOHelper.processICCIO(options); |
michael@0 | 5587 | }; |
michael@0 | 5588 | RilObject.prototype[REQUEST_SEND_USSD] = function REQUEST_SEND_USSD(length, options) { |
michael@0 | 5589 | if (DEBUG) { |
michael@0 | 5590 | this.context.debug("REQUEST_SEND_USSD " + JSON.stringify(options)); |
michael@0 | 5591 | } |
michael@0 | 5592 | options.success = (this._ussdSession = options.rilRequestError === 0); |
michael@0 | 5593 | options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError]; |
michael@0 | 5594 | this.sendChromeMessage(options); |
michael@0 | 5595 | }; |
michael@0 | 5596 | RilObject.prototype[REQUEST_CANCEL_USSD] = function REQUEST_CANCEL_USSD(length, options) { |
michael@0 | 5597 | if (DEBUG) { |
michael@0 | 5598 | this.context.debug("REQUEST_CANCEL_USSD" + JSON.stringify(options)); |
michael@0 | 5599 | } |
michael@0 | 5600 | options.success = (options.rilRequestError === 0); |
michael@0 | 5601 | this._ussdSession = !options.success; |
michael@0 | 5602 | options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError]; |
michael@0 | 5603 | this.sendChromeMessage(options); |
michael@0 | 5604 | }; |
michael@0 | 5605 | RilObject.prototype[REQUEST_GET_CLIR] = function REQUEST_GET_CLIR(length, options) { |
michael@0 | 5606 | options.success = (options.rilRequestError === 0); |
michael@0 | 5607 | if (!options.success) { |
michael@0 | 5608 | options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError]; |
michael@0 | 5609 | this.sendChromeMessage(options); |
michael@0 | 5610 | return; |
michael@0 | 5611 | } |
michael@0 | 5612 | |
michael@0 | 5613 | let Buf = this.context.Buf; |
michael@0 | 5614 | let bufLength = Buf.readInt32(); |
michael@0 | 5615 | if (!bufLength || bufLength < 2) { |
michael@0 | 5616 | options.success = false; |
michael@0 | 5617 | options.errorMsg = GECKO_ERROR_GENERIC_FAILURE; |
michael@0 | 5618 | this.sendChromeMessage(options); |
michael@0 | 5619 | return; |
michael@0 | 5620 | } |
michael@0 | 5621 | |
michael@0 | 5622 | options.n = Buf.readInt32(); // Will be TS 27.007 +CLIR parameter 'n'. |
michael@0 | 5623 | options.m = Buf.readInt32(); // Will be TS 27.007 +CLIR parameter 'm'. |
michael@0 | 5624 | |
michael@0 | 5625 | if (options.rilMessageType === "sendMMI") { |
michael@0 | 5626 | // TS 27.007 +CLIR parameter 'm'. |
michael@0 | 5627 | switch (options.m) { |
michael@0 | 5628 | // CLIR not provisioned. |
michael@0 | 5629 | case 0: |
michael@0 | 5630 | options.statusMessage = MMI_SM_KS_SERVICE_NOT_PROVISIONED; |
michael@0 | 5631 | break; |
michael@0 | 5632 | // CLIR provisioned in permanent mode. |
michael@0 | 5633 | case 1: |
michael@0 | 5634 | options.statusMessage = MMI_SM_KS_CLIR_PERMANENT; |
michael@0 | 5635 | break; |
michael@0 | 5636 | // Unknown (e.g. no network, etc.). |
michael@0 | 5637 | case 2: |
michael@0 | 5638 | options.success = false; |
michael@0 | 5639 | options.errorMsg = MMI_ERROR_KS_ERROR; |
michael@0 | 5640 | break; |
michael@0 | 5641 | // CLIR temporary mode presentation restricted. |
michael@0 | 5642 | case 3: |
michael@0 | 5643 | // TS 27.007 +CLIR parameter 'n'. |
michael@0 | 5644 | switch (options.n) { |
michael@0 | 5645 | // Default. |
michael@0 | 5646 | case 0: |
michael@0 | 5647 | // CLIR invocation. |
michael@0 | 5648 | case 1: |
michael@0 | 5649 | options.statusMessage = MMI_SM_KS_CLIR_DEFAULT_ON_NEXT_CALL_ON; |
michael@0 | 5650 | break; |
michael@0 | 5651 | // CLIR suppression. |
michael@0 | 5652 | case 2: |
michael@0 | 5653 | options.statusMessage = MMI_SM_KS_CLIR_DEFAULT_ON_NEXT_CALL_OFF; |
michael@0 | 5654 | break; |
michael@0 | 5655 | default: |
michael@0 | 5656 | options.success = false; |
michael@0 | 5657 | options.errorMsg = GECKO_ERROR_GENERIC_FAILURE; |
michael@0 | 5658 | break; |
michael@0 | 5659 | } |
michael@0 | 5660 | break; |
michael@0 | 5661 | // CLIR temporary mode presentation allowed. |
michael@0 | 5662 | case 4: |
michael@0 | 5663 | // TS 27.007 +CLIR parameter 'n'. |
michael@0 | 5664 | switch (options.n) { |
michael@0 | 5665 | // Default. |
michael@0 | 5666 | case 0: |
michael@0 | 5667 | // CLIR suppression. |
michael@0 | 5668 | case 2: |
michael@0 | 5669 | options.statusMessage = MMI_SM_KS_CLIR_DEFAULT_OFF_NEXT_CALL_OFF; |
michael@0 | 5670 | break; |
michael@0 | 5671 | // CLIR invocation. |
michael@0 | 5672 | case 1: |
michael@0 | 5673 | options.statusMessage = MMI_SM_KS_CLIR_DEFAULT_OFF_NEXT_CALL_ON; |
michael@0 | 5674 | break; |
michael@0 | 5675 | default: |
michael@0 | 5676 | options.success = false; |
michael@0 | 5677 | options.errorMsg = GECKO_ERROR_GENERIC_FAILURE; |
michael@0 | 5678 | break; |
michael@0 | 5679 | } |
michael@0 | 5680 | break; |
michael@0 | 5681 | default: |
michael@0 | 5682 | options.success = false; |
michael@0 | 5683 | options.errorMsg = GECKO_ERROR_GENERIC_FAILURE; |
michael@0 | 5684 | break; |
michael@0 | 5685 | } |
michael@0 | 5686 | } |
michael@0 | 5687 | |
michael@0 | 5688 | this.sendChromeMessage(options); |
michael@0 | 5689 | }; |
michael@0 | 5690 | RilObject.prototype[REQUEST_SET_CLIR] = function REQUEST_SET_CLIR(length, options) { |
michael@0 | 5691 | if (options.rilMessageType == null) { |
michael@0 | 5692 | // The request was made by ril_worker itself automatically. Don't report. |
michael@0 | 5693 | return; |
michael@0 | 5694 | } |
michael@0 | 5695 | options.success = (options.rilRequestError === 0); |
michael@0 | 5696 | if (!options.success) { |
michael@0 | 5697 | options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError]; |
michael@0 | 5698 | } else if (options.rilMessageType === "sendMMI") { |
michael@0 | 5699 | switch (options.procedure) { |
michael@0 | 5700 | case MMI_PROCEDURE_ACTIVATION: |
michael@0 | 5701 | options.statusMessage = MMI_SM_KS_SERVICE_ENABLED; |
michael@0 | 5702 | break; |
michael@0 | 5703 | case MMI_PROCEDURE_DEACTIVATION: |
michael@0 | 5704 | options.statusMessage = MMI_SM_KS_SERVICE_DISABLED; |
michael@0 | 5705 | break; |
michael@0 | 5706 | } |
michael@0 | 5707 | } |
michael@0 | 5708 | this.sendChromeMessage(options); |
michael@0 | 5709 | }; |
michael@0 | 5710 | |
michael@0 | 5711 | RilObject.prototype[REQUEST_QUERY_CALL_FORWARD_STATUS] = |
michael@0 | 5712 | function REQUEST_QUERY_CALL_FORWARD_STATUS(length, options) { |
michael@0 | 5713 | options.success = (options.rilRequestError === 0); |
michael@0 | 5714 | if (!options.success) { |
michael@0 | 5715 | options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError]; |
michael@0 | 5716 | this.sendChromeMessage(options); |
michael@0 | 5717 | return; |
michael@0 | 5718 | } |
michael@0 | 5719 | |
michael@0 | 5720 | let Buf = this.context.Buf; |
michael@0 | 5721 | let rulesLength = 0; |
michael@0 | 5722 | if (length) { |
michael@0 | 5723 | rulesLength = Buf.readInt32(); |
michael@0 | 5724 | } |
michael@0 | 5725 | if (!rulesLength) { |
michael@0 | 5726 | options.success = false; |
michael@0 | 5727 | options.errorMsg = GECKO_ERROR_GENERIC_FAILURE; |
michael@0 | 5728 | this.sendChromeMessage(options); |
michael@0 | 5729 | return; |
michael@0 | 5730 | } |
michael@0 | 5731 | let rules = new Array(rulesLength); |
michael@0 | 5732 | for (let i = 0; i < rulesLength; i++) { |
michael@0 | 5733 | let rule = {}; |
michael@0 | 5734 | rule.active = Buf.readInt32() == 1; // CALL_FORWARD_STATUS_* |
michael@0 | 5735 | rule.reason = Buf.readInt32(); // CALL_FORWARD_REASON_* |
michael@0 | 5736 | rule.serviceClass = Buf.readInt32(); |
michael@0 | 5737 | rule.toa = Buf.readInt32(); |
michael@0 | 5738 | rule.number = Buf.readString(); |
michael@0 | 5739 | rule.timeSeconds = Buf.readInt32(); |
michael@0 | 5740 | rules[i] = rule; |
michael@0 | 5741 | } |
michael@0 | 5742 | options.rules = rules; |
michael@0 | 5743 | if (options.rilMessageType === "sendMMI") { |
michael@0 | 5744 | options.statusMessage = MMI_SM_KS_SERVICE_INTERROGATED; |
michael@0 | 5745 | // MMI query call forwarding options request returns a set of rules that |
michael@0 | 5746 | // will be exposed in the form of an array of nsIDOMMozMobileCFInfo |
michael@0 | 5747 | // instances. |
michael@0 | 5748 | options.additionalInformation = rules; |
michael@0 | 5749 | } |
michael@0 | 5750 | this.sendChromeMessage(options); |
michael@0 | 5751 | }; |
michael@0 | 5752 | RilObject.prototype[REQUEST_SET_CALL_FORWARD] = |
michael@0 | 5753 | function REQUEST_SET_CALL_FORWARD(length, options) { |
michael@0 | 5754 | options.success = (options.rilRequestError === 0); |
michael@0 | 5755 | if (!options.success) { |
michael@0 | 5756 | options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError]; |
michael@0 | 5757 | } else if (options.rilMessageType === "sendMMI") { |
michael@0 | 5758 | switch (options.action) { |
michael@0 | 5759 | case CALL_FORWARD_ACTION_ENABLE: |
michael@0 | 5760 | options.statusMessage = MMI_SM_KS_SERVICE_ENABLED; |
michael@0 | 5761 | break; |
michael@0 | 5762 | case CALL_FORWARD_ACTION_DISABLE: |
michael@0 | 5763 | options.statusMessage = MMI_SM_KS_SERVICE_DISABLED; |
michael@0 | 5764 | break; |
michael@0 | 5765 | case CALL_FORWARD_ACTION_REGISTRATION: |
michael@0 | 5766 | options.statusMessage = MMI_SM_KS_SERVICE_REGISTERED; |
michael@0 | 5767 | break; |
michael@0 | 5768 | case CALL_FORWARD_ACTION_ERASURE: |
michael@0 | 5769 | options.statusMessage = MMI_SM_KS_SERVICE_ERASED; |
michael@0 | 5770 | break; |
michael@0 | 5771 | } |
michael@0 | 5772 | } |
michael@0 | 5773 | this.sendChromeMessage(options); |
michael@0 | 5774 | }; |
michael@0 | 5775 | RilObject.prototype[REQUEST_QUERY_CALL_WAITING] = |
michael@0 | 5776 | function REQUEST_QUERY_CALL_WAITING(length, options) { |
michael@0 | 5777 | options.success = (options.rilRequestError === 0); |
michael@0 | 5778 | if (!options.success) { |
michael@0 | 5779 | options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError]; |
michael@0 | 5780 | this.sendChromeMessage(options); |
michael@0 | 5781 | return; |
michael@0 | 5782 | } |
michael@0 | 5783 | |
michael@0 | 5784 | if (options.callback) { |
michael@0 | 5785 | options.callback.call(this, options); |
michael@0 | 5786 | return; |
michael@0 | 5787 | } |
michael@0 | 5788 | |
michael@0 | 5789 | let Buf = this.context.Buf; |
michael@0 | 5790 | options.length = Buf.readInt32(); |
michael@0 | 5791 | options.enabled = ((Buf.readInt32() == 1) && |
michael@0 | 5792 | ((Buf.readInt32() & ICC_SERVICE_CLASS_VOICE) == 0x01)); |
michael@0 | 5793 | this.sendChromeMessage(options); |
michael@0 | 5794 | }; |
michael@0 | 5795 | |
michael@0 | 5796 | RilObject.prototype[REQUEST_SET_CALL_WAITING] = function REQUEST_SET_CALL_WAITING(length, options) { |
michael@0 | 5797 | options.success = (options.rilRequestError === 0); |
michael@0 | 5798 | if (!options.success) { |
michael@0 | 5799 | options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError]; |
michael@0 | 5800 | this.sendChromeMessage(options); |
michael@0 | 5801 | return; |
michael@0 | 5802 | } |
michael@0 | 5803 | |
michael@0 | 5804 | if (options.callback) { |
michael@0 | 5805 | options.callback.call(this, options); |
michael@0 | 5806 | return; |
michael@0 | 5807 | } |
michael@0 | 5808 | |
michael@0 | 5809 | this.sendChromeMessage(options); |
michael@0 | 5810 | }; |
michael@0 | 5811 | RilObject.prototype[REQUEST_SMS_ACKNOWLEDGE] = null; |
michael@0 | 5812 | RilObject.prototype[REQUEST_GET_IMEI] = function REQUEST_GET_IMEI(length, options) { |
michael@0 | 5813 | this.IMEI = this.context.Buf.readString(); |
michael@0 | 5814 | let rilMessageType = options.rilMessageType; |
michael@0 | 5815 | // So far we only send the IMEI back to chrome if it was requested via MMI. |
michael@0 | 5816 | if (rilMessageType !== "sendMMI") { |
michael@0 | 5817 | return; |
michael@0 | 5818 | } |
michael@0 | 5819 | |
michael@0 | 5820 | options.success = (options.rilRequestError === 0); |
michael@0 | 5821 | options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError]; |
michael@0 | 5822 | if ((!options.success || this.IMEI == null) && !options.errorMsg) { |
michael@0 | 5823 | options.errorMsg = GECKO_ERROR_GENERIC_FAILURE; |
michael@0 | 5824 | } |
michael@0 | 5825 | options.statusMessage = this.IMEI; |
michael@0 | 5826 | this.sendChromeMessage(options); |
michael@0 | 5827 | }; |
michael@0 | 5828 | RilObject.prototype[REQUEST_GET_IMEISV] = function REQUEST_GET_IMEISV(length, options) { |
michael@0 | 5829 | if (options.rilRequestError) { |
michael@0 | 5830 | return; |
michael@0 | 5831 | } |
michael@0 | 5832 | |
michael@0 | 5833 | this.IMEISV = this.context.Buf.readString(); |
michael@0 | 5834 | }; |
michael@0 | 5835 | RilObject.prototype[REQUEST_ANSWER] = null; |
michael@0 | 5836 | RilObject.prototype[REQUEST_DEACTIVATE_DATA_CALL] = function REQUEST_DEACTIVATE_DATA_CALL(length, options) { |
michael@0 | 5837 | if (options.rilRequestError) { |
michael@0 | 5838 | return; |
michael@0 | 5839 | } |
michael@0 | 5840 | |
michael@0 | 5841 | let datacall = this.currentDataCalls[options.cid]; |
michael@0 | 5842 | delete this.currentDataCalls[options.cid]; |
michael@0 | 5843 | datacall.state = GECKO_NETWORK_STATE_UNKNOWN; |
michael@0 | 5844 | datacall.rilMessageType = "datacallstatechange"; |
michael@0 | 5845 | this.sendChromeMessage(datacall); |
michael@0 | 5846 | }; |
michael@0 | 5847 | RilObject.prototype[REQUEST_QUERY_FACILITY_LOCK] = function REQUEST_QUERY_FACILITY_LOCK(length, options) { |
michael@0 | 5848 | options.success = (options.rilRequestError === 0); |
michael@0 | 5849 | if (!options.success) { |
michael@0 | 5850 | options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError]; |
michael@0 | 5851 | } |
michael@0 | 5852 | |
michael@0 | 5853 | let services; |
michael@0 | 5854 | if (length) { |
michael@0 | 5855 | // Buf.readInt32List()[0] for Call Barring is a bit vector of services. |
michael@0 | 5856 | services = this.context.Buf.readInt32List()[0]; |
michael@0 | 5857 | } else { |
michael@0 | 5858 | options.success = false; |
michael@0 | 5859 | options.errorMsg = GECKO_ERROR_GENERIC_FAILURE; |
michael@0 | 5860 | this.sendChromeMessage(options); |
michael@0 | 5861 | return; |
michael@0 | 5862 | } |
michael@0 | 5863 | |
michael@0 | 5864 | options.enabled = services === 0 ? false : true; |
michael@0 | 5865 | |
michael@0 | 5866 | if (options.success && (options.rilMessageType === "sendMMI")) { |
michael@0 | 5867 | if (!options.enabled) { |
michael@0 | 5868 | options.statusMessage = MMI_SM_KS_SERVICE_DISABLED; |
michael@0 | 5869 | } else { |
michael@0 | 5870 | options.statusMessage = MMI_SM_KS_SERVICE_ENABLED_FOR; |
michael@0 | 5871 | let serviceClass = []; |
michael@0 | 5872 | for (let serviceClassMask = 1; |
michael@0 | 5873 | serviceClassMask <= ICC_SERVICE_CLASS_MAX; |
michael@0 | 5874 | serviceClassMask <<= 1) { |
michael@0 | 5875 | if ((serviceClassMask & services) !== 0) { |
michael@0 | 5876 | serviceClass.push(MMI_KS_SERVICE_CLASS_MAPPING[serviceClassMask]); |
michael@0 | 5877 | } |
michael@0 | 5878 | } |
michael@0 | 5879 | |
michael@0 | 5880 | options.additionalInformation = serviceClass; |
michael@0 | 5881 | } |
michael@0 | 5882 | } |
michael@0 | 5883 | this.sendChromeMessage(options); |
michael@0 | 5884 | }; |
michael@0 | 5885 | RilObject.prototype[REQUEST_SET_FACILITY_LOCK] = function REQUEST_SET_FACILITY_LOCK(length, options) { |
michael@0 | 5886 | options.success = (options.rilRequestError === 0); |
michael@0 | 5887 | if (!options.success) { |
michael@0 | 5888 | options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError]; |
michael@0 | 5889 | } |
michael@0 | 5890 | |
michael@0 | 5891 | options.retryCount = length ? this.context.Buf.readInt32List()[0] : -1; |
michael@0 | 5892 | |
michael@0 | 5893 | if (options.success && (options.rilMessageType === "sendMMI")) { |
michael@0 | 5894 | switch (options.procedure) { |
michael@0 | 5895 | case MMI_PROCEDURE_ACTIVATION: |
michael@0 | 5896 | options.statusMessage = MMI_SM_KS_SERVICE_ENABLED; |
michael@0 | 5897 | break; |
michael@0 | 5898 | case MMI_PROCEDURE_DEACTIVATION: |
michael@0 | 5899 | options.statusMessage = MMI_SM_KS_SERVICE_DISABLED; |
michael@0 | 5900 | break; |
michael@0 | 5901 | } |
michael@0 | 5902 | } |
michael@0 | 5903 | this.sendChromeMessage(options); |
michael@0 | 5904 | }; |
michael@0 | 5905 | RilObject.prototype[REQUEST_CHANGE_BARRING_PASSWORD] = |
michael@0 | 5906 | function REQUEST_CHANGE_BARRING_PASSWORD(length, options) { |
michael@0 | 5907 | if (options.rilRequestError) { |
michael@0 | 5908 | options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError]; |
michael@0 | 5909 | } |
michael@0 | 5910 | this.sendChromeMessage(options); |
michael@0 | 5911 | }; |
michael@0 | 5912 | RilObject.prototype[REQUEST_SIM_OPEN_CHANNEL] = function REQUEST_SIM_OPEN_CHANNEL(length, options) { |
michael@0 | 5913 | if (options.rilRequestError) { |
michael@0 | 5914 | options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError]; |
michael@0 | 5915 | this.sendChromeMessage(options); |
michael@0 | 5916 | return; |
michael@0 | 5917 | } |
michael@0 | 5918 | |
michael@0 | 5919 | options.channel = this.context.Buf.readInt32(); |
michael@0 | 5920 | if (DEBUG) { |
michael@0 | 5921 | this.context.debug("Setting channel number in options: " + options.channel); |
michael@0 | 5922 | } |
michael@0 | 5923 | this.sendChromeMessage(options); |
michael@0 | 5924 | }; |
michael@0 | 5925 | RilObject.prototype[REQUEST_SIM_CLOSE_CHANNEL] = function REQUEST_SIM_CLOSE_CHANNEL(length, options) { |
michael@0 | 5926 | if (options.rilRequestError) { |
michael@0 | 5927 | options.error = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError]; |
michael@0 | 5928 | this.sendChromeMessage(options); |
michael@0 | 5929 | return; |
michael@0 | 5930 | } |
michael@0 | 5931 | |
michael@0 | 5932 | // No return value |
michael@0 | 5933 | this.sendChromeMessage(options); |
michael@0 | 5934 | }; |
michael@0 | 5935 | RilObject.prototype[REQUEST_SIM_ACCESS_CHANNEL] = function REQUEST_SIM_ACCESS_CHANNEL(length, options) { |
michael@0 | 5936 | if (options.rilRequestError) { |
michael@0 | 5937 | options.error = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError]; |
michael@0 | 5938 | this.sendChromeMessage(options); |
michael@0 | 5939 | } |
michael@0 | 5940 | |
michael@0 | 5941 | let Buf = this.context.Buf; |
michael@0 | 5942 | options.sw1 = Buf.readInt32(); |
michael@0 | 5943 | options.sw2 = Buf.readInt32(); |
michael@0 | 5944 | options.simResponse = Buf.readString(); |
michael@0 | 5945 | if (DEBUG) { |
michael@0 | 5946 | this.context.debug("Setting return values for RIL[REQUEST_SIM_ACCESS_CHANNEL]: [" + |
michael@0 | 5947 | options.sw1 + "," + |
michael@0 | 5948 | options.sw2 + ", " + |
michael@0 | 5949 | options.simResponse + "]"); |
michael@0 | 5950 | } |
michael@0 | 5951 | this.sendChromeMessage(options); |
michael@0 | 5952 | }; |
michael@0 | 5953 | RilObject.prototype[REQUEST_QUERY_NETWORK_SELECTION_MODE] = function REQUEST_QUERY_NETWORK_SELECTION_MODE(length, options) { |
michael@0 | 5954 | this._receivedNetworkInfo(NETWORK_INFO_NETWORK_SELECTION_MODE); |
michael@0 | 5955 | |
michael@0 | 5956 | if (options.rilRequestError) { |
michael@0 | 5957 | return; |
michael@0 | 5958 | } |
michael@0 | 5959 | |
michael@0 | 5960 | let mode = this.context.Buf.readInt32List(); |
michael@0 | 5961 | let selectionMode; |
michael@0 | 5962 | |
michael@0 | 5963 | switch (mode[0]) { |
michael@0 | 5964 | case NETWORK_SELECTION_MODE_AUTOMATIC: |
michael@0 | 5965 | selectionMode = GECKO_NETWORK_SELECTION_AUTOMATIC; |
michael@0 | 5966 | break; |
michael@0 | 5967 | case NETWORK_SELECTION_MODE_MANUAL: |
michael@0 | 5968 | selectionMode = GECKO_NETWORK_SELECTION_MANUAL; |
michael@0 | 5969 | break; |
michael@0 | 5970 | default: |
michael@0 | 5971 | selectionMode = GECKO_NETWORK_SELECTION_UNKNOWN; |
michael@0 | 5972 | break; |
michael@0 | 5973 | } |
michael@0 | 5974 | |
michael@0 | 5975 | if (this.networkSelectionMode != selectionMode) { |
michael@0 | 5976 | this.networkSelectionMode = options.mode = selectionMode; |
michael@0 | 5977 | options.rilMessageType = "networkselectionmodechange"; |
michael@0 | 5978 | this._sendNetworkInfoMessage(NETWORK_INFO_NETWORK_SELECTION_MODE, options); |
michael@0 | 5979 | } |
michael@0 | 5980 | }; |
michael@0 | 5981 | RilObject.prototype[REQUEST_SET_NETWORK_SELECTION_AUTOMATIC] = function REQUEST_SET_NETWORK_SELECTION_AUTOMATIC(length, options) { |
michael@0 | 5982 | if (options.rilRequestError) { |
michael@0 | 5983 | options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError]; |
michael@0 | 5984 | } |
michael@0 | 5985 | |
michael@0 | 5986 | this.sendChromeMessage(options); |
michael@0 | 5987 | }; |
michael@0 | 5988 | RilObject.prototype[REQUEST_SET_NETWORK_SELECTION_MANUAL] = function REQUEST_SET_NETWORK_SELECTION_MANUAL(length, options) { |
michael@0 | 5989 | if (options.rilRequestError) { |
michael@0 | 5990 | options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError]; |
michael@0 | 5991 | } |
michael@0 | 5992 | |
michael@0 | 5993 | this.sendChromeMessage(options); |
michael@0 | 5994 | }; |
michael@0 | 5995 | RilObject.prototype[REQUEST_QUERY_AVAILABLE_NETWORKS] = function REQUEST_QUERY_AVAILABLE_NETWORKS(length, options) { |
michael@0 | 5996 | if (options.rilRequestError) { |
michael@0 | 5997 | options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError]; |
michael@0 | 5998 | } else { |
michael@0 | 5999 | options.networks = this._processNetworks(); |
michael@0 | 6000 | } |
michael@0 | 6001 | this.sendChromeMessage(options); |
michael@0 | 6002 | }; |
michael@0 | 6003 | RilObject.prototype[REQUEST_DTMF_START] = null; |
michael@0 | 6004 | RilObject.prototype[REQUEST_DTMF_STOP] = null; |
michael@0 | 6005 | RilObject.prototype[REQUEST_BASEBAND_VERSION] = function REQUEST_BASEBAND_VERSION(length, options) { |
michael@0 | 6006 | if (options.rilRequestError) { |
michael@0 | 6007 | return; |
michael@0 | 6008 | } |
michael@0 | 6009 | |
michael@0 | 6010 | this.basebandVersion = this.context.Buf.readString(); |
michael@0 | 6011 | if (DEBUG) this.context.debug("Baseband version: " + this.basebandVersion); |
michael@0 | 6012 | }; |
michael@0 | 6013 | RilObject.prototype[REQUEST_SEPARATE_CONNECTION] = function REQUEST_SEPARATE_CONNECTION(length, options) { |
michael@0 | 6014 | options.success = (options.rilRequestError === 0); |
michael@0 | 6015 | if (!options.success) { |
michael@0 | 6016 | options.errorName = "removeError"; |
michael@0 | 6017 | options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError]; |
michael@0 | 6018 | this.sendChromeMessage(options); |
michael@0 | 6019 | return; |
michael@0 | 6020 | } |
michael@0 | 6021 | |
michael@0 | 6022 | this.sendChromeMessage(options); |
michael@0 | 6023 | }; |
michael@0 | 6024 | RilObject.prototype[REQUEST_SET_MUTE] = null; |
michael@0 | 6025 | RilObject.prototype[REQUEST_GET_MUTE] = null; |
michael@0 | 6026 | RilObject.prototype[REQUEST_QUERY_CLIP] = function REQUEST_QUERY_CLIP(length, options) { |
michael@0 | 6027 | options.success = (options.rilRequestError === 0); |
michael@0 | 6028 | if (!options.success) { |
michael@0 | 6029 | options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError]; |
michael@0 | 6030 | this.sendChromeMessage(options); |
michael@0 | 6031 | return; |
michael@0 | 6032 | } |
michael@0 | 6033 | |
michael@0 | 6034 | let Buf = this.context.Buf; |
michael@0 | 6035 | let bufLength = Buf.readInt32(); |
michael@0 | 6036 | if (!bufLength) { |
michael@0 | 6037 | options.success = false; |
michael@0 | 6038 | options.errorMsg = GECKO_ERROR_GENERIC_FAILURE; |
michael@0 | 6039 | this.sendChromeMessage(options); |
michael@0 | 6040 | return; |
michael@0 | 6041 | } |
michael@0 | 6042 | |
michael@0 | 6043 | // options.provisioned informs about the called party receives the calling |
michael@0 | 6044 | // party's address information: |
michael@0 | 6045 | // 0 for CLIP not provisioned |
michael@0 | 6046 | // 1 for CLIP provisioned |
michael@0 | 6047 | // 2 for unknown |
michael@0 | 6048 | options.provisioned = Buf.readInt32(); |
michael@0 | 6049 | if (options.rilMessageType === "sendMMI") { |
michael@0 | 6050 | switch (options.provisioned) { |
michael@0 | 6051 | case 0: |
michael@0 | 6052 | options.statusMessage = MMI_SM_KS_SERVICE_DISABLED; |
michael@0 | 6053 | break; |
michael@0 | 6054 | case 1: |
michael@0 | 6055 | options.statusMessage = MMI_SM_KS_SERVICE_ENABLED; |
michael@0 | 6056 | break; |
michael@0 | 6057 | default: |
michael@0 | 6058 | options.success = false; |
michael@0 | 6059 | options.errorMsg = MMI_ERROR_KS_ERROR; |
michael@0 | 6060 | break; |
michael@0 | 6061 | } |
michael@0 | 6062 | } |
michael@0 | 6063 | this.sendChromeMessage(options); |
michael@0 | 6064 | }; |
michael@0 | 6065 | RilObject.prototype[REQUEST_LAST_DATA_CALL_FAIL_CAUSE] = null; |
michael@0 | 6066 | |
michael@0 | 6067 | /** |
michael@0 | 6068 | * V3: |
michael@0 | 6069 | * # address - A space-delimited list of addresses. |
michael@0 | 6070 | * |
michael@0 | 6071 | * V4: |
michael@0 | 6072 | * # address - An address. |
michael@0 | 6073 | * |
michael@0 | 6074 | * V5: |
michael@0 | 6075 | * # addresses - A space-delimited list of addresses. |
michael@0 | 6076 | * # dnses - A space-delimited list of DNS server addresses. |
michael@0 | 6077 | * |
michael@0 | 6078 | * V6: |
michael@0 | 6079 | * # addresses - A space-delimited list of addresses with optional "/" prefix |
michael@0 | 6080 | * length. |
michael@0 | 6081 | * # dnses - A space-delimited list of DNS server addresses. |
michael@0 | 6082 | * # gateways - A space-delimited list of default gateway addresses. |
michael@0 | 6083 | */ |
michael@0 | 6084 | RilObject.prototype.readDataCall_v5 = function(options) { |
michael@0 | 6085 | if (!options) { |
michael@0 | 6086 | options = {}; |
michael@0 | 6087 | } |
michael@0 | 6088 | let Buf = this.context.Buf; |
michael@0 | 6089 | options.cid = Buf.readInt32().toString(); |
michael@0 | 6090 | options.active = Buf.readInt32(); // DATACALL_ACTIVE_* |
michael@0 | 6091 | options.type = Buf.readString(); |
michael@0 | 6092 | options.apn = Buf.readString(); |
michael@0 | 6093 | let addresses = Buf.readString(); |
michael@0 | 6094 | let dnses = Buf.readString(); |
michael@0 | 6095 | options.addresses = addresses ? addresses.split(" ") : []; |
michael@0 | 6096 | options.dnses = dnses ? dnses.split(" ") : []; |
michael@0 | 6097 | options.gateways = []; |
michael@0 | 6098 | return options; |
michael@0 | 6099 | }; |
michael@0 | 6100 | |
michael@0 | 6101 | RilObject.prototype.readDataCall_v6 = function(options) { |
michael@0 | 6102 | if (!options) { |
michael@0 | 6103 | options = {}; |
michael@0 | 6104 | } |
michael@0 | 6105 | let Buf = this.context.Buf; |
michael@0 | 6106 | options.status = Buf.readInt32(); // DATACALL_FAIL_* |
michael@0 | 6107 | options.suggestedRetryTime = Buf.readInt32(); |
michael@0 | 6108 | options.cid = Buf.readInt32().toString(); |
michael@0 | 6109 | options.active = Buf.readInt32(); // DATACALL_ACTIVE_* |
michael@0 | 6110 | options.type = Buf.readString(); |
michael@0 | 6111 | options.ifname = Buf.readString(); |
michael@0 | 6112 | let addresses = Buf.readString(); |
michael@0 | 6113 | let dnses = Buf.readString(); |
michael@0 | 6114 | let gateways = Buf.readString(); |
michael@0 | 6115 | options.addresses = addresses ? addresses.split(" ") : []; |
michael@0 | 6116 | options.dnses = dnses ? dnses.split(" ") : []; |
michael@0 | 6117 | options.gateways = gateways ? gateways.split(" ") : []; |
michael@0 | 6118 | return options; |
michael@0 | 6119 | }; |
michael@0 | 6120 | |
michael@0 | 6121 | RilObject.prototype[REQUEST_DATA_CALL_LIST] = function REQUEST_DATA_CALL_LIST(length, options) { |
michael@0 | 6122 | if (options.rilRequestError) { |
michael@0 | 6123 | return; |
michael@0 | 6124 | } |
michael@0 | 6125 | |
michael@0 | 6126 | if (!length) { |
michael@0 | 6127 | this._processDataCallList(null); |
michael@0 | 6128 | return; |
michael@0 | 6129 | } |
michael@0 | 6130 | |
michael@0 | 6131 | let Buf = this.context.Buf; |
michael@0 | 6132 | let version = 0; |
michael@0 | 6133 | if (!this.v5Legacy) { |
michael@0 | 6134 | version = Buf.readInt32(); |
michael@0 | 6135 | } |
michael@0 | 6136 | let num = Buf.readInt32(); |
michael@0 | 6137 | let datacalls = {}; |
michael@0 | 6138 | for (let i = 0; i < num; i++) { |
michael@0 | 6139 | let datacall; |
michael@0 | 6140 | if (version < 6) { |
michael@0 | 6141 | datacall = this.readDataCall_v5(); |
michael@0 | 6142 | } else { |
michael@0 | 6143 | datacall = this.readDataCall_v6(); |
michael@0 | 6144 | } |
michael@0 | 6145 | datacalls[datacall.cid] = datacall; |
michael@0 | 6146 | } |
michael@0 | 6147 | |
michael@0 | 6148 | let newDataCallOptions = null; |
michael@0 | 6149 | if (options.rilRequestType == REQUEST_SETUP_DATA_CALL) { |
michael@0 | 6150 | newDataCallOptions = options; |
michael@0 | 6151 | } |
michael@0 | 6152 | this._processDataCallList(datacalls, newDataCallOptions); |
michael@0 | 6153 | }; |
michael@0 | 6154 | RilObject.prototype[REQUEST_RESET_RADIO] = null; |
michael@0 | 6155 | RilObject.prototype[REQUEST_OEM_HOOK_RAW] = null; |
michael@0 | 6156 | RilObject.prototype[REQUEST_OEM_HOOK_STRINGS] = null; |
michael@0 | 6157 | RilObject.prototype[REQUEST_SCREEN_STATE] = null; |
michael@0 | 6158 | RilObject.prototype[REQUEST_SET_SUPP_SVC_NOTIFICATION] = null; |
michael@0 | 6159 | RilObject.prototype[REQUEST_WRITE_SMS_TO_SIM] = function REQUEST_WRITE_SMS_TO_SIM(length, options) { |
michael@0 | 6160 | if (options.rilRequestError) { |
michael@0 | 6161 | // `The MS shall return a "protocol error, unspecified" error message if |
michael@0 | 6162 | // the short message cannot be stored in the (U)SIM, and there is other |
michael@0 | 6163 | // message storage available at the MS` ~ 3GPP TS 23.038 section 4. Here |
michael@0 | 6164 | // we assume we always have indexed db as another storage. |
michael@0 | 6165 | this.acknowledgeGsmSms(false, PDU_FCS_PROTOCOL_ERROR); |
michael@0 | 6166 | } else { |
michael@0 | 6167 | this.acknowledgeGsmSms(true, PDU_FCS_OK); |
michael@0 | 6168 | } |
michael@0 | 6169 | }; |
michael@0 | 6170 | RilObject.prototype[REQUEST_DELETE_SMS_ON_SIM] = null; |
michael@0 | 6171 | RilObject.prototype[REQUEST_SET_BAND_MODE] = null; |
michael@0 | 6172 | RilObject.prototype[REQUEST_QUERY_AVAILABLE_BAND_MODE] = null; |
michael@0 | 6173 | RilObject.prototype[REQUEST_STK_GET_PROFILE] = null; |
michael@0 | 6174 | RilObject.prototype[REQUEST_STK_SET_PROFILE] = null; |
michael@0 | 6175 | RilObject.prototype[REQUEST_STK_SEND_ENVELOPE_COMMAND] = null; |
michael@0 | 6176 | RilObject.prototype[REQUEST_STK_SEND_TERMINAL_RESPONSE] = null; |
michael@0 | 6177 | RilObject.prototype[REQUEST_STK_HANDLE_CALL_SETUP_REQUESTED_FROM_SIM] = null; |
michael@0 | 6178 | RilObject.prototype[REQUEST_EXPLICIT_CALL_TRANSFER] = null; |
michael@0 | 6179 | RilObject.prototype[REQUEST_SET_PREFERRED_NETWORK_TYPE] = function REQUEST_SET_PREFERRED_NETWORK_TYPE(length, options) { |
michael@0 | 6180 | if (options.networkType == null) { |
michael@0 | 6181 | // The request was made by ril_worker itself automatically. Don't report. |
michael@0 | 6182 | return; |
michael@0 | 6183 | } |
michael@0 | 6184 | |
michael@0 | 6185 | if (options.rilRequestError) { |
michael@0 | 6186 | options.success = false; |
michael@0 | 6187 | options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError]; |
michael@0 | 6188 | } else { |
michael@0 | 6189 | options.success = true; |
michael@0 | 6190 | } |
michael@0 | 6191 | this.sendChromeMessage(options); |
michael@0 | 6192 | }; |
michael@0 | 6193 | RilObject.prototype[REQUEST_GET_PREFERRED_NETWORK_TYPE] = function REQUEST_GET_PREFERRED_NETWORK_TYPE(length, options) { |
michael@0 | 6194 | if (options.rilRequestError) { |
michael@0 | 6195 | options.success = false; |
michael@0 | 6196 | options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError]; |
michael@0 | 6197 | this.sendChromeMessage(options); |
michael@0 | 6198 | return; |
michael@0 | 6199 | } |
michael@0 | 6200 | |
michael@0 | 6201 | let results = this.context.Buf.readInt32List(); |
michael@0 | 6202 | options.networkType = this.preferredNetworkType = results[0]; |
michael@0 | 6203 | options.success = true; |
michael@0 | 6204 | |
michael@0 | 6205 | this.sendChromeMessage(options); |
michael@0 | 6206 | }; |
michael@0 | 6207 | RilObject.prototype[REQUEST_GET_NEIGHBORING_CELL_IDS] = null; |
michael@0 | 6208 | RilObject.prototype[REQUEST_SET_LOCATION_UPDATES] = null; |
michael@0 | 6209 | RilObject.prototype[REQUEST_CDMA_SET_SUBSCRIPTION_SOURCE] = null; |
michael@0 | 6210 | RilObject.prototype[REQUEST_CDMA_SET_ROAMING_PREFERENCE] = function REQUEST_CDMA_SET_ROAMING_PREFERENCE(length, options) { |
michael@0 | 6211 | if (options.rilRequestError) { |
michael@0 | 6212 | options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError]; |
michael@0 | 6213 | } |
michael@0 | 6214 | this.sendChromeMessage(options); |
michael@0 | 6215 | }; |
michael@0 | 6216 | RilObject.prototype[REQUEST_CDMA_QUERY_ROAMING_PREFERENCE] = function REQUEST_CDMA_QUERY_ROAMING_PREFERENCE(length, options) { |
michael@0 | 6217 | if (options.rilRequestError) { |
michael@0 | 6218 | options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError]; |
michael@0 | 6219 | } else { |
michael@0 | 6220 | let mode = this.context.Buf.readInt32List(); |
michael@0 | 6221 | options.mode = CDMA_ROAMING_PREFERENCE_TO_GECKO[mode[0]]; |
michael@0 | 6222 | } |
michael@0 | 6223 | this.sendChromeMessage(options); |
michael@0 | 6224 | }; |
michael@0 | 6225 | RilObject.prototype[REQUEST_SET_TTY_MODE] = null; |
michael@0 | 6226 | RilObject.prototype[REQUEST_QUERY_TTY_MODE] = null; |
michael@0 | 6227 | RilObject.prototype[REQUEST_CDMA_SET_PREFERRED_VOICE_PRIVACY_MODE] = function REQUEST_CDMA_SET_PREFERRED_VOICE_PRIVACY_MODE(length, options) { |
michael@0 | 6228 | if (options.rilRequestError) { |
michael@0 | 6229 | options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError]; |
michael@0 | 6230 | this.sendChromeMessage(options); |
michael@0 | 6231 | return; |
michael@0 | 6232 | } |
michael@0 | 6233 | |
michael@0 | 6234 | this.sendChromeMessage(options); |
michael@0 | 6235 | }; |
michael@0 | 6236 | RilObject.prototype[REQUEST_CDMA_QUERY_PREFERRED_VOICE_PRIVACY_MODE] = function REQUEST_CDMA_QUERY_PREFERRED_VOICE_PRIVACY_MODE(length, options) { |
michael@0 | 6237 | if (options.rilRequestError) { |
michael@0 | 6238 | options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError]; |
michael@0 | 6239 | this.sendChromeMessage(options); |
michael@0 | 6240 | return; |
michael@0 | 6241 | } |
michael@0 | 6242 | |
michael@0 | 6243 | let enabled = this.context.Buf.readInt32List(); |
michael@0 | 6244 | options.enabled = enabled[0] ? true : false; |
michael@0 | 6245 | this.sendChromeMessage(options); |
michael@0 | 6246 | }; |
michael@0 | 6247 | RilObject.prototype[REQUEST_CDMA_FLASH] = function REQUEST_CDMA_FLASH(length, options) { |
michael@0 | 6248 | options.success = (options.rilRequestError === 0); |
michael@0 | 6249 | if (!options.success) { |
michael@0 | 6250 | if (options.rilMessageType === "conferenceCall") { |
michael@0 | 6251 | options.errorName = "addError"; |
michael@0 | 6252 | } else if (options.rilMessageType === "separateCall") { |
michael@0 | 6253 | options.errorName = "removeError"; |
michael@0 | 6254 | } |
michael@0 | 6255 | options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError]; |
michael@0 | 6256 | } |
michael@0 | 6257 | |
michael@0 | 6258 | this.sendChromeMessage(options); |
michael@0 | 6259 | }; |
michael@0 | 6260 | RilObject.prototype[REQUEST_CDMA_BURST_DTMF] = null; |
michael@0 | 6261 | RilObject.prototype[REQUEST_CDMA_VALIDATE_AND_WRITE_AKEY] = null; |
michael@0 | 6262 | RilObject.prototype[REQUEST_CDMA_SEND_SMS] = function REQUEST_CDMA_SEND_SMS(length, options) { |
michael@0 | 6263 | this._processSmsSendResult(length, options); |
michael@0 | 6264 | }; |
michael@0 | 6265 | RilObject.prototype[REQUEST_CDMA_SMS_ACKNOWLEDGE] = null; |
michael@0 | 6266 | RilObject.prototype[REQUEST_GSM_GET_BROADCAST_SMS_CONFIG] = null; |
michael@0 | 6267 | RilObject.prototype[REQUEST_GSM_SET_BROADCAST_SMS_CONFIG] = function REQUEST_GSM_SET_BROADCAST_SMS_CONFIG(length, options) { |
michael@0 | 6268 | if (options.rilRequestError == ERROR_SUCCESS) { |
michael@0 | 6269 | this.setSmsBroadcastActivation(true); |
michael@0 | 6270 | } |
michael@0 | 6271 | }; |
michael@0 | 6272 | RilObject.prototype[REQUEST_GSM_SMS_BROADCAST_ACTIVATION] = null; |
michael@0 | 6273 | RilObject.prototype[REQUEST_CDMA_GET_BROADCAST_SMS_CONFIG] = null; |
michael@0 | 6274 | RilObject.prototype[REQUEST_CDMA_SET_BROADCAST_SMS_CONFIG] = null; |
michael@0 | 6275 | RilObject.prototype[REQUEST_CDMA_SMS_BROADCAST_ACTIVATION] = null; |
michael@0 | 6276 | RilObject.prototype[REQUEST_CDMA_SUBSCRIPTION] = function REQUEST_CDMA_SUBSCRIPTION(length, options) { |
michael@0 | 6277 | if (options.rilRequestError) { |
michael@0 | 6278 | return; |
michael@0 | 6279 | } |
michael@0 | 6280 | |
michael@0 | 6281 | let result = this.context.Buf.readStringList(); |
michael@0 | 6282 | |
michael@0 | 6283 | this.iccInfo.mdn = result[0]; |
michael@0 | 6284 | // The result[1] is Home SID. (Already be handled in readCDMAHome()) |
michael@0 | 6285 | // The result[2] is Home NID. (Already be handled in readCDMAHome()) |
michael@0 | 6286 | // The result[3] is MIN. |
michael@0 | 6287 | this.iccInfo.prlVersion = parseInt(result[4], 10); |
michael@0 | 6288 | |
michael@0 | 6289 | this.context.ICCUtilsHelper.handleICCInfoChange(); |
michael@0 | 6290 | }; |
michael@0 | 6291 | RilObject.prototype[REQUEST_CDMA_WRITE_SMS_TO_RUIM] = null; |
michael@0 | 6292 | RilObject.prototype[REQUEST_CDMA_DELETE_SMS_ON_RUIM] = null; |
michael@0 | 6293 | RilObject.prototype[REQUEST_DEVICE_IDENTITY] = function REQUEST_DEVICE_IDENTITY(length, options) { |
michael@0 | 6294 | if (options.rilRequestError) { |
michael@0 | 6295 | return; |
michael@0 | 6296 | } |
michael@0 | 6297 | |
michael@0 | 6298 | let result = this.context.Buf.readStringList(); |
michael@0 | 6299 | |
michael@0 | 6300 | // The result[0] is for IMEI. (Already be handled in REQUEST_GET_IMEI) |
michael@0 | 6301 | // The result[1] is for IMEISV. (Already be handled in REQUEST_GET_IMEISV) |
michael@0 | 6302 | // They are both ignored. |
michael@0 | 6303 | this.ESN = result[2]; |
michael@0 | 6304 | this.MEID = result[3]; |
michael@0 | 6305 | }; |
michael@0 | 6306 | RilObject.prototype[REQUEST_EXIT_EMERGENCY_CALLBACK_MODE] = function REQUEST_EXIT_EMERGENCY_CALLBACK_MODE(length, options) { |
michael@0 | 6307 | if (options.internal) { |
michael@0 | 6308 | return; |
michael@0 | 6309 | } |
michael@0 | 6310 | |
michael@0 | 6311 | options.success = (options.rilRequestError === 0); |
michael@0 | 6312 | if (!options.success) { |
michael@0 | 6313 | options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError]; |
michael@0 | 6314 | } |
michael@0 | 6315 | this.sendChromeMessage(options); |
michael@0 | 6316 | }; |
michael@0 | 6317 | RilObject.prototype[REQUEST_GET_SMSC_ADDRESS] = function REQUEST_GET_SMSC_ADDRESS(length, options) { |
michael@0 | 6318 | this.SMSC = options.rilRequestError ? null : this.context.Buf.readString(); |
michael@0 | 6319 | |
michael@0 | 6320 | if (!options.rilMessageType || options.rilMessageType !== "getSmscAddress") { |
michael@0 | 6321 | return; |
michael@0 | 6322 | } |
michael@0 | 6323 | |
michael@0 | 6324 | options.smscAddress = this.SMSC; |
michael@0 | 6325 | options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError]; |
michael@0 | 6326 | this.sendChromeMessage(options); |
michael@0 | 6327 | }; |
michael@0 | 6328 | RilObject.prototype[REQUEST_SET_SMSC_ADDRESS] = null; |
michael@0 | 6329 | RilObject.prototype[REQUEST_REPORT_SMS_MEMORY_STATUS] = null; |
michael@0 | 6330 | RilObject.prototype[REQUEST_REPORT_STK_SERVICE_IS_RUNNING] = null; |
michael@0 | 6331 | RilObject.prototype[REQUEST_ACKNOWLEDGE_INCOMING_GSM_SMS_WITH_PDU] = null; |
michael@0 | 6332 | RilObject.prototype[REQUEST_STK_SEND_ENVELOPE_WITH_STATUS] = function REQUEST_STK_SEND_ENVELOPE_WITH_STATUS(length, options) { |
michael@0 | 6333 | if (options.rilRequestError) { |
michael@0 | 6334 | this.acknowledgeGsmSms(false, PDU_FCS_UNSPECIFIED); |
michael@0 | 6335 | return; |
michael@0 | 6336 | } |
michael@0 | 6337 | |
michael@0 | 6338 | let Buf = this.context.Buf; |
michael@0 | 6339 | let sw1 = Buf.readInt32(); |
michael@0 | 6340 | let sw2 = Buf.readInt32(); |
michael@0 | 6341 | if ((sw1 == ICC_STATUS_SAT_BUSY) && (sw2 === 0x00)) { |
michael@0 | 6342 | this.acknowledgeGsmSms(false, PDU_FCS_USAT_BUSY); |
michael@0 | 6343 | return; |
michael@0 | 6344 | } |
michael@0 | 6345 | |
michael@0 | 6346 | let success = ((sw1 == ICC_STATUS_NORMAL_ENDING) && (sw2 === 0x00)) |
michael@0 | 6347 | || (sw1 == ICC_STATUS_NORMAL_ENDING_WITH_EXTRA); |
michael@0 | 6348 | |
michael@0 | 6349 | let messageStringLength = Buf.readInt32(); // In semi-octets |
michael@0 | 6350 | let responsePduLen = messageStringLength / 2; // In octets |
michael@0 | 6351 | if (!responsePduLen) { |
michael@0 | 6352 | this.acknowledgeGsmSms(success, success ? PDU_FCS_OK |
michael@0 | 6353 | : PDU_FCS_USIM_DATA_DOWNLOAD_ERROR); |
michael@0 | 6354 | return; |
michael@0 | 6355 | } |
michael@0 | 6356 | |
michael@0 | 6357 | this.acknowledgeIncomingGsmSmsWithPDU(success, responsePduLen, options); |
michael@0 | 6358 | }; |
michael@0 | 6359 | RilObject.prototype[REQUEST_VOICE_RADIO_TECH] = function REQUEST_VOICE_RADIO_TECH(length, options) { |
michael@0 | 6360 | if (options.rilRequestError) { |
michael@0 | 6361 | if (DEBUG) { |
michael@0 | 6362 | this.context.debug("Error when getting voice radio tech: " + |
michael@0 | 6363 | options.rilRequestError); |
michael@0 | 6364 | } |
michael@0 | 6365 | return; |
michael@0 | 6366 | } |
michael@0 | 6367 | let radioTech = this.context.Buf.readInt32List(); |
michael@0 | 6368 | this._processRadioTech(radioTech[0]); |
michael@0 | 6369 | }; |
michael@0 | 6370 | RilObject.prototype[REQUEST_GET_UNLOCK_RETRY_COUNT] = function REQUEST_GET_UNLOCK_RETRY_COUNT(length, options) { |
michael@0 | 6371 | options.success = (options.rilRequestError === 0); |
michael@0 | 6372 | if (!options.success) { |
michael@0 | 6373 | options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError]; |
michael@0 | 6374 | } |
michael@0 | 6375 | options.retryCount = length ? this.context.Buf.readInt32List()[0] : -1; |
michael@0 | 6376 | this.sendChromeMessage(options); |
michael@0 | 6377 | }; |
michael@0 | 6378 | RilObject.prototype[RIL_REQUEST_GPRS_ATTACH] = null; |
michael@0 | 6379 | RilObject.prototype[RIL_REQUEST_GPRS_DETACH] = null; |
michael@0 | 6380 | RilObject.prototype[UNSOLICITED_RESPONSE_RADIO_STATE_CHANGED] = function UNSOLICITED_RESPONSE_RADIO_STATE_CHANGED() { |
michael@0 | 6381 | let radioState = this.context.Buf.readInt32(); |
michael@0 | 6382 | let newState; |
michael@0 | 6383 | if (radioState == RADIO_STATE_UNAVAILABLE) { |
michael@0 | 6384 | newState = GECKO_RADIOSTATE_UNAVAILABLE; |
michael@0 | 6385 | } else if (radioState == RADIO_STATE_OFF) { |
michael@0 | 6386 | newState = GECKO_RADIOSTATE_OFF; |
michael@0 | 6387 | } else { |
michael@0 | 6388 | newState = GECKO_RADIOSTATE_READY; |
michael@0 | 6389 | } |
michael@0 | 6390 | |
michael@0 | 6391 | if (DEBUG) { |
michael@0 | 6392 | this.context.debug("Radio state changed from '" + this.radioState + |
michael@0 | 6393 | "' to '" + newState + "'"); |
michael@0 | 6394 | } |
michael@0 | 6395 | if (this.radioState == newState) { |
michael@0 | 6396 | return; |
michael@0 | 6397 | } |
michael@0 | 6398 | |
michael@0 | 6399 | switch (radioState) { |
michael@0 | 6400 | case RADIO_STATE_SIM_READY: |
michael@0 | 6401 | case RADIO_STATE_SIM_NOT_READY: |
michael@0 | 6402 | case RADIO_STATE_SIM_LOCKED_OR_ABSENT: |
michael@0 | 6403 | this._isCdma = false; |
michael@0 | 6404 | this._waitingRadioTech = false; |
michael@0 | 6405 | break; |
michael@0 | 6406 | case RADIO_STATE_RUIM_READY: |
michael@0 | 6407 | case RADIO_STATE_RUIM_NOT_READY: |
michael@0 | 6408 | case RADIO_STATE_RUIM_LOCKED_OR_ABSENT: |
michael@0 | 6409 | case RADIO_STATE_NV_READY: |
michael@0 | 6410 | case RADIO_STATE_NV_NOT_READY: |
michael@0 | 6411 | this._isCdma = true; |
michael@0 | 6412 | this._waitingRadioTech = false; |
michael@0 | 6413 | break; |
michael@0 | 6414 | case RADIO_STATE_ON: // RIL v7 |
michael@0 | 6415 | // This value is defined in RIL v7, we will retrieve radio tech by another |
michael@0 | 6416 | // request. We leave _isCdma untouched, and it will be set once we get the |
michael@0 | 6417 | // radio technology. |
michael@0 | 6418 | this._waitingRadioTech = true; |
michael@0 | 6419 | this.getVoiceRadioTechnology(); |
michael@0 | 6420 | break; |
michael@0 | 6421 | } |
michael@0 | 6422 | |
michael@0 | 6423 | if ((this.radioState == GECKO_RADIOSTATE_UNAVAILABLE || |
michael@0 | 6424 | this.radioState == GECKO_RADIOSTATE_OFF) && |
michael@0 | 6425 | newState == GECKO_RADIOSTATE_READY) { |
michael@0 | 6426 | // The radio became available, let's get its info. |
michael@0 | 6427 | if (!this._waitingRadioTech) { |
michael@0 | 6428 | if (this._isCdma) { |
michael@0 | 6429 | this.getDeviceIdentity(); |
michael@0 | 6430 | } else { |
michael@0 | 6431 | this.getIMEI(); |
michael@0 | 6432 | this.getIMEISV(); |
michael@0 | 6433 | } |
michael@0 | 6434 | } |
michael@0 | 6435 | this.getBasebandVersion(); |
michael@0 | 6436 | this.updateCellBroadcastConfig(); |
michael@0 | 6437 | this.setPreferredNetworkType(); |
michael@0 | 6438 | this.setCLIR(); |
michael@0 | 6439 | if (RILQUIRKS_DATA_REGISTRATION_ON_DEMAND && this._attachDataRegistration) { |
michael@0 | 6440 | this.setDataRegistration({attach: true}); |
michael@0 | 6441 | } |
michael@0 | 6442 | } |
michael@0 | 6443 | |
michael@0 | 6444 | this.radioState = newState; |
michael@0 | 6445 | this.sendChromeMessage({ |
michael@0 | 6446 | rilMessageType: "radiostatechange", |
michael@0 | 6447 | radioState: newState |
michael@0 | 6448 | }); |
michael@0 | 6449 | |
michael@0 | 6450 | // If the radio is up and on, so let's query the card state. |
michael@0 | 6451 | // On older RILs only if the card is actually ready, though. |
michael@0 | 6452 | // If _waitingRadioTech is set, we don't need to get icc status now. |
michael@0 | 6453 | if (radioState == RADIO_STATE_UNAVAILABLE || |
michael@0 | 6454 | radioState == RADIO_STATE_OFF || |
michael@0 | 6455 | this._waitingRadioTech) { |
michael@0 | 6456 | return; |
michael@0 | 6457 | } |
michael@0 | 6458 | this.getICCStatus(); |
michael@0 | 6459 | }; |
michael@0 | 6460 | RilObject.prototype[UNSOLICITED_RESPONSE_CALL_STATE_CHANGED] = function UNSOLICITED_RESPONSE_CALL_STATE_CHANGED() { |
michael@0 | 6461 | this.getCurrentCalls(); |
michael@0 | 6462 | }; |
michael@0 | 6463 | RilObject.prototype[UNSOLICITED_RESPONSE_VOICE_NETWORK_STATE_CHANGED] = function UNSOLICITED_RESPONSE_VOICE_NETWORK_STATE_CHANGED() { |
michael@0 | 6464 | if (DEBUG) { |
michael@0 | 6465 | this.context.debug("Network state changed, re-requesting phone state and " + |
michael@0 | 6466 | "ICC status"); |
michael@0 | 6467 | } |
michael@0 | 6468 | this.getICCStatus(); |
michael@0 | 6469 | this.requestNetworkInfo(); |
michael@0 | 6470 | }; |
michael@0 | 6471 | RilObject.prototype[UNSOLICITED_RESPONSE_NEW_SMS] = function UNSOLICITED_RESPONSE_NEW_SMS(length) { |
michael@0 | 6472 | let [message, result] = this.context.GsmPDUHelper.processReceivedSms(length); |
michael@0 | 6473 | |
michael@0 | 6474 | if (message) { |
michael@0 | 6475 | result = this._processSmsMultipart(message); |
michael@0 | 6476 | } |
michael@0 | 6477 | |
michael@0 | 6478 | if (result == PDU_FCS_RESERVED || result == MOZ_FCS_WAIT_FOR_EXPLICIT_ACK) { |
michael@0 | 6479 | return; |
michael@0 | 6480 | } |
michael@0 | 6481 | |
michael@0 | 6482 | // Not reserved FCS values, send ACK now. |
michael@0 | 6483 | this.acknowledgeGsmSms(result == PDU_FCS_OK, result); |
michael@0 | 6484 | }; |
michael@0 | 6485 | RilObject.prototype[UNSOLICITED_RESPONSE_NEW_SMS_STATUS_REPORT] = function UNSOLICITED_RESPONSE_NEW_SMS_STATUS_REPORT(length) { |
michael@0 | 6486 | let result = this._processSmsStatusReport(length); |
michael@0 | 6487 | this.acknowledgeGsmSms(result == PDU_FCS_OK, result); |
michael@0 | 6488 | }; |
michael@0 | 6489 | RilObject.prototype[UNSOLICITED_RESPONSE_NEW_SMS_ON_SIM] = function UNSOLICITED_RESPONSE_NEW_SMS_ON_SIM(length) { |
michael@0 | 6490 | let recordNumber = this.context.Buf.readInt32List()[0]; |
michael@0 | 6491 | |
michael@0 | 6492 | this.context.SimRecordHelper.readSMS( |
michael@0 | 6493 | recordNumber, |
michael@0 | 6494 | function onsuccess(message) { |
michael@0 | 6495 | if (message && message.simStatus === 3) { //New Unread SMS |
michael@0 | 6496 | this._processSmsMultipart(message); |
michael@0 | 6497 | } |
michael@0 | 6498 | }.bind(this), |
michael@0 | 6499 | function onerror(errorMsg) { |
michael@0 | 6500 | if (DEBUG) { |
michael@0 | 6501 | this.context.debug("Failed to Read NEW SMS on SIM #" + recordNumber + |
michael@0 | 6502 | ", errorMsg: " + errorMsg); |
michael@0 | 6503 | } |
michael@0 | 6504 | }); |
michael@0 | 6505 | }; |
michael@0 | 6506 | RilObject.prototype[UNSOLICITED_ON_USSD] = function UNSOLICITED_ON_USSD() { |
michael@0 | 6507 | let [typeCode, message] = this.context.Buf.readStringList(); |
michael@0 | 6508 | if (DEBUG) { |
michael@0 | 6509 | this.context.debug("On USSD. Type Code: " + typeCode + " Message: " + message); |
michael@0 | 6510 | } |
michael@0 | 6511 | |
michael@0 | 6512 | this._ussdSession = (typeCode != "0" && typeCode != "2"); |
michael@0 | 6513 | |
michael@0 | 6514 | this.sendChromeMessage({rilMessageType: "USSDReceived", |
michael@0 | 6515 | message: message, |
michael@0 | 6516 | sessionEnded: !this._ussdSession}); |
michael@0 | 6517 | }; |
michael@0 | 6518 | RilObject.prototype[UNSOLICITED_NITZ_TIME_RECEIVED] = function UNSOLICITED_NITZ_TIME_RECEIVED() { |
michael@0 | 6519 | let dateString = this.context.Buf.readString(); |
michael@0 | 6520 | |
michael@0 | 6521 | // The data contained in the NITZ message is |
michael@0 | 6522 | // in the form "yy/mm/dd,hh:mm:ss(+/-)tz,dt" |
michael@0 | 6523 | // for example: 12/02/16,03:36:08-20,00,310410 |
michael@0 | 6524 | // See also bug 714352 - Listen for NITZ updates from rild. |
michael@0 | 6525 | |
michael@0 | 6526 | if (DEBUG) this.context.debug("DateTimeZone string " + dateString); |
michael@0 | 6527 | |
michael@0 | 6528 | let now = Date.now(); |
michael@0 | 6529 | |
michael@0 | 6530 | let year = parseInt(dateString.substr(0, 2), 10); |
michael@0 | 6531 | let month = parseInt(dateString.substr(3, 2), 10); |
michael@0 | 6532 | let day = parseInt(dateString.substr(6, 2), 10); |
michael@0 | 6533 | let hours = parseInt(dateString.substr(9, 2), 10); |
michael@0 | 6534 | let minutes = parseInt(dateString.substr(12, 2), 10); |
michael@0 | 6535 | let seconds = parseInt(dateString.substr(15, 2), 10); |
michael@0 | 6536 | // Note that |tz| is in 15-min units. |
michael@0 | 6537 | let tz = parseInt(dateString.substr(17, 3), 10); |
michael@0 | 6538 | // Note that |dst| is in 1-hour units and is already applied in |tz|. |
michael@0 | 6539 | let dst = parseInt(dateString.substr(21, 2), 10); |
michael@0 | 6540 | |
michael@0 | 6541 | let timeInMS = Date.UTC(year + PDU_TIMESTAMP_YEAR_OFFSET, month - 1, day, |
michael@0 | 6542 | hours, minutes, seconds); |
michael@0 | 6543 | |
michael@0 | 6544 | if (isNaN(timeInMS)) { |
michael@0 | 6545 | if (DEBUG) this.context.debug("NITZ failed to convert date"); |
michael@0 | 6546 | return; |
michael@0 | 6547 | } |
michael@0 | 6548 | |
michael@0 | 6549 | this.sendChromeMessage({rilMessageType: "nitzTime", |
michael@0 | 6550 | networkTimeInMS: timeInMS, |
michael@0 | 6551 | networkTimeZoneInMinutes: -(tz * 15), |
michael@0 | 6552 | networkDSTInMinutes: -(dst * 60), |
michael@0 | 6553 | receiveTimeInMS: now}); |
michael@0 | 6554 | }; |
michael@0 | 6555 | |
michael@0 | 6556 | RilObject.prototype[UNSOLICITED_SIGNAL_STRENGTH] = function UNSOLICITED_SIGNAL_STRENGTH(length) { |
michael@0 | 6557 | this[REQUEST_SIGNAL_STRENGTH](length, {rilRequestError: ERROR_SUCCESS}); |
michael@0 | 6558 | }; |
michael@0 | 6559 | RilObject.prototype[UNSOLICITED_DATA_CALL_LIST_CHANGED] = function UNSOLICITED_DATA_CALL_LIST_CHANGED(length) { |
michael@0 | 6560 | if (this.v5Legacy) { |
michael@0 | 6561 | this.getDataCallList(); |
michael@0 | 6562 | return; |
michael@0 | 6563 | } |
michael@0 | 6564 | this[REQUEST_DATA_CALL_LIST](length, {rilRequestError: ERROR_SUCCESS}); |
michael@0 | 6565 | }; |
michael@0 | 6566 | RilObject.prototype[UNSOLICITED_SUPP_SVC_NOTIFICATION] = function UNSOLICITED_SUPP_SVC_NOTIFICATION(length) { |
michael@0 | 6567 | let Buf = this.context.Buf; |
michael@0 | 6568 | let info = {}; |
michael@0 | 6569 | info.notificationType = Buf.readInt32(); |
michael@0 | 6570 | info.code = Buf.readInt32(); |
michael@0 | 6571 | info.index = Buf.readInt32(); |
michael@0 | 6572 | info.type = Buf.readInt32(); |
michael@0 | 6573 | info.number = Buf.readString(); |
michael@0 | 6574 | |
michael@0 | 6575 | this._processSuppSvcNotification(info); |
michael@0 | 6576 | }; |
michael@0 | 6577 | |
michael@0 | 6578 | RilObject.prototype[UNSOLICITED_STK_SESSION_END] = function UNSOLICITED_STK_SESSION_END() { |
michael@0 | 6579 | this.sendChromeMessage({rilMessageType: "stksessionend"}); |
michael@0 | 6580 | }; |
michael@0 | 6581 | RilObject.prototype[UNSOLICITED_STK_PROACTIVE_COMMAND] = function UNSOLICITED_STK_PROACTIVE_COMMAND() { |
michael@0 | 6582 | this.processStkProactiveCommand(); |
michael@0 | 6583 | }; |
michael@0 | 6584 | RilObject.prototype[UNSOLICITED_STK_EVENT_NOTIFY] = function UNSOLICITED_STK_EVENT_NOTIFY() { |
michael@0 | 6585 | this.processStkProactiveCommand(); |
michael@0 | 6586 | }; |
michael@0 | 6587 | RilObject.prototype[UNSOLICITED_STK_CALL_SETUP] = null; |
michael@0 | 6588 | RilObject.prototype[UNSOLICITED_SIM_SMS_STORAGE_FULL] = null; |
michael@0 | 6589 | RilObject.prototype[UNSOLICITED_SIM_REFRESH] = null; |
michael@0 | 6590 | RilObject.prototype[UNSOLICITED_CALL_RING] = function UNSOLICITED_CALL_RING() { |
michael@0 | 6591 | let Buf = this.context.Buf; |
michael@0 | 6592 | let info = {rilMessageType: "callRing"}; |
michael@0 | 6593 | let isCDMA = false; //XXX TODO hard-code this for now |
michael@0 | 6594 | if (isCDMA) { |
michael@0 | 6595 | info.isPresent = Buf.readInt32(); |
michael@0 | 6596 | info.signalType = Buf.readInt32(); |
michael@0 | 6597 | info.alertPitch = Buf.readInt32(); |
michael@0 | 6598 | info.signal = Buf.readInt32(); |
michael@0 | 6599 | } |
michael@0 | 6600 | // At this point we don't know much other than the fact there's an incoming |
michael@0 | 6601 | // call, but that's enough to bring up the Phone app already. We'll know |
michael@0 | 6602 | // details once we get a call state changed notification and can then |
michael@0 | 6603 | // dispatch DOM events etc. |
michael@0 | 6604 | this.sendChromeMessage(info); |
michael@0 | 6605 | }; |
michael@0 | 6606 | RilObject.prototype[UNSOLICITED_RESPONSE_SIM_STATUS_CHANGED] = function UNSOLICITED_RESPONSE_SIM_STATUS_CHANGED() { |
michael@0 | 6607 | this.getICCStatus(); |
michael@0 | 6608 | }; |
michael@0 | 6609 | RilObject.prototype[UNSOLICITED_RESPONSE_CDMA_NEW_SMS] = function UNSOLICITED_RESPONSE_CDMA_NEW_SMS(length) { |
michael@0 | 6610 | let [message, result] = this.context.CdmaPDUHelper.processReceivedSms(length); |
michael@0 | 6611 | |
michael@0 | 6612 | if (message) { |
michael@0 | 6613 | if (message.teleservice === PDU_CDMA_MSG_TELESERIVCIE_ID_WAP) { |
michael@0 | 6614 | result = this._processCdmaSmsWapPush(message); |
michael@0 | 6615 | } else if (message.subMsgType === PDU_CDMA_MSG_TYPE_DELIVER_ACK) { |
michael@0 | 6616 | result = this._processCdmaSmsStatusReport(message); |
michael@0 | 6617 | } else { |
michael@0 | 6618 | result = this._processSmsMultipart(message); |
michael@0 | 6619 | } |
michael@0 | 6620 | } |
michael@0 | 6621 | |
michael@0 | 6622 | if (result == PDU_FCS_RESERVED || result == MOZ_FCS_WAIT_FOR_EXPLICIT_ACK) { |
michael@0 | 6623 | return; |
michael@0 | 6624 | } |
michael@0 | 6625 | |
michael@0 | 6626 | // Not reserved FCS values, send ACK now. |
michael@0 | 6627 | this.acknowledgeCdmaSms(result == PDU_FCS_OK, result); |
michael@0 | 6628 | }; |
michael@0 | 6629 | RilObject.prototype[UNSOLICITED_RESPONSE_NEW_BROADCAST_SMS] = function UNSOLICITED_RESPONSE_NEW_BROADCAST_SMS(length) { |
michael@0 | 6630 | let message; |
michael@0 | 6631 | try { |
michael@0 | 6632 | message = |
michael@0 | 6633 | this.context.GsmPDUHelper.readCbMessage(this.context.Buf.readInt32()); |
michael@0 | 6634 | } catch (e) { |
michael@0 | 6635 | if (DEBUG) { |
michael@0 | 6636 | this.context.debug("Failed to parse Cell Broadcast message: " + |
michael@0 | 6637 | JSON.stringify(e)); |
michael@0 | 6638 | } |
michael@0 | 6639 | return; |
michael@0 | 6640 | } |
michael@0 | 6641 | |
michael@0 | 6642 | message = this._processReceivedSmsCbPage(message); |
michael@0 | 6643 | if (!message) { |
michael@0 | 6644 | return; |
michael@0 | 6645 | } |
michael@0 | 6646 | |
michael@0 | 6647 | message.rilMessageType = "cellbroadcast-received"; |
michael@0 | 6648 | this.sendChromeMessage(message); |
michael@0 | 6649 | }; |
michael@0 | 6650 | RilObject.prototype[UNSOLICITED_CDMA_RUIM_SMS_STORAGE_FULL] = null; |
michael@0 | 6651 | RilObject.prototype[UNSOLICITED_RESTRICTED_STATE_CHANGED] = null; |
michael@0 | 6652 | RilObject.prototype[UNSOLICITED_ENTER_EMERGENCY_CALLBACK_MODE] = function UNSOLICITED_ENTER_EMERGENCY_CALLBACK_MODE() { |
michael@0 | 6653 | this._handleChangedEmergencyCbMode(true); |
michael@0 | 6654 | }; |
michael@0 | 6655 | RilObject.prototype[UNSOLICITED_CDMA_CALL_WAITING] = function UNSOLICITED_CDMA_CALL_WAITING(length) { |
michael@0 | 6656 | let Buf = this.context.Buf; |
michael@0 | 6657 | let call = {}; |
michael@0 | 6658 | call.number = Buf.readString(); |
michael@0 | 6659 | call.numberPresentation = Buf.readInt32(); |
michael@0 | 6660 | call.name = Buf.readString(); |
michael@0 | 6661 | call.namePresentation = Buf.readInt32(); |
michael@0 | 6662 | call.isPresent = Buf.readInt32(); |
michael@0 | 6663 | call.signalType = Buf.readInt32(); |
michael@0 | 6664 | call.alertPitch = Buf.readInt32(); |
michael@0 | 6665 | call.signal = Buf.readInt32(); |
michael@0 | 6666 | this.sendChromeMessage({rilMessageType: "cdmaCallWaiting", |
michael@0 | 6667 | number: call.number}); |
michael@0 | 6668 | }; |
michael@0 | 6669 | RilObject.prototype[UNSOLICITED_CDMA_OTA_PROVISION_STATUS] = function UNSOLICITED_CDMA_OTA_PROVISION_STATUS() { |
michael@0 | 6670 | let status = this.context.Buf.readInt32List()[0]; |
michael@0 | 6671 | this.sendChromeMessage({rilMessageType: "otastatuschange", |
michael@0 | 6672 | status: status}); |
michael@0 | 6673 | }; |
michael@0 | 6674 | RilObject.prototype[UNSOLICITED_CDMA_INFO_REC] = function UNSOLICITED_CDMA_INFO_REC(length) { |
michael@0 | 6675 | let record = this.context.CdmaPDUHelper.decodeInformationRecord(); |
michael@0 | 6676 | record.rilMessageType = "cdma-info-rec-received"; |
michael@0 | 6677 | this.sendChromeMessage(record); |
michael@0 | 6678 | }; |
michael@0 | 6679 | RilObject.prototype[UNSOLICITED_OEM_HOOK_RAW] = null; |
michael@0 | 6680 | RilObject.prototype[UNSOLICITED_RINGBACK_TONE] = null; |
michael@0 | 6681 | RilObject.prototype[UNSOLICITED_RESEND_INCALL_MUTE] = null; |
michael@0 | 6682 | RilObject.prototype[UNSOLICITED_CDMA_SUBSCRIPTION_SOURCE_CHANGED] = null; |
michael@0 | 6683 | RilObject.prototype[UNSOLICITED_CDMA_PRL_CHANGED] = function UNSOLICITED_CDMA_PRL_CHANGED(length) { |
michael@0 | 6684 | let version = this.context.Buf.readInt32List()[0]; |
michael@0 | 6685 | if (version !== this.iccInfo.prlVersion) { |
michael@0 | 6686 | this.iccInfo.prlVersion = version; |
michael@0 | 6687 | this.context.ICCUtilsHelper.handleICCInfoChange(); |
michael@0 | 6688 | } |
michael@0 | 6689 | }; |
michael@0 | 6690 | RilObject.prototype[UNSOLICITED_EXIT_EMERGENCY_CALLBACK_MODE] = function UNSOLICITED_EXIT_EMERGENCY_CALLBACK_MODE() { |
michael@0 | 6691 | this._handleChangedEmergencyCbMode(false); |
michael@0 | 6692 | }; |
michael@0 | 6693 | RilObject.prototype[UNSOLICITED_RIL_CONNECTED] = function UNSOLICITED_RIL_CONNECTED(length) { |
michael@0 | 6694 | // Prevent response id collision between UNSOLICITED_RIL_CONNECTED and |
michael@0 | 6695 | // UNSOLICITED_VOICE_RADIO_TECH_CHANGED for Akami on gingerbread branch. |
michael@0 | 6696 | if (!length) { |
michael@0 | 6697 | return; |
michael@0 | 6698 | } |
michael@0 | 6699 | |
michael@0 | 6700 | let version = this.context.Buf.readInt32List()[0]; |
michael@0 | 6701 | this.v5Legacy = (version < 5); |
michael@0 | 6702 | if (DEBUG) { |
michael@0 | 6703 | this.context.debug("Detected RIL version " + version); |
michael@0 | 6704 | this.context.debug("this.v5Legacy is " + this.v5Legacy); |
michael@0 | 6705 | } |
michael@0 | 6706 | |
michael@0 | 6707 | this.initRILState(); |
michael@0 | 6708 | // Always ensure that we are not in emergency callback mode when init. |
michael@0 | 6709 | this.exitEmergencyCbMode(); |
michael@0 | 6710 | // Reset radio in the case that b2g restart (or crash). |
michael@0 | 6711 | this.setRadioEnabled({enabled: false}); |
michael@0 | 6712 | }; |
michael@0 | 6713 | |
michael@0 | 6714 | /** |
michael@0 | 6715 | * This object exposes the functionality to parse and serialize PDU strings |
michael@0 | 6716 | * |
michael@0 | 6717 | * A PDU is a string containing a series of hexadecimally encoded octets |
michael@0 | 6718 | * or nibble-swapped binary-coded decimals (BCDs). It contains not only the |
michael@0 | 6719 | * message text but information about the sender, the SMS service center, |
michael@0 | 6720 | * timestamp, etc. |
michael@0 | 6721 | */ |
michael@0 | 6722 | function GsmPDUHelperObject(aContext) { |
michael@0 | 6723 | this.context = aContext; |
michael@0 | 6724 | } |
michael@0 | 6725 | GsmPDUHelperObject.prototype = { |
michael@0 | 6726 | context: null, |
michael@0 | 6727 | |
michael@0 | 6728 | /** |
michael@0 | 6729 | * Read one character (2 bytes) from a RIL string and decode as hex. |
michael@0 | 6730 | * |
michael@0 | 6731 | * @return the nibble as a number. |
michael@0 | 6732 | */ |
michael@0 | 6733 | readHexNibble: function() { |
michael@0 | 6734 | let nibble = this.context.Buf.readUint16(); |
michael@0 | 6735 | if (nibble >= 48 && nibble <= 57) { |
michael@0 | 6736 | nibble -= 48; // ASCII '0'..'9' |
michael@0 | 6737 | } else if (nibble >= 65 && nibble <= 70) { |
michael@0 | 6738 | nibble -= 55; // ASCII 'A'..'F' |
michael@0 | 6739 | } else if (nibble >= 97 && nibble <= 102) { |
michael@0 | 6740 | nibble -= 87; // ASCII 'a'..'f' |
michael@0 | 6741 | } else { |
michael@0 | 6742 | throw "Found invalid nibble during PDU parsing: " + |
michael@0 | 6743 | String.fromCharCode(nibble); |
michael@0 | 6744 | } |
michael@0 | 6745 | return nibble; |
michael@0 | 6746 | }, |
michael@0 | 6747 | |
michael@0 | 6748 | /** |
michael@0 | 6749 | * Encode a nibble as one hex character in a RIL string (2 bytes). |
michael@0 | 6750 | * |
michael@0 | 6751 | * @param nibble |
michael@0 | 6752 | * The nibble to encode (represented as a number) |
michael@0 | 6753 | */ |
michael@0 | 6754 | writeHexNibble: function(nibble) { |
michael@0 | 6755 | nibble &= 0x0f; |
michael@0 | 6756 | if (nibble < 10) { |
michael@0 | 6757 | nibble += 48; // ASCII '0' |
michael@0 | 6758 | } else { |
michael@0 | 6759 | nibble += 55; // ASCII 'A' |
michael@0 | 6760 | } |
michael@0 | 6761 | this.context.Buf.writeUint16(nibble); |
michael@0 | 6762 | }, |
michael@0 | 6763 | |
michael@0 | 6764 | /** |
michael@0 | 6765 | * Read a hex-encoded octet (two nibbles). |
michael@0 | 6766 | * |
michael@0 | 6767 | * @return the octet as a number. |
michael@0 | 6768 | */ |
michael@0 | 6769 | readHexOctet: function() { |
michael@0 | 6770 | return (this.readHexNibble() << 4) | this.readHexNibble(); |
michael@0 | 6771 | }, |
michael@0 | 6772 | |
michael@0 | 6773 | /** |
michael@0 | 6774 | * Write an octet as two hex-encoded nibbles. |
michael@0 | 6775 | * |
michael@0 | 6776 | * @param octet |
michael@0 | 6777 | * The octet (represented as a number) to encode. |
michael@0 | 6778 | */ |
michael@0 | 6779 | writeHexOctet: function(octet) { |
michael@0 | 6780 | this.writeHexNibble(octet >> 4); |
michael@0 | 6781 | this.writeHexNibble(octet); |
michael@0 | 6782 | }, |
michael@0 | 6783 | |
michael@0 | 6784 | /** |
michael@0 | 6785 | * Read an array of hex-encoded octets. |
michael@0 | 6786 | */ |
michael@0 | 6787 | readHexOctetArray: function(length) { |
michael@0 | 6788 | let array = new Uint8Array(length); |
michael@0 | 6789 | for (let i = 0; i < length; i++) { |
michael@0 | 6790 | array[i] = this.readHexOctet(); |
michael@0 | 6791 | } |
michael@0 | 6792 | return array; |
michael@0 | 6793 | }, |
michael@0 | 6794 | |
michael@0 | 6795 | /** |
michael@0 | 6796 | * Convert an octet (number) to a BCD number. |
michael@0 | 6797 | * |
michael@0 | 6798 | * Any nibbles that are not in the BCD range count as 0. |
michael@0 | 6799 | * |
michael@0 | 6800 | * @param octet |
michael@0 | 6801 | * The octet (a number, as returned by getOctet()) |
michael@0 | 6802 | * |
michael@0 | 6803 | * @return the corresponding BCD number. |
michael@0 | 6804 | */ |
michael@0 | 6805 | octetToBCD: function(octet) { |
michael@0 | 6806 | return ((octet & 0xf0) <= 0x90) * ((octet >> 4) & 0x0f) + |
michael@0 | 6807 | ((octet & 0x0f) <= 0x09) * (octet & 0x0f) * 10; |
michael@0 | 6808 | }, |
michael@0 | 6809 | |
michael@0 | 6810 | /** |
michael@0 | 6811 | * Convert a BCD number to an octet (number) |
michael@0 | 6812 | * |
michael@0 | 6813 | * Only take two digits with absolute value. |
michael@0 | 6814 | * |
michael@0 | 6815 | * @param bcd |
michael@0 | 6816 | * |
michael@0 | 6817 | * @return the corresponding octet. |
michael@0 | 6818 | */ |
michael@0 | 6819 | BCDToOctet: function(bcd) { |
michael@0 | 6820 | bcd = Math.abs(bcd); |
michael@0 | 6821 | return ((bcd % 10) << 4) + (Math.floor(bcd / 10) % 10); |
michael@0 | 6822 | }, |
michael@0 | 6823 | |
michael@0 | 6824 | /** |
michael@0 | 6825 | * Convert a semi-octet (number) to a GSM BCD char, or return empty string |
michael@0 | 6826 | * if invalid semiOctet and supressException is set to true. |
michael@0 | 6827 | * |
michael@0 | 6828 | * @param semiOctet |
michael@0 | 6829 | * Nibble to be converted to. |
michael@0 | 6830 | * @param [optional] supressException |
michael@0 | 6831 | * Supress exception if invalid semiOctet and supressException is set |
michael@0 | 6832 | * to true. |
michael@0 | 6833 | * |
michael@0 | 6834 | * @return GSM BCD char, or empty string. |
michael@0 | 6835 | */ |
michael@0 | 6836 | bcdChars: "0123456789*#,;", |
michael@0 | 6837 | semiOctetToBcdChar: function(semiOctet, supressException) { |
michael@0 | 6838 | if (semiOctet >= 14) { |
michael@0 | 6839 | if (supressException) { |
michael@0 | 6840 | return ""; |
michael@0 | 6841 | } else { |
michael@0 | 6842 | throw new RangeError(); |
michael@0 | 6843 | } |
michael@0 | 6844 | } |
michael@0 | 6845 | |
michael@0 | 6846 | return this.bcdChars.charAt(semiOctet); |
michael@0 | 6847 | }, |
michael@0 | 6848 | |
michael@0 | 6849 | /** |
michael@0 | 6850 | * Read a *swapped nibble* binary coded decimal (BCD) |
michael@0 | 6851 | * |
michael@0 | 6852 | * @param pairs |
michael@0 | 6853 | * Number of nibble *pairs* to read. |
michael@0 | 6854 | * |
michael@0 | 6855 | * @return the decimal as a number. |
michael@0 | 6856 | */ |
michael@0 | 6857 | readSwappedNibbleBcdNum: function(pairs) { |
michael@0 | 6858 | let number = 0; |
michael@0 | 6859 | for (let i = 0; i < pairs; i++) { |
michael@0 | 6860 | let octet = this.readHexOctet(); |
michael@0 | 6861 | // Ignore 'ff' octets as they're often used as filler. |
michael@0 | 6862 | if (octet == 0xff) { |
michael@0 | 6863 | continue; |
michael@0 | 6864 | } |
michael@0 | 6865 | // If the first nibble is an "F" , only the second nibble is to be taken |
michael@0 | 6866 | // into account. |
michael@0 | 6867 | if ((octet & 0xf0) == 0xf0) { |
michael@0 | 6868 | number *= 10; |
michael@0 | 6869 | number += octet & 0x0f; |
michael@0 | 6870 | continue; |
michael@0 | 6871 | } |
michael@0 | 6872 | number *= 100; |
michael@0 | 6873 | number += this.octetToBCD(octet); |
michael@0 | 6874 | } |
michael@0 | 6875 | return number; |
michael@0 | 6876 | }, |
michael@0 | 6877 | |
michael@0 | 6878 | /** |
michael@0 | 6879 | * Read a *swapped nibble* binary coded string (BCD) |
michael@0 | 6880 | * |
michael@0 | 6881 | * @param pairs |
michael@0 | 6882 | * Number of nibble *pairs* to read. |
michael@0 | 6883 | * @param [optional] supressException |
michael@0 | 6884 | * Supress exception if invalid semiOctet and supressException is set |
michael@0 | 6885 | * to true. |
michael@0 | 6886 | * |
michael@0 | 6887 | * @return The BCD string. |
michael@0 | 6888 | */ |
michael@0 | 6889 | readSwappedNibbleBcdString: function(pairs, supressException) { |
michael@0 | 6890 | let str = ""; |
michael@0 | 6891 | for (let i = 0; i < pairs; i++) { |
michael@0 | 6892 | let nibbleH = this.readHexNibble(); |
michael@0 | 6893 | let nibbleL = this.readHexNibble(); |
michael@0 | 6894 | if (nibbleL == 0x0F) { |
michael@0 | 6895 | break; |
michael@0 | 6896 | } |
michael@0 | 6897 | |
michael@0 | 6898 | str += this.semiOctetToBcdChar(nibbleL, supressException); |
michael@0 | 6899 | if (nibbleH != 0x0F) { |
michael@0 | 6900 | str += this.semiOctetToBcdChar(nibbleH, supressException); |
michael@0 | 6901 | } |
michael@0 | 6902 | } |
michael@0 | 6903 | |
michael@0 | 6904 | return str; |
michael@0 | 6905 | }, |
michael@0 | 6906 | |
michael@0 | 6907 | /** |
michael@0 | 6908 | * Write numerical data as swapped nibble BCD. |
michael@0 | 6909 | * |
michael@0 | 6910 | * @param data |
michael@0 | 6911 | * Data to write (as a string or a number) |
michael@0 | 6912 | */ |
michael@0 | 6913 | writeSwappedNibbleBCD: function(data) { |
michael@0 | 6914 | data = data.toString(); |
michael@0 | 6915 | if (data.length % 2) { |
michael@0 | 6916 | data += "F"; |
michael@0 | 6917 | } |
michael@0 | 6918 | let Buf = this.context.Buf; |
michael@0 | 6919 | for (let i = 0; i < data.length; i += 2) { |
michael@0 | 6920 | Buf.writeUint16(data.charCodeAt(i + 1)); |
michael@0 | 6921 | Buf.writeUint16(data.charCodeAt(i)); |
michael@0 | 6922 | } |
michael@0 | 6923 | }, |
michael@0 | 6924 | |
michael@0 | 6925 | /** |
michael@0 | 6926 | * Write numerical data as swapped nibble BCD. |
michael@0 | 6927 | * If the number of digit of data is even, add '0' at the beginning. |
michael@0 | 6928 | * |
michael@0 | 6929 | * @param data |
michael@0 | 6930 | * Data to write (as a string or a number) |
michael@0 | 6931 | */ |
michael@0 | 6932 | writeSwappedNibbleBCDNum: function(data) { |
michael@0 | 6933 | data = data.toString(); |
michael@0 | 6934 | if (data.length % 2) { |
michael@0 | 6935 | data = "0" + data; |
michael@0 | 6936 | } |
michael@0 | 6937 | let Buf = this.context.Buf; |
michael@0 | 6938 | for (let i = 0; i < data.length; i += 2) { |
michael@0 | 6939 | Buf.writeUint16(data.charCodeAt(i + 1)); |
michael@0 | 6940 | Buf.writeUint16(data.charCodeAt(i)); |
michael@0 | 6941 | } |
michael@0 | 6942 | }, |
michael@0 | 6943 | |
michael@0 | 6944 | /** |
michael@0 | 6945 | * Read user data, convert to septets, look up relevant characters in a |
michael@0 | 6946 | * 7-bit alphabet, and construct string. |
michael@0 | 6947 | * |
michael@0 | 6948 | * @param length |
michael@0 | 6949 | * Number of septets to read (*not* octets) |
michael@0 | 6950 | * @param paddingBits |
michael@0 | 6951 | * Number of padding bits in the first byte of user data. |
michael@0 | 6952 | * @param langIndex |
michael@0 | 6953 | * Table index used for normal 7-bit encoded character lookup. |
michael@0 | 6954 | * @param langShiftIndex |
michael@0 | 6955 | * Table index used for escaped 7-bit encoded character lookup. |
michael@0 | 6956 | * |
michael@0 | 6957 | * @return a string. |
michael@0 | 6958 | */ |
michael@0 | 6959 | readSeptetsToString: function(length, paddingBits, langIndex, langShiftIndex) { |
michael@0 | 6960 | let ret = ""; |
michael@0 | 6961 | let byteLength = Math.ceil((length * 7 + paddingBits) / 8); |
michael@0 | 6962 | |
michael@0 | 6963 | /** |
michael@0 | 6964 | * |<- last byte in header ->| |
michael@0 | 6965 | * |<- incompleteBits ->|<- last header septet->| |
michael@0 | 6966 | * +===7===|===6===|===5===|===4===|===3===|===2===|===1===|===0===| |
michael@0 | 6967 | * |
michael@0 | 6968 | * |<- 1st byte in user data ->| |
michael@0 | 6969 | * |<- data septet 1 ->|<-paddingBits->| |
michael@0 | 6970 | * +===7===|===6===|===5===|===4===|===3===|===2===|===1===|===0===| |
michael@0 | 6971 | * |
michael@0 | 6972 | * |<- 2nd byte in user data ->| |
michael@0 | 6973 | * |<- data spetet 2 ->|<-ds1->| |
michael@0 | 6974 | * +===7===|===6===|===5===|===4===|===3===|===2===|===1===|===0===| |
michael@0 | 6975 | */ |
michael@0 | 6976 | let data = 0; |
michael@0 | 6977 | let dataBits = 0; |
michael@0 | 6978 | if (paddingBits) { |
michael@0 | 6979 | data = this.readHexOctet() >> paddingBits; |
michael@0 | 6980 | dataBits = 8 - paddingBits; |
michael@0 | 6981 | --byteLength; |
michael@0 | 6982 | } |
michael@0 | 6983 | |
michael@0 | 6984 | let escapeFound = false; |
michael@0 | 6985 | const langTable = PDU_NL_LOCKING_SHIFT_TABLES[langIndex]; |
michael@0 | 6986 | const langShiftTable = PDU_NL_SINGLE_SHIFT_TABLES[langShiftIndex]; |
michael@0 | 6987 | do { |
michael@0 | 6988 | // Read as much as fits in 32bit word |
michael@0 | 6989 | let bytesToRead = Math.min(byteLength, dataBits ? 3 : 4); |
michael@0 | 6990 | for (let i = 0; i < bytesToRead; i++) { |
michael@0 | 6991 | data |= this.readHexOctet() << dataBits; |
michael@0 | 6992 | dataBits += 8; |
michael@0 | 6993 | --byteLength; |
michael@0 | 6994 | } |
michael@0 | 6995 | |
michael@0 | 6996 | // Consume available full septets |
michael@0 | 6997 | for (; dataBits >= 7; dataBits -= 7) { |
michael@0 | 6998 | let septet = data & 0x7F; |
michael@0 | 6999 | data >>>= 7; |
michael@0 | 7000 | |
michael@0 | 7001 | if (escapeFound) { |
michael@0 | 7002 | escapeFound = false; |
michael@0 | 7003 | if (septet == PDU_NL_EXTENDED_ESCAPE) { |
michael@0 | 7004 | // According to 3GPP TS 23.038, section 6.2.1.1, NOTE 1, "On |
michael@0 | 7005 | // receipt of this code, a receiving entity shall display a space |
michael@0 | 7006 | // until another extensiion table is defined." |
michael@0 | 7007 | ret += " "; |
michael@0 | 7008 | } else if (septet == PDU_NL_RESERVED_CONTROL) { |
michael@0 | 7009 | // According to 3GPP TS 23.038 B.2, "This code represents a control |
michael@0 | 7010 | // character and therefore must not be used for language specific |
michael@0 | 7011 | // characters." |
michael@0 | 7012 | ret += " "; |
michael@0 | 7013 | } else { |
michael@0 | 7014 | ret += langShiftTable[septet]; |
michael@0 | 7015 | } |
michael@0 | 7016 | } else if (septet == PDU_NL_EXTENDED_ESCAPE) { |
michael@0 | 7017 | escapeFound = true; |
michael@0 | 7018 | |
michael@0 | 7019 | // <escape> is not an effective character |
michael@0 | 7020 | --length; |
michael@0 | 7021 | } else { |
michael@0 | 7022 | ret += langTable[septet]; |
michael@0 | 7023 | } |
michael@0 | 7024 | } |
michael@0 | 7025 | } while (byteLength); |
michael@0 | 7026 | |
michael@0 | 7027 | if (ret.length != length) { |
michael@0 | 7028 | /** |
michael@0 | 7029 | * If num of effective characters does not equal to the length of read |
michael@0 | 7030 | * string, cut the tail off. This happens when the last octet of user |
michael@0 | 7031 | * data has following layout: |
michael@0 | 7032 | * |
michael@0 | 7033 | * |<- penultimate octet in user data ->| |
michael@0 | 7034 | * |<- data septet N ->|<- dsN-1 ->| |
michael@0 | 7035 | * +===7===|===6===|===5===|===4===|===3===|===2===|===1===|===0===| |
michael@0 | 7036 | * |
michael@0 | 7037 | * |<- last octet in user data ->| |
michael@0 | 7038 | * |<- fill bits ->|<-dsN->| |
michael@0 | 7039 | * +===7===|===6===|===5===|===4===|===3===|===2===|===1===|===0===| |
michael@0 | 7040 | * |
michael@0 | 7041 | * The fill bits in the last octet may happen to form a full septet and |
michael@0 | 7042 | * be appended at the end of result string. |
michael@0 | 7043 | */ |
michael@0 | 7044 | ret = ret.slice(0, length); |
michael@0 | 7045 | } |
michael@0 | 7046 | return ret; |
michael@0 | 7047 | }, |
michael@0 | 7048 | |
michael@0 | 7049 | writeStringAsSeptets: function(message, paddingBits, langIndex, langShiftIndex) { |
michael@0 | 7050 | const langTable = PDU_NL_LOCKING_SHIFT_TABLES[langIndex]; |
michael@0 | 7051 | const langShiftTable = PDU_NL_SINGLE_SHIFT_TABLES[langShiftIndex]; |
michael@0 | 7052 | |
michael@0 | 7053 | let dataBits = paddingBits; |
michael@0 | 7054 | let data = 0; |
michael@0 | 7055 | for (let i = 0; i < message.length; i++) { |
michael@0 | 7056 | let c = message.charAt(i); |
michael@0 | 7057 | let septet = langTable.indexOf(c); |
michael@0 | 7058 | if (septet == PDU_NL_EXTENDED_ESCAPE) { |
michael@0 | 7059 | continue; |
michael@0 | 7060 | } |
michael@0 | 7061 | |
michael@0 | 7062 | if (septet >= 0) { |
michael@0 | 7063 | data |= septet << dataBits; |
michael@0 | 7064 | dataBits += 7; |
michael@0 | 7065 | } else { |
michael@0 | 7066 | septet = langShiftTable.indexOf(c); |
michael@0 | 7067 | if (septet == -1) { |
michael@0 | 7068 | throw new Error("'" + c + "' is not in 7 bit alphabet " |
michael@0 | 7069 | + langIndex + ":" + langShiftIndex + "!"); |
michael@0 | 7070 | } |
michael@0 | 7071 | |
michael@0 | 7072 | if (septet == PDU_NL_RESERVED_CONTROL) { |
michael@0 | 7073 | continue; |
michael@0 | 7074 | } |
michael@0 | 7075 | |
michael@0 | 7076 | data |= PDU_NL_EXTENDED_ESCAPE << dataBits; |
michael@0 | 7077 | dataBits += 7; |
michael@0 | 7078 | data |= septet << dataBits; |
michael@0 | 7079 | dataBits += 7; |
michael@0 | 7080 | } |
michael@0 | 7081 | |
michael@0 | 7082 | for (; dataBits >= 8; dataBits -= 8) { |
michael@0 | 7083 | this.writeHexOctet(data & 0xFF); |
michael@0 | 7084 | data >>>= 8; |
michael@0 | 7085 | } |
michael@0 | 7086 | } |
michael@0 | 7087 | |
michael@0 | 7088 | if (dataBits !== 0) { |
michael@0 | 7089 | this.writeHexOctet(data & 0xFF); |
michael@0 | 7090 | } |
michael@0 | 7091 | }, |
michael@0 | 7092 | |
michael@0 | 7093 | /** |
michael@0 | 7094 | * Read user data and decode as a UCS2 string. |
michael@0 | 7095 | * |
michael@0 | 7096 | * @param numOctets |
michael@0 | 7097 | * Number of octets to be read as UCS2 string. |
michael@0 | 7098 | * |
michael@0 | 7099 | * @return a string. |
michael@0 | 7100 | */ |
michael@0 | 7101 | readUCS2String: function(numOctets) { |
michael@0 | 7102 | let str = ""; |
michael@0 | 7103 | let length = numOctets / 2; |
michael@0 | 7104 | for (let i = 0; i < length; ++i) { |
michael@0 | 7105 | let code = (this.readHexOctet() << 8) | this.readHexOctet(); |
michael@0 | 7106 | str += String.fromCharCode(code); |
michael@0 | 7107 | } |
michael@0 | 7108 | |
michael@0 | 7109 | if (DEBUG) this.context.debug("Read UCS2 string: " + str); |
michael@0 | 7110 | |
michael@0 | 7111 | return str; |
michael@0 | 7112 | }, |
michael@0 | 7113 | |
michael@0 | 7114 | /** |
michael@0 | 7115 | * Write user data as a UCS2 string. |
michael@0 | 7116 | * |
michael@0 | 7117 | * @param message |
michael@0 | 7118 | * Message string to encode as UCS2 in hex-encoded octets. |
michael@0 | 7119 | */ |
michael@0 | 7120 | writeUCS2String: function(message) { |
michael@0 | 7121 | for (let i = 0; i < message.length; ++i) { |
michael@0 | 7122 | let code = message.charCodeAt(i); |
michael@0 | 7123 | this.writeHexOctet((code >> 8) & 0xFF); |
michael@0 | 7124 | this.writeHexOctet(code & 0xFF); |
michael@0 | 7125 | } |
michael@0 | 7126 | }, |
michael@0 | 7127 | |
michael@0 | 7128 | /** |
michael@0 | 7129 | * Read 1 + UDHL octets and construct user data header. |
michael@0 | 7130 | * |
michael@0 | 7131 | * @param msg |
michael@0 | 7132 | * message object for output. |
michael@0 | 7133 | * |
michael@0 | 7134 | * @see 3GPP TS 23.040 9.2.3.24 |
michael@0 | 7135 | */ |
michael@0 | 7136 | readUserDataHeader: function(msg) { |
michael@0 | 7137 | /** |
michael@0 | 7138 | * A header object with properties contained in received message. |
michael@0 | 7139 | * The properties set include: |
michael@0 | 7140 | * |
michael@0 | 7141 | * length: totoal length of the header, default 0. |
michael@0 | 7142 | * langIndex: used locking shift table index, default |
michael@0 | 7143 | * PDU_NL_IDENTIFIER_DEFAULT. |
michael@0 | 7144 | * langShiftIndex: used locking shift table index, default |
michael@0 | 7145 | * PDU_NL_IDENTIFIER_DEFAULT. |
michael@0 | 7146 | * |
michael@0 | 7147 | */ |
michael@0 | 7148 | let header = { |
michael@0 | 7149 | length: 0, |
michael@0 | 7150 | langIndex: PDU_NL_IDENTIFIER_DEFAULT, |
michael@0 | 7151 | langShiftIndex: PDU_NL_IDENTIFIER_DEFAULT |
michael@0 | 7152 | }; |
michael@0 | 7153 | |
michael@0 | 7154 | header.length = this.readHexOctet(); |
michael@0 | 7155 | if (DEBUG) this.context.debug("Read UDH length: " + header.length); |
michael@0 | 7156 | |
michael@0 | 7157 | let dataAvailable = header.length; |
michael@0 | 7158 | while (dataAvailable >= 2) { |
michael@0 | 7159 | let id = this.readHexOctet(); |
michael@0 | 7160 | let length = this.readHexOctet(); |
michael@0 | 7161 | if (DEBUG) this.context.debug("Read UDH id: " + id + ", length: " + length); |
michael@0 | 7162 | |
michael@0 | 7163 | dataAvailable -= 2; |
michael@0 | 7164 | |
michael@0 | 7165 | switch (id) { |
michael@0 | 7166 | case PDU_IEI_CONCATENATED_SHORT_MESSAGES_8BIT: { |
michael@0 | 7167 | let ref = this.readHexOctet(); |
michael@0 | 7168 | let max = this.readHexOctet(); |
michael@0 | 7169 | let seq = this.readHexOctet(); |
michael@0 | 7170 | dataAvailable -= 3; |
michael@0 | 7171 | if (max && seq && (seq <= max)) { |
michael@0 | 7172 | header.segmentRef = ref; |
michael@0 | 7173 | header.segmentMaxSeq = max; |
michael@0 | 7174 | header.segmentSeq = seq; |
michael@0 | 7175 | } |
michael@0 | 7176 | break; |
michael@0 | 7177 | } |
michael@0 | 7178 | case PDU_IEI_APPLICATION_PORT_ADDRESSING_SCHEME_8BIT: { |
michael@0 | 7179 | let dstp = this.readHexOctet(); |
michael@0 | 7180 | let orip = this.readHexOctet(); |
michael@0 | 7181 | dataAvailable -= 2; |
michael@0 | 7182 | if ((dstp < PDU_APA_RESERVED_8BIT_PORTS) |
michael@0 | 7183 | || (orip < PDU_APA_RESERVED_8BIT_PORTS)) { |
michael@0 | 7184 | // 3GPP TS 23.040 clause 9.2.3.24.3: "A receiving entity shall |
michael@0 | 7185 | // ignore any information element where the value of the |
michael@0 | 7186 | // Information-Element-Data is Reserved or not supported" |
michael@0 | 7187 | break; |
michael@0 | 7188 | } |
michael@0 | 7189 | header.destinationPort = dstp; |
michael@0 | 7190 | header.originatorPort = orip; |
michael@0 | 7191 | break; |
michael@0 | 7192 | } |
michael@0 | 7193 | case PDU_IEI_APPLICATION_PORT_ADDRESSING_SCHEME_16BIT: { |
michael@0 | 7194 | let dstp = (this.readHexOctet() << 8) | this.readHexOctet(); |
michael@0 | 7195 | let orip = (this.readHexOctet() << 8) | this.readHexOctet(); |
michael@0 | 7196 | dataAvailable -= 4; |
michael@0 | 7197 | // 3GPP TS 23.040 clause 9.2.3.24.4: "A receiving entity shall |
michael@0 | 7198 | // ignore any information element where the value of the |
michael@0 | 7199 | // Information-Element-Data is Reserved or not supported" |
michael@0 | 7200 | if ((dstp < PDU_APA_VALID_16BIT_PORTS) |
michael@0 | 7201 | && (orip < PDU_APA_VALID_16BIT_PORTS)) { |
michael@0 | 7202 | header.destinationPort = dstp; |
michael@0 | 7203 | header.originatorPort = orip; |
michael@0 | 7204 | } |
michael@0 | 7205 | break; |
michael@0 | 7206 | } |
michael@0 | 7207 | case PDU_IEI_CONCATENATED_SHORT_MESSAGES_16BIT: { |
michael@0 | 7208 | let ref = (this.readHexOctet() << 8) | this.readHexOctet(); |
michael@0 | 7209 | let max = this.readHexOctet(); |
michael@0 | 7210 | let seq = this.readHexOctet(); |
michael@0 | 7211 | dataAvailable -= 4; |
michael@0 | 7212 | if (max && seq && (seq <= max)) { |
michael@0 | 7213 | header.segmentRef = ref; |
michael@0 | 7214 | header.segmentMaxSeq = max; |
michael@0 | 7215 | header.segmentSeq = seq; |
michael@0 | 7216 | } |
michael@0 | 7217 | break; |
michael@0 | 7218 | } |
michael@0 | 7219 | case PDU_IEI_NATIONAL_LANGUAGE_SINGLE_SHIFT: |
michael@0 | 7220 | let langShiftIndex = this.readHexOctet(); |
michael@0 | 7221 | --dataAvailable; |
michael@0 | 7222 | if (langShiftIndex < PDU_NL_SINGLE_SHIFT_TABLES.length) { |
michael@0 | 7223 | header.langShiftIndex = langShiftIndex; |
michael@0 | 7224 | } |
michael@0 | 7225 | break; |
michael@0 | 7226 | case PDU_IEI_NATIONAL_LANGUAGE_LOCKING_SHIFT: |
michael@0 | 7227 | let langIndex = this.readHexOctet(); |
michael@0 | 7228 | --dataAvailable; |
michael@0 | 7229 | if (langIndex < PDU_NL_LOCKING_SHIFT_TABLES.length) { |
michael@0 | 7230 | header.langIndex = langIndex; |
michael@0 | 7231 | } |
michael@0 | 7232 | break; |
michael@0 | 7233 | case PDU_IEI_SPECIAL_SMS_MESSAGE_INDICATION: |
michael@0 | 7234 | let msgInd = this.readHexOctet() & 0xFF; |
michael@0 | 7235 | let msgCount = this.readHexOctet(); |
michael@0 | 7236 | dataAvailable -= 2; |
michael@0 | 7237 | |
michael@0 | 7238 | |
michael@0 | 7239 | /* |
michael@0 | 7240 | * TS 23.040 V6.8.1 Sec 9.2.3.24.2 |
michael@0 | 7241 | * bits 1 0 : basic message indication type |
michael@0 | 7242 | * bits 4 3 2 : extended message indication type |
michael@0 | 7243 | * bits 6 5 : Profile id |
michael@0 | 7244 | * bit 7 : storage type |
michael@0 | 7245 | */ |
michael@0 | 7246 | let storeType = msgInd & PDU_MWI_STORE_TYPE_BIT; |
michael@0 | 7247 | let mwi = msg.mwi; |
michael@0 | 7248 | if (!mwi) { |
michael@0 | 7249 | mwi = msg.mwi = {}; |
michael@0 | 7250 | } |
michael@0 | 7251 | |
michael@0 | 7252 | if (storeType == PDU_MWI_STORE_TYPE_STORE) { |
michael@0 | 7253 | // Store message because TP_UDH indicates so, note this may override |
michael@0 | 7254 | // the setting in DCS, but that is expected |
michael@0 | 7255 | mwi.discard = false; |
michael@0 | 7256 | } else if (mwi.discard === undefined) { |
michael@0 | 7257 | // storeType == PDU_MWI_STORE_TYPE_DISCARD |
michael@0 | 7258 | // only override mwi.discard here if it hasn't already been set |
michael@0 | 7259 | mwi.discard = true; |
michael@0 | 7260 | } |
michael@0 | 7261 | |
michael@0 | 7262 | mwi.msgCount = msgCount & 0xFF; |
michael@0 | 7263 | mwi.active = mwi.msgCount > 0; |
michael@0 | 7264 | |
michael@0 | 7265 | if (DEBUG) { |
michael@0 | 7266 | this.context.debug("MWI in TP_UDH received: " + JSON.stringify(mwi)); |
michael@0 | 7267 | } |
michael@0 | 7268 | |
michael@0 | 7269 | break; |
michael@0 | 7270 | default: |
michael@0 | 7271 | if (DEBUG) { |
michael@0 | 7272 | this.context.debug("readUserDataHeader: unsupported IEI(" + id + |
michael@0 | 7273 | "), " + length + " bytes."); |
michael@0 | 7274 | } |
michael@0 | 7275 | |
michael@0 | 7276 | // Read out unsupported data |
michael@0 | 7277 | if (length) { |
michael@0 | 7278 | let octets; |
michael@0 | 7279 | if (DEBUG) octets = new Uint8Array(length); |
michael@0 | 7280 | |
michael@0 | 7281 | for (let i = 0; i < length; i++) { |
michael@0 | 7282 | let octet = this.readHexOctet(); |
michael@0 | 7283 | if (DEBUG) octets[i] = octet; |
michael@0 | 7284 | } |
michael@0 | 7285 | dataAvailable -= length; |
michael@0 | 7286 | |
michael@0 | 7287 | if (DEBUG) { |
michael@0 | 7288 | this.context.debug("readUserDataHeader: " + Array.slice(octets)); |
michael@0 | 7289 | } |
michael@0 | 7290 | } |
michael@0 | 7291 | break; |
michael@0 | 7292 | } |
michael@0 | 7293 | } |
michael@0 | 7294 | |
michael@0 | 7295 | if (dataAvailable !== 0) { |
michael@0 | 7296 | throw new Error("Illegal user data header found!"); |
michael@0 | 7297 | } |
michael@0 | 7298 | |
michael@0 | 7299 | msg.header = header; |
michael@0 | 7300 | }, |
michael@0 | 7301 | |
michael@0 | 7302 | /** |
michael@0 | 7303 | * Write out user data header. |
michael@0 | 7304 | * |
michael@0 | 7305 | * @param options |
michael@0 | 7306 | * Options containing information for user data header write-out. The |
michael@0 | 7307 | * `userDataHeaderLength` property must be correctly pre-calculated. |
michael@0 | 7308 | */ |
michael@0 | 7309 | writeUserDataHeader: function(options) { |
michael@0 | 7310 | this.writeHexOctet(options.userDataHeaderLength); |
michael@0 | 7311 | |
michael@0 | 7312 | if (options.segmentMaxSeq > 1) { |
michael@0 | 7313 | if (options.segmentRef16Bit) { |
michael@0 | 7314 | this.writeHexOctet(PDU_IEI_CONCATENATED_SHORT_MESSAGES_16BIT); |
michael@0 | 7315 | this.writeHexOctet(4); |
michael@0 | 7316 | this.writeHexOctet((options.segmentRef >> 8) & 0xFF); |
michael@0 | 7317 | } else { |
michael@0 | 7318 | this.writeHexOctet(PDU_IEI_CONCATENATED_SHORT_MESSAGES_8BIT); |
michael@0 | 7319 | this.writeHexOctet(3); |
michael@0 | 7320 | } |
michael@0 | 7321 | this.writeHexOctet(options.segmentRef & 0xFF); |
michael@0 | 7322 | this.writeHexOctet(options.segmentMaxSeq & 0xFF); |
michael@0 | 7323 | this.writeHexOctet(options.segmentSeq & 0xFF); |
michael@0 | 7324 | } |
michael@0 | 7325 | |
michael@0 | 7326 | if (options.dcs == PDU_DCS_MSG_CODING_7BITS_ALPHABET) { |
michael@0 | 7327 | if (options.langIndex != PDU_NL_IDENTIFIER_DEFAULT) { |
michael@0 | 7328 | this.writeHexOctet(PDU_IEI_NATIONAL_LANGUAGE_LOCKING_SHIFT); |
michael@0 | 7329 | this.writeHexOctet(1); |
michael@0 | 7330 | this.writeHexOctet(options.langIndex); |
michael@0 | 7331 | } |
michael@0 | 7332 | |
michael@0 | 7333 | if (options.langShiftIndex != PDU_NL_IDENTIFIER_DEFAULT) { |
michael@0 | 7334 | this.writeHexOctet(PDU_IEI_NATIONAL_LANGUAGE_SINGLE_SHIFT); |
michael@0 | 7335 | this.writeHexOctet(1); |
michael@0 | 7336 | this.writeHexOctet(options.langShiftIndex); |
michael@0 | 7337 | } |
michael@0 | 7338 | } |
michael@0 | 7339 | }, |
michael@0 | 7340 | |
michael@0 | 7341 | /** |
michael@0 | 7342 | * Read SM-TL Address. |
michael@0 | 7343 | * |
michael@0 | 7344 | * @param len |
michael@0 | 7345 | * Length of useful semi-octets within the Address-Value field. For |
michael@0 | 7346 | * example, the lenth of "12345" should be 5, and 4 for "1234". |
michael@0 | 7347 | * |
michael@0 | 7348 | * @see 3GPP TS 23.040 9.1.2.5 |
michael@0 | 7349 | */ |
michael@0 | 7350 | readAddress: function(len) { |
michael@0 | 7351 | // Address Length |
michael@0 | 7352 | if (!len || (len < 0)) { |
michael@0 | 7353 | if (DEBUG) { |
michael@0 | 7354 | this.context.debug("PDU error: invalid sender address length: " + len); |
michael@0 | 7355 | } |
michael@0 | 7356 | return null; |
michael@0 | 7357 | } |
michael@0 | 7358 | if (len % 2 == 1) { |
michael@0 | 7359 | len += 1; |
michael@0 | 7360 | } |
michael@0 | 7361 | if (DEBUG) this.context.debug("PDU: Going to read address: " + len); |
michael@0 | 7362 | |
michael@0 | 7363 | // Type-of-Address |
michael@0 | 7364 | let toa = this.readHexOctet(); |
michael@0 | 7365 | let addr = ""; |
michael@0 | 7366 | |
michael@0 | 7367 | if ((toa & 0xF0) == PDU_TOA_ALPHANUMERIC) { |
michael@0 | 7368 | addr = this.readSeptetsToString(Math.floor(len * 4 / 7), 0, |
michael@0 | 7369 | PDU_NL_IDENTIFIER_DEFAULT , PDU_NL_IDENTIFIER_DEFAULT ); |
michael@0 | 7370 | return addr; |
michael@0 | 7371 | } |
michael@0 | 7372 | addr = this.readSwappedNibbleBcdString(len / 2); |
michael@0 | 7373 | if (addr.length <= 0) { |
michael@0 | 7374 | if (DEBUG) this.context.debug("PDU error: no number provided"); |
michael@0 | 7375 | return null; |
michael@0 | 7376 | } |
michael@0 | 7377 | if ((toa & 0xF0) == (PDU_TOA_INTERNATIONAL)) { |
michael@0 | 7378 | addr = '+' + addr; |
michael@0 | 7379 | } |
michael@0 | 7380 | |
michael@0 | 7381 | return addr; |
michael@0 | 7382 | }, |
michael@0 | 7383 | |
michael@0 | 7384 | /** |
michael@0 | 7385 | * Read TP-Protocol-Indicator(TP-PID). |
michael@0 | 7386 | * |
michael@0 | 7387 | * @param msg |
michael@0 | 7388 | * message object for output. |
michael@0 | 7389 | * |
michael@0 | 7390 | * @see 3GPP TS 23.040 9.2.3.9 |
michael@0 | 7391 | */ |
michael@0 | 7392 | readProtocolIndicator: function(msg) { |
michael@0 | 7393 | // `The MS shall interpret reserved, obsolete, or unsupported values as the |
michael@0 | 7394 | // value 00000000 but shall store them exactly as received.` |
michael@0 | 7395 | msg.pid = this.readHexOctet(); |
michael@0 | 7396 | |
michael@0 | 7397 | msg.epid = msg.pid; |
michael@0 | 7398 | switch (msg.epid & 0xC0) { |
michael@0 | 7399 | case 0x40: |
michael@0 | 7400 | // Bit 7..0 = 01xxxxxx |
michael@0 | 7401 | switch (msg.epid) { |
michael@0 | 7402 | case PDU_PID_SHORT_MESSAGE_TYPE_0: |
michael@0 | 7403 | case PDU_PID_ANSI_136_R_DATA: |
michael@0 | 7404 | case PDU_PID_USIM_DATA_DOWNLOAD: |
michael@0 | 7405 | return; |
michael@0 | 7406 | } |
michael@0 | 7407 | break; |
michael@0 | 7408 | } |
michael@0 | 7409 | |
michael@0 | 7410 | msg.epid = PDU_PID_DEFAULT; |
michael@0 | 7411 | }, |
michael@0 | 7412 | |
michael@0 | 7413 | /** |
michael@0 | 7414 | * Read TP-Data-Coding-Scheme(TP-DCS) |
michael@0 | 7415 | * |
michael@0 | 7416 | * @param msg |
michael@0 | 7417 | * message object for output. |
michael@0 | 7418 | * |
michael@0 | 7419 | * @see 3GPP TS 23.040 9.2.3.10, 3GPP TS 23.038 4. |
michael@0 | 7420 | */ |
michael@0 | 7421 | readDataCodingScheme: function(msg) { |
michael@0 | 7422 | let dcs = this.readHexOctet(); |
michael@0 | 7423 | if (DEBUG) this.context.debug("PDU: read SMS dcs: " + dcs); |
michael@0 | 7424 | |
michael@0 | 7425 | // No message class by default. |
michael@0 | 7426 | let messageClass = PDU_DCS_MSG_CLASS_NORMAL; |
michael@0 | 7427 | // 7 bit is the default fallback encoding. |
michael@0 | 7428 | let encoding = PDU_DCS_MSG_CODING_7BITS_ALPHABET; |
michael@0 | 7429 | switch (dcs & PDU_DCS_CODING_GROUP_BITS) { |
michael@0 | 7430 | case 0x40: // bits 7..4 = 01xx |
michael@0 | 7431 | case 0x50: |
michael@0 | 7432 | case 0x60: |
michael@0 | 7433 | case 0x70: |
michael@0 | 7434 | // Bit 5..0 are coded exactly the same as Group 00xx |
michael@0 | 7435 | case 0x00: // bits 7..4 = 00xx |
michael@0 | 7436 | case 0x10: |
michael@0 | 7437 | case 0x20: |
michael@0 | 7438 | case 0x30: |
michael@0 | 7439 | if (dcs & 0x10) { |
michael@0 | 7440 | messageClass = dcs & PDU_DCS_MSG_CLASS_BITS; |
michael@0 | 7441 | } |
michael@0 | 7442 | switch (dcs & 0x0C) { |
michael@0 | 7443 | case 0x4: |
michael@0 | 7444 | encoding = PDU_DCS_MSG_CODING_8BITS_ALPHABET; |
michael@0 | 7445 | break; |
michael@0 | 7446 | case 0x8: |
michael@0 | 7447 | encoding = PDU_DCS_MSG_CODING_16BITS_ALPHABET; |
michael@0 | 7448 | break; |
michael@0 | 7449 | } |
michael@0 | 7450 | break; |
michael@0 | 7451 | |
michael@0 | 7452 | case 0xE0: // bits 7..4 = 1110 |
michael@0 | 7453 | encoding = PDU_DCS_MSG_CODING_16BITS_ALPHABET; |
michael@0 | 7454 | // Bit 3..0 are coded exactly the same as Message Waiting Indication |
michael@0 | 7455 | // Group 1101. |
michael@0 | 7456 | // Fall through. |
michael@0 | 7457 | case 0xC0: // bits 7..4 = 1100 |
michael@0 | 7458 | case 0xD0: // bits 7..4 = 1101 |
michael@0 | 7459 | // Indiciates voicemail indicator set or clear |
michael@0 | 7460 | let active = (dcs & PDU_DCS_MWI_ACTIVE_BITS) == PDU_DCS_MWI_ACTIVE_VALUE; |
michael@0 | 7461 | |
michael@0 | 7462 | // If TP-UDH is present, these values will be overwritten |
michael@0 | 7463 | switch (dcs & PDU_DCS_MWI_TYPE_BITS) { |
michael@0 | 7464 | case PDU_DCS_MWI_TYPE_VOICEMAIL: |
michael@0 | 7465 | let mwi = msg.mwi; |
michael@0 | 7466 | if (!mwi) { |
michael@0 | 7467 | mwi = msg.mwi = {}; |
michael@0 | 7468 | } |
michael@0 | 7469 | |
michael@0 | 7470 | mwi.active = active; |
michael@0 | 7471 | mwi.discard = (dcs & PDU_DCS_CODING_GROUP_BITS) == 0xC0; |
michael@0 | 7472 | mwi.msgCount = active ? GECKO_VOICEMAIL_MESSAGE_COUNT_UNKNOWN : 0; |
michael@0 | 7473 | |
michael@0 | 7474 | if (DEBUG) { |
michael@0 | 7475 | this.context.debug("MWI in DCS received for voicemail: " + |
michael@0 | 7476 | JSON.stringify(mwi)); |
michael@0 | 7477 | } |
michael@0 | 7478 | break; |
michael@0 | 7479 | case PDU_DCS_MWI_TYPE_FAX: |
michael@0 | 7480 | if (DEBUG) this.context.debug("MWI in DCS received for fax"); |
michael@0 | 7481 | break; |
michael@0 | 7482 | case PDU_DCS_MWI_TYPE_EMAIL: |
michael@0 | 7483 | if (DEBUG) this.context.debug("MWI in DCS received for email"); |
michael@0 | 7484 | break; |
michael@0 | 7485 | default: |
michael@0 | 7486 | if (DEBUG) this.context.debug("MWI in DCS received for \"other\""); |
michael@0 | 7487 | break; |
michael@0 | 7488 | } |
michael@0 | 7489 | break; |
michael@0 | 7490 | |
michael@0 | 7491 | case 0xF0: // bits 7..4 = 1111 |
michael@0 | 7492 | if (dcs & 0x04) { |
michael@0 | 7493 | encoding = PDU_DCS_MSG_CODING_8BITS_ALPHABET; |
michael@0 | 7494 | } |
michael@0 | 7495 | messageClass = dcs & PDU_DCS_MSG_CLASS_BITS; |
michael@0 | 7496 | break; |
michael@0 | 7497 | |
michael@0 | 7498 | default: |
michael@0 | 7499 | // Falling back to default encoding. |
michael@0 | 7500 | break; |
michael@0 | 7501 | } |
michael@0 | 7502 | |
michael@0 | 7503 | msg.dcs = dcs; |
michael@0 | 7504 | msg.encoding = encoding; |
michael@0 | 7505 | msg.messageClass = GECKO_SMS_MESSAGE_CLASSES[messageClass]; |
michael@0 | 7506 | |
michael@0 | 7507 | if (DEBUG) this.context.debug("PDU: message encoding is " + encoding + " bit."); |
michael@0 | 7508 | }, |
michael@0 | 7509 | |
michael@0 | 7510 | /** |
michael@0 | 7511 | * Read GSM TP-Service-Centre-Time-Stamp(TP-SCTS). |
michael@0 | 7512 | * |
michael@0 | 7513 | * @see 3GPP TS 23.040 9.2.3.11 |
michael@0 | 7514 | */ |
michael@0 | 7515 | readTimestamp: function() { |
michael@0 | 7516 | let year = this.readSwappedNibbleBcdNum(1) + PDU_TIMESTAMP_YEAR_OFFSET; |
michael@0 | 7517 | let month = this.readSwappedNibbleBcdNum(1) - 1; |
michael@0 | 7518 | let day = this.readSwappedNibbleBcdNum(1); |
michael@0 | 7519 | let hour = this.readSwappedNibbleBcdNum(1); |
michael@0 | 7520 | let minute = this.readSwappedNibbleBcdNum(1); |
michael@0 | 7521 | let second = this.readSwappedNibbleBcdNum(1); |
michael@0 | 7522 | let timestamp = Date.UTC(year, month, day, hour, minute, second); |
michael@0 | 7523 | |
michael@0 | 7524 | // If the most significant bit of the least significant nibble is 1, |
michael@0 | 7525 | // the timezone offset is negative (fourth bit from the right => 0x08): |
michael@0 | 7526 | // localtime = UTC + tzOffset |
michael@0 | 7527 | // therefore |
michael@0 | 7528 | // UTC = localtime - tzOffset |
michael@0 | 7529 | let tzOctet = this.readHexOctet(); |
michael@0 | 7530 | let tzOffset = this.octetToBCD(tzOctet & ~0x08) * 15 * 60 * 1000; |
michael@0 | 7531 | tzOffset = (tzOctet & 0x08) ? -tzOffset : tzOffset; |
michael@0 | 7532 | timestamp -= tzOffset; |
michael@0 | 7533 | |
michael@0 | 7534 | return timestamp; |
michael@0 | 7535 | }, |
michael@0 | 7536 | |
michael@0 | 7537 | /** |
michael@0 | 7538 | * Write GSM TP-Service-Centre-Time-Stamp(TP-SCTS). |
michael@0 | 7539 | * |
michael@0 | 7540 | * @see 3GPP TS 23.040 9.2.3.11 |
michael@0 | 7541 | */ |
michael@0 | 7542 | writeTimestamp: function(date) { |
michael@0 | 7543 | this.writeSwappedNibbleBCDNum(date.getFullYear() - PDU_TIMESTAMP_YEAR_OFFSET); |
michael@0 | 7544 | |
michael@0 | 7545 | // The value returned by getMonth() is an integer between 0 and 11. |
michael@0 | 7546 | // 0 is corresponds to January, 1 to February, and so on. |
michael@0 | 7547 | this.writeSwappedNibbleBCDNum(date.getMonth() + 1); |
michael@0 | 7548 | this.writeSwappedNibbleBCDNum(date.getDate()); |
michael@0 | 7549 | this.writeSwappedNibbleBCDNum(date.getHours()); |
michael@0 | 7550 | this.writeSwappedNibbleBCDNum(date.getMinutes()); |
michael@0 | 7551 | this.writeSwappedNibbleBCDNum(date.getSeconds()); |
michael@0 | 7552 | |
michael@0 | 7553 | // the value returned by getTimezoneOffset() is the difference, |
michael@0 | 7554 | // in minutes, between UTC and local time. |
michael@0 | 7555 | // For example, if your time zone is UTC+10 (Australian Eastern Standard Time), |
michael@0 | 7556 | // -600 will be returned. |
michael@0 | 7557 | // In TS 23.040 9.2.3.11, the Time Zone field of TP-SCTS indicates |
michael@0 | 7558 | // the different between the local time and GMT. |
michael@0 | 7559 | // And expressed in quarters of an hours. (so need to divid by 15) |
michael@0 | 7560 | let zone = date.getTimezoneOffset() / 15; |
michael@0 | 7561 | let octet = this.BCDToOctet(zone); |
michael@0 | 7562 | |
michael@0 | 7563 | // the bit3 of the Time Zone field represents the algebraic sign. |
michael@0 | 7564 | // (0: positive, 1: negative). |
michael@0 | 7565 | // For example, if the time zone is -0800 GMT, |
michael@0 | 7566 | // 480 will be returned by getTimezoneOffset(). |
michael@0 | 7567 | // In this case, need to mark sign bit as 1. => 0x08 |
michael@0 | 7568 | if (zone > 0) { |
michael@0 | 7569 | octet = octet | 0x08; |
michael@0 | 7570 | } |
michael@0 | 7571 | this.writeHexOctet(octet); |
michael@0 | 7572 | }, |
michael@0 | 7573 | |
michael@0 | 7574 | /** |
michael@0 | 7575 | * User data can be 7 bit (default alphabet) data, 8 bit data, or 16 bit |
michael@0 | 7576 | * (UCS2) data. |
michael@0 | 7577 | * |
michael@0 | 7578 | * @param msg |
michael@0 | 7579 | * message object for output. |
michael@0 | 7580 | * @param length |
michael@0 | 7581 | * length of user data to read in octets. |
michael@0 | 7582 | */ |
michael@0 | 7583 | readUserData: function(msg, length) { |
michael@0 | 7584 | if (DEBUG) { |
michael@0 | 7585 | this.context.debug("Reading " + length + " bytes of user data."); |
michael@0 | 7586 | } |
michael@0 | 7587 | |
michael@0 | 7588 | let paddingBits = 0; |
michael@0 | 7589 | if (msg.udhi) { |
michael@0 | 7590 | this.readUserDataHeader(msg); |
michael@0 | 7591 | |
michael@0 | 7592 | if (msg.encoding == PDU_DCS_MSG_CODING_7BITS_ALPHABET) { |
michael@0 | 7593 | let headerBits = (msg.header.length + 1) * 8; |
michael@0 | 7594 | let headerSeptets = Math.ceil(headerBits / 7); |
michael@0 | 7595 | |
michael@0 | 7596 | length -= headerSeptets; |
michael@0 | 7597 | paddingBits = headerSeptets * 7 - headerBits; |
michael@0 | 7598 | } else { |
michael@0 | 7599 | length -= (msg.header.length + 1); |
michael@0 | 7600 | } |
michael@0 | 7601 | } |
michael@0 | 7602 | |
michael@0 | 7603 | if (DEBUG) { |
michael@0 | 7604 | this.context.debug("After header, " + length + " septets left of user data"); |
michael@0 | 7605 | } |
michael@0 | 7606 | |
michael@0 | 7607 | msg.body = null; |
michael@0 | 7608 | msg.data = null; |
michael@0 | 7609 | switch (msg.encoding) { |
michael@0 | 7610 | case PDU_DCS_MSG_CODING_7BITS_ALPHABET: |
michael@0 | 7611 | // 7 bit encoding allows 140 octets, which means 160 characters |
michael@0 | 7612 | // ((140x8) / 7 = 160 chars) |
michael@0 | 7613 | if (length > PDU_MAX_USER_DATA_7BIT) { |
michael@0 | 7614 | if (DEBUG) { |
michael@0 | 7615 | this.context.debug("PDU error: user data is too long: " + length); |
michael@0 | 7616 | } |
michael@0 | 7617 | break; |
michael@0 | 7618 | } |
michael@0 | 7619 | |
michael@0 | 7620 | let langIndex = msg.udhi ? msg.header.langIndex : PDU_NL_IDENTIFIER_DEFAULT; |
michael@0 | 7621 | let langShiftIndex = msg.udhi ? msg.header.langShiftIndex : PDU_NL_IDENTIFIER_DEFAULT; |
michael@0 | 7622 | msg.body = this.readSeptetsToString(length, paddingBits, langIndex, |
michael@0 | 7623 | langShiftIndex); |
michael@0 | 7624 | break; |
michael@0 | 7625 | case PDU_DCS_MSG_CODING_8BITS_ALPHABET: |
michael@0 | 7626 | msg.data = this.readHexOctetArray(length); |
michael@0 | 7627 | break; |
michael@0 | 7628 | case PDU_DCS_MSG_CODING_16BITS_ALPHABET: |
michael@0 | 7629 | msg.body = this.readUCS2String(length); |
michael@0 | 7630 | break; |
michael@0 | 7631 | } |
michael@0 | 7632 | }, |
michael@0 | 7633 | |
michael@0 | 7634 | /** |
michael@0 | 7635 | * Read extra parameters if TP-PI is set. |
michael@0 | 7636 | * |
michael@0 | 7637 | * @param msg |
michael@0 | 7638 | * message object for output. |
michael@0 | 7639 | */ |
michael@0 | 7640 | readExtraParams: function(msg) { |
michael@0 | 7641 | // Because each PDU octet is converted to two UCS2 char2, we should always |
michael@0 | 7642 | // get even messageStringLength in this#_processReceivedSms(). So, we'll |
michael@0 | 7643 | // always need two delimitors at the end. |
michael@0 | 7644 | if (this.context.Buf.getReadAvailable() <= 4) { |
michael@0 | 7645 | return; |
michael@0 | 7646 | } |
michael@0 | 7647 | |
michael@0 | 7648 | // TP-Parameter-Indicator |
michael@0 | 7649 | let pi; |
michael@0 | 7650 | do { |
michael@0 | 7651 | // `The most significant bit in octet 1 and any other TP-PI octets which |
michael@0 | 7652 | // may be added later is reserved as an extension bit which when set to a |
michael@0 | 7653 | // 1 shall indicate that another TP-PI octet follows immediately |
michael@0 | 7654 | // afterwards.` ~ 3GPP TS 23.040 9.2.3.27 |
michael@0 | 7655 | pi = this.readHexOctet(); |
michael@0 | 7656 | } while (pi & PDU_PI_EXTENSION); |
michael@0 | 7657 | |
michael@0 | 7658 | // `If the TP-UDL bit is set to "1" but the TP-DCS bit is set to "0" then |
michael@0 | 7659 | // the receiving entity shall for TP-DCS assume a value of 0x00, i.e. the |
michael@0 | 7660 | // 7bit default alphabet.` ~ 3GPP 23.040 9.2.3.27 |
michael@0 | 7661 | msg.dcs = 0; |
michael@0 | 7662 | msg.encoding = PDU_DCS_MSG_CODING_7BITS_ALPHABET; |
michael@0 | 7663 | |
michael@0 | 7664 | // TP-Protocol-Identifier |
michael@0 | 7665 | if (pi & PDU_PI_PROTOCOL_IDENTIFIER) { |
michael@0 | 7666 | this.readProtocolIndicator(msg); |
michael@0 | 7667 | } |
michael@0 | 7668 | // TP-Data-Coding-Scheme |
michael@0 | 7669 | if (pi & PDU_PI_DATA_CODING_SCHEME) { |
michael@0 | 7670 | this.readDataCodingScheme(msg); |
michael@0 | 7671 | } |
michael@0 | 7672 | // TP-User-Data-Length |
michael@0 | 7673 | if (pi & PDU_PI_USER_DATA_LENGTH) { |
michael@0 | 7674 | let userDataLength = this.readHexOctet(); |
michael@0 | 7675 | this.readUserData(msg, userDataLength); |
michael@0 | 7676 | } |
michael@0 | 7677 | }, |
michael@0 | 7678 | |
michael@0 | 7679 | /** |
michael@0 | 7680 | * Read and decode a PDU-encoded message from the stream. |
michael@0 | 7681 | * |
michael@0 | 7682 | * TODO: add some basic sanity checks like: |
michael@0 | 7683 | * - do we have the minimum number of chars available |
michael@0 | 7684 | */ |
michael@0 | 7685 | readMessage: function() { |
michael@0 | 7686 | // An empty message object. This gets filled below and then returned. |
michael@0 | 7687 | let msg = { |
michael@0 | 7688 | // D:DELIVER, DR:DELIVER-REPORT, S:SUBMIT, SR:SUBMIT-REPORT, |
michael@0 | 7689 | // ST:STATUS-REPORT, C:COMMAND |
michael@0 | 7690 | // M:Mandatory, O:Optional, X:Unavailable |
michael@0 | 7691 | // D DR S SR ST C |
michael@0 | 7692 | SMSC: null, // M M M M M M |
michael@0 | 7693 | mti: null, // M M M M M M |
michael@0 | 7694 | udhi: null, // M M O M M M |
michael@0 | 7695 | sender: null, // M X X X X X |
michael@0 | 7696 | recipient: null, // X X M X M M |
michael@0 | 7697 | pid: null, // M O M O O M |
michael@0 | 7698 | epid: null, // M O M O O M |
michael@0 | 7699 | dcs: null, // M O M O O X |
michael@0 | 7700 | mwi: null, // O O O O O O |
michael@0 | 7701 | replace: false, // O O O O O O |
michael@0 | 7702 | header: null, // M M O M M M |
michael@0 | 7703 | body: null, // M O M O O O |
michael@0 | 7704 | data: null, // M O M O O O |
michael@0 | 7705 | sentTimestamp: null, // M X X X X X |
michael@0 | 7706 | status: null, // X X X X M X |
michael@0 | 7707 | scts: null, // X X X M M X |
michael@0 | 7708 | dt: null, // X X X X M X |
michael@0 | 7709 | }; |
michael@0 | 7710 | |
michael@0 | 7711 | // SMSC info |
michael@0 | 7712 | let smscLength = this.readHexOctet(); |
michael@0 | 7713 | if (smscLength > 0) { |
michael@0 | 7714 | let smscTypeOfAddress = this.readHexOctet(); |
michael@0 | 7715 | // Subtract the type-of-address octet we just read from the length. |
michael@0 | 7716 | msg.SMSC = this.readSwappedNibbleBcdString(smscLength - 1); |
michael@0 | 7717 | if ((smscTypeOfAddress >> 4) == (PDU_TOA_INTERNATIONAL >> 4)) { |
michael@0 | 7718 | msg.SMSC = '+' + msg.SMSC; |
michael@0 | 7719 | } |
michael@0 | 7720 | } |
michael@0 | 7721 | |
michael@0 | 7722 | // First octet of this SMS-DELIVER or SMS-SUBMIT message |
michael@0 | 7723 | let firstOctet = this.readHexOctet(); |
michael@0 | 7724 | // Message Type Indicator |
michael@0 | 7725 | msg.mti = firstOctet & 0x03; |
michael@0 | 7726 | // User data header indicator |
michael@0 | 7727 | msg.udhi = firstOctet & PDU_UDHI; |
michael@0 | 7728 | |
michael@0 | 7729 | switch (msg.mti) { |
michael@0 | 7730 | case PDU_MTI_SMS_RESERVED: |
michael@0 | 7731 | // `If an MS receives a TPDU with a "Reserved" value in the TP-MTI it |
michael@0 | 7732 | // shall process the message as if it were an "SMS-DELIVER" but store |
michael@0 | 7733 | // the message exactly as received.` ~ 3GPP TS 23.040 9.2.3.1 |
michael@0 | 7734 | case PDU_MTI_SMS_DELIVER: |
michael@0 | 7735 | return this.readDeliverMessage(msg); |
michael@0 | 7736 | case PDU_MTI_SMS_STATUS_REPORT: |
michael@0 | 7737 | return this.readStatusReportMessage(msg); |
michael@0 | 7738 | default: |
michael@0 | 7739 | return null; |
michael@0 | 7740 | } |
michael@0 | 7741 | }, |
michael@0 | 7742 | |
michael@0 | 7743 | /** |
michael@0 | 7744 | * Helper for processing received SMS parcel data. |
michael@0 | 7745 | * |
michael@0 | 7746 | * @param length |
michael@0 | 7747 | * Length of SMS string in the incoming parcel. |
michael@0 | 7748 | * |
michael@0 | 7749 | * @return Message parsed or null for invalid message. |
michael@0 | 7750 | */ |
michael@0 | 7751 | processReceivedSms: function(length) { |
michael@0 | 7752 | if (!length) { |
michael@0 | 7753 | if (DEBUG) this.context.debug("Received empty SMS!"); |
michael@0 | 7754 | return [null, PDU_FCS_UNSPECIFIED]; |
michael@0 | 7755 | } |
michael@0 | 7756 | |
michael@0 | 7757 | let Buf = this.context.Buf; |
michael@0 | 7758 | |
michael@0 | 7759 | // An SMS is a string, but we won't read it as such, so let's read the |
michael@0 | 7760 | // string length and then defer to PDU parsing helper. |
michael@0 | 7761 | let messageStringLength = Buf.readInt32(); |
michael@0 | 7762 | if (DEBUG) this.context.debug("Got new SMS, length " + messageStringLength); |
michael@0 | 7763 | let message = this.readMessage(); |
michael@0 | 7764 | if (DEBUG) this.context.debug("Got new SMS: " + JSON.stringify(message)); |
michael@0 | 7765 | |
michael@0 | 7766 | // Read string delimiters. See Buf.readString(). |
michael@0 | 7767 | Buf.readStringDelimiter(length); |
michael@0 | 7768 | |
michael@0 | 7769 | // Determine result |
michael@0 | 7770 | if (!message) { |
michael@0 | 7771 | return [null, PDU_FCS_UNSPECIFIED]; |
michael@0 | 7772 | } |
michael@0 | 7773 | |
michael@0 | 7774 | if (message.epid == PDU_PID_SHORT_MESSAGE_TYPE_0) { |
michael@0 | 7775 | // `A short message type 0 indicates that the ME must acknowledge receipt |
michael@0 | 7776 | // of the short message but shall discard its contents.` ~ 3GPP TS 23.040 |
michael@0 | 7777 | // 9.2.3.9 |
michael@0 | 7778 | return [null, PDU_FCS_OK]; |
michael@0 | 7779 | } |
michael@0 | 7780 | |
michael@0 | 7781 | if (message.messageClass == GECKO_SMS_MESSAGE_CLASSES[PDU_DCS_MSG_CLASS_2]) { |
michael@0 | 7782 | let RIL = this.context.RIL; |
michael@0 | 7783 | switch (message.epid) { |
michael@0 | 7784 | case PDU_PID_ANSI_136_R_DATA: |
michael@0 | 7785 | case PDU_PID_USIM_DATA_DOWNLOAD: |
michael@0 | 7786 | let ICCUtilsHelper = this.context.ICCUtilsHelper; |
michael@0 | 7787 | if (ICCUtilsHelper.isICCServiceAvailable("DATA_DOWNLOAD_SMS_PP")) { |
michael@0 | 7788 | // `If the service "data download via SMS Point-to-Point" is |
michael@0 | 7789 | // allocated and activated in the (U)SIM Service Table, ... then the |
michael@0 | 7790 | // ME shall pass the message transparently to the UICC using the |
michael@0 | 7791 | // ENVELOPE (SMS-PP DOWNLOAD).` ~ 3GPP TS 31.111 7.1.1.1 |
michael@0 | 7792 | RIL.dataDownloadViaSMSPP(message); |
michael@0 | 7793 | |
michael@0 | 7794 | // `the ME shall not display the message, or alert the user of a |
michael@0 | 7795 | // short message waiting.` ~ 3GPP TS 31.111 7.1.1.1 |
michael@0 | 7796 | return [null, PDU_FCS_RESERVED]; |
michael@0 | 7797 | } |
michael@0 | 7798 | |
michael@0 | 7799 | // If the service "data download via SMS-PP" is not available in the |
michael@0 | 7800 | // (U)SIM Service Table, ..., then the ME shall store the message in |
michael@0 | 7801 | // EFsms in accordance with TS 31.102` ~ 3GPP TS 31.111 7.1.1.1 |
michael@0 | 7802 | |
michael@0 | 7803 | // Fall through. |
michael@0 | 7804 | default: |
michael@0 | 7805 | RIL.writeSmsToSIM(message); |
michael@0 | 7806 | break; |
michael@0 | 7807 | } |
michael@0 | 7808 | } |
michael@0 | 7809 | |
michael@0 | 7810 | // TODO: Bug 739143: B2G SMS: Support SMS Storage Full event |
michael@0 | 7811 | if ((message.messageClass != GECKO_SMS_MESSAGE_CLASSES[PDU_DCS_MSG_CLASS_0]) && !true) { |
michael@0 | 7812 | // `When a mobile terminated message is class 0..., the MS shall display |
michael@0 | 7813 | // the message immediately and send a ACK to the SC ..., irrespective of |
michael@0 | 7814 | // whether there is memory available in the (U)SIM or ME.` ~ 3GPP 23.038 |
michael@0 | 7815 | // clause 4. |
michael@0 | 7816 | |
michael@0 | 7817 | if (message.messageClass == GECKO_SMS_MESSAGE_CLASSES[PDU_DCS_MSG_CLASS_2]) { |
michael@0 | 7818 | // `If all the short message storage at the MS is already in use, the |
michael@0 | 7819 | // MS shall return "memory capacity exceeded".` ~ 3GPP 23.038 clause 4. |
michael@0 | 7820 | return [null, PDU_FCS_MEMORY_CAPACITY_EXCEEDED]; |
michael@0 | 7821 | } |
michael@0 | 7822 | |
michael@0 | 7823 | return [null, PDU_FCS_UNSPECIFIED]; |
michael@0 | 7824 | } |
michael@0 | 7825 | |
michael@0 | 7826 | return [message, PDU_FCS_OK]; |
michael@0 | 7827 | }, |
michael@0 | 7828 | |
michael@0 | 7829 | /** |
michael@0 | 7830 | * Read and decode a SMS-DELIVER PDU. |
michael@0 | 7831 | * |
michael@0 | 7832 | * @param msg |
michael@0 | 7833 | * message object for output. |
michael@0 | 7834 | */ |
michael@0 | 7835 | readDeliverMessage: function(msg) { |
michael@0 | 7836 | // - Sender Address info - |
michael@0 | 7837 | let senderAddressLength = this.readHexOctet(); |
michael@0 | 7838 | msg.sender = this.readAddress(senderAddressLength); |
michael@0 | 7839 | // - TP-Protocolo-Identifier - |
michael@0 | 7840 | this.readProtocolIndicator(msg); |
michael@0 | 7841 | // - TP-Data-Coding-Scheme - |
michael@0 | 7842 | this.readDataCodingScheme(msg); |
michael@0 | 7843 | // - TP-Service-Center-Time-Stamp - |
michael@0 | 7844 | msg.sentTimestamp = this.readTimestamp(); |
michael@0 | 7845 | // - TP-User-Data-Length - |
michael@0 | 7846 | let userDataLength = this.readHexOctet(); |
michael@0 | 7847 | |
michael@0 | 7848 | // - TP-User-Data - |
michael@0 | 7849 | if (userDataLength > 0) { |
michael@0 | 7850 | this.readUserData(msg, userDataLength); |
michael@0 | 7851 | } |
michael@0 | 7852 | |
michael@0 | 7853 | return msg; |
michael@0 | 7854 | }, |
michael@0 | 7855 | |
michael@0 | 7856 | /** |
michael@0 | 7857 | * Read and decode a SMS-STATUS-REPORT PDU. |
michael@0 | 7858 | * |
michael@0 | 7859 | * @param msg |
michael@0 | 7860 | * message object for output. |
michael@0 | 7861 | */ |
michael@0 | 7862 | readStatusReportMessage: function(msg) { |
michael@0 | 7863 | // TP-Message-Reference |
michael@0 | 7864 | msg.messageRef = this.readHexOctet(); |
michael@0 | 7865 | // TP-Recipient-Address |
michael@0 | 7866 | let recipientAddressLength = this.readHexOctet(); |
michael@0 | 7867 | msg.recipient = this.readAddress(recipientAddressLength); |
michael@0 | 7868 | // TP-Service-Centre-Time-Stamp |
michael@0 | 7869 | msg.scts = this.readTimestamp(); |
michael@0 | 7870 | // TP-Discharge-Time |
michael@0 | 7871 | msg.dt = this.readTimestamp(); |
michael@0 | 7872 | // TP-Status |
michael@0 | 7873 | msg.status = this.readHexOctet(); |
michael@0 | 7874 | |
michael@0 | 7875 | this.readExtraParams(msg); |
michael@0 | 7876 | |
michael@0 | 7877 | return msg; |
michael@0 | 7878 | }, |
michael@0 | 7879 | |
michael@0 | 7880 | /** |
michael@0 | 7881 | * Serialize a SMS-SUBMIT PDU message and write it to the output stream. |
michael@0 | 7882 | * |
michael@0 | 7883 | * This method expects that a data coding scheme has been chosen already |
michael@0 | 7884 | * and that the length of the user data payload in that encoding is known, |
michael@0 | 7885 | * too. Both go hand in hand together anyway. |
michael@0 | 7886 | * |
michael@0 | 7887 | * @param address |
michael@0 | 7888 | * String containing the address (number) of the SMS receiver |
michael@0 | 7889 | * @param userData |
michael@0 | 7890 | * String containing the message to be sent as user data |
michael@0 | 7891 | * @param dcs |
michael@0 | 7892 | * Data coding scheme. One of the PDU_DCS_MSG_CODING_*BITS_ALPHABET |
michael@0 | 7893 | * constants. |
michael@0 | 7894 | * @param userDataHeaderLength |
michael@0 | 7895 | * Length of embedded user data header, in bytes. The whole header |
michael@0 | 7896 | * size will be userDataHeaderLength + 1; 0 for no header. |
michael@0 | 7897 | * @param encodedBodyLength |
michael@0 | 7898 | * Length of the user data when encoded with the given DCS. For UCS2, |
michael@0 | 7899 | * in bytes; for 7-bit, in septets. |
michael@0 | 7900 | * @param langIndex |
michael@0 | 7901 | * Table index used for normal 7-bit encoded character lookup. |
michael@0 | 7902 | * @param langShiftIndex |
michael@0 | 7903 | * Table index used for escaped 7-bit encoded character lookup. |
michael@0 | 7904 | * @param requestStatusReport |
michael@0 | 7905 | * Request status report. |
michael@0 | 7906 | */ |
michael@0 | 7907 | writeMessage: function(options) { |
michael@0 | 7908 | if (DEBUG) { |
michael@0 | 7909 | this.context.debug("writeMessage: " + JSON.stringify(options)); |
michael@0 | 7910 | } |
michael@0 | 7911 | let Buf = this.context.Buf; |
michael@0 | 7912 | let address = options.number; |
michael@0 | 7913 | let body = options.body; |
michael@0 | 7914 | let dcs = options.dcs; |
michael@0 | 7915 | let userDataHeaderLength = options.userDataHeaderLength; |
michael@0 | 7916 | let encodedBodyLength = options.encodedBodyLength; |
michael@0 | 7917 | let langIndex = options.langIndex; |
michael@0 | 7918 | let langShiftIndex = options.langShiftIndex; |
michael@0 | 7919 | |
michael@0 | 7920 | // SMS-SUBMIT Format: |
michael@0 | 7921 | // |
michael@0 | 7922 | // PDU Type - 1 octet |
michael@0 | 7923 | // Message Reference - 1 octet |
michael@0 | 7924 | // DA - Destination Address - 2 to 12 octets |
michael@0 | 7925 | // PID - Protocol Identifier - 1 octet |
michael@0 | 7926 | // DCS - Data Coding Scheme - 1 octet |
michael@0 | 7927 | // VP - Validity Period - 0, 1 or 7 octets |
michael@0 | 7928 | // UDL - User Data Length - 1 octet |
michael@0 | 7929 | // UD - User Data - 140 octets |
michael@0 | 7930 | |
michael@0 | 7931 | let addressFormat = PDU_TOA_ISDN; // 81 |
michael@0 | 7932 | if (address[0] == '+') { |
michael@0 | 7933 | addressFormat = PDU_TOA_INTERNATIONAL | PDU_TOA_ISDN; // 91 |
michael@0 | 7934 | address = address.substring(1); |
michael@0 | 7935 | } |
michael@0 | 7936 | //TODO validity is unsupported for now |
michael@0 | 7937 | let validity = 0; |
michael@0 | 7938 | |
michael@0 | 7939 | let headerOctets = (userDataHeaderLength ? userDataHeaderLength + 1 : 0); |
michael@0 | 7940 | let paddingBits; |
michael@0 | 7941 | let userDataLengthInSeptets; |
michael@0 | 7942 | let userDataLengthInOctets; |
michael@0 | 7943 | if (dcs == PDU_DCS_MSG_CODING_7BITS_ALPHABET) { |
michael@0 | 7944 | let headerSeptets = Math.ceil(headerOctets * 8 / 7); |
michael@0 | 7945 | userDataLengthInSeptets = headerSeptets + encodedBodyLength; |
michael@0 | 7946 | userDataLengthInOctets = Math.ceil(userDataLengthInSeptets * 7 / 8); |
michael@0 | 7947 | paddingBits = headerSeptets * 7 - headerOctets * 8; |
michael@0 | 7948 | } else { |
michael@0 | 7949 | userDataLengthInOctets = headerOctets + encodedBodyLength; |
michael@0 | 7950 | paddingBits = 0; |
michael@0 | 7951 | } |
michael@0 | 7952 | |
michael@0 | 7953 | let pduOctetLength = 4 + // PDU Type, Message Ref, address length + format |
michael@0 | 7954 | Math.ceil(address.length / 2) + |
michael@0 | 7955 | 3 + // PID, DCS, UDL |
michael@0 | 7956 | userDataLengthInOctets; |
michael@0 | 7957 | if (validity) { |
michael@0 | 7958 | //TODO: add more to pduOctetLength |
michael@0 | 7959 | } |
michael@0 | 7960 | |
michael@0 | 7961 | // Start the string. Since octets are represented in hex, we will need |
michael@0 | 7962 | // twice as many characters as octets. |
michael@0 | 7963 | Buf.writeInt32(pduOctetLength * 2); |
michael@0 | 7964 | |
michael@0 | 7965 | // - PDU-TYPE- |
michael@0 | 7966 | |
michael@0 | 7967 | // +--------+----------+---------+---------+--------+---------+ |
michael@0 | 7968 | // | RP (1) | UDHI (1) | SRR (1) | VPF (2) | RD (1) | MTI (2) | |
michael@0 | 7969 | // +--------+----------+---------+---------+--------+---------+ |
michael@0 | 7970 | // RP: 0 Reply path parameter is not set |
michael@0 | 7971 | // 1 Reply path parameter is set |
michael@0 | 7972 | // UDHI: 0 The UD Field contains only the short message |
michael@0 | 7973 | // 1 The beginning of the UD field contains a header in addition |
michael@0 | 7974 | // of the short message |
michael@0 | 7975 | // SRR: 0 A status report is not requested |
michael@0 | 7976 | // 1 A status report is requested |
michael@0 | 7977 | // VPF: bit4 bit3 |
michael@0 | 7978 | // 0 0 VP field is not present |
michael@0 | 7979 | // 0 1 Reserved |
michael@0 | 7980 | // 1 0 VP field present an integer represented (relative) |
michael@0 | 7981 | // 1 1 VP field present a semi-octet represented (absolute) |
michael@0 | 7982 | // RD: Instruct the SMSC to accept(0) or reject(1) an SMS-SUBMIT |
michael@0 | 7983 | // for a short message still held in the SMSC which has the same |
michael@0 | 7984 | // MR and DA as a previously submitted short message from the |
michael@0 | 7985 | // same OA |
michael@0 | 7986 | // MTI: bit1 bit0 Message Type |
michael@0 | 7987 | // 0 0 SMS-DELIVER (SMSC ==> MS) |
michael@0 | 7988 | // 0 1 SMS-SUBMIT (MS ==> SMSC) |
michael@0 | 7989 | |
michael@0 | 7990 | // PDU type. MTI is set to SMS-SUBMIT |
michael@0 | 7991 | let firstOctet = PDU_MTI_SMS_SUBMIT; |
michael@0 | 7992 | |
michael@0 | 7993 | // Status-Report-Request |
michael@0 | 7994 | if (options.requestStatusReport) { |
michael@0 | 7995 | firstOctet |= PDU_SRI_SRR; |
michael@0 | 7996 | } |
michael@0 | 7997 | |
michael@0 | 7998 | // Validity period |
michael@0 | 7999 | if (validity) { |
michael@0 | 8000 | //TODO: not supported yet, OR with one of PDU_VPF_* |
michael@0 | 8001 | } |
michael@0 | 8002 | // User data header indicator |
michael@0 | 8003 | if (headerOctets) { |
michael@0 | 8004 | firstOctet |= PDU_UDHI; |
michael@0 | 8005 | } |
michael@0 | 8006 | this.writeHexOctet(firstOctet); |
michael@0 | 8007 | |
michael@0 | 8008 | // Message reference 00 |
michael@0 | 8009 | this.writeHexOctet(0x00); |
michael@0 | 8010 | |
michael@0 | 8011 | // - Destination Address - |
michael@0 | 8012 | this.writeHexOctet(address.length); |
michael@0 | 8013 | this.writeHexOctet(addressFormat); |
michael@0 | 8014 | this.writeSwappedNibbleBCD(address); |
michael@0 | 8015 | |
michael@0 | 8016 | // - Protocol Identifier - |
michael@0 | 8017 | this.writeHexOctet(0x00); |
michael@0 | 8018 | |
michael@0 | 8019 | // - Data coding scheme - |
michael@0 | 8020 | // For now it assumes bits 7..4 = 1111 except for the 16 bits use case |
michael@0 | 8021 | this.writeHexOctet(dcs); |
michael@0 | 8022 | |
michael@0 | 8023 | // - Validity Period - |
michael@0 | 8024 | if (validity) { |
michael@0 | 8025 | this.writeHexOctet(validity); |
michael@0 | 8026 | } |
michael@0 | 8027 | |
michael@0 | 8028 | // - User Data - |
michael@0 | 8029 | if (dcs == PDU_DCS_MSG_CODING_7BITS_ALPHABET) { |
michael@0 | 8030 | this.writeHexOctet(userDataLengthInSeptets); |
michael@0 | 8031 | } else { |
michael@0 | 8032 | this.writeHexOctet(userDataLengthInOctets); |
michael@0 | 8033 | } |
michael@0 | 8034 | |
michael@0 | 8035 | if (headerOctets) { |
michael@0 | 8036 | this.writeUserDataHeader(options); |
michael@0 | 8037 | } |
michael@0 | 8038 | |
michael@0 | 8039 | switch (dcs) { |
michael@0 | 8040 | case PDU_DCS_MSG_CODING_7BITS_ALPHABET: |
michael@0 | 8041 | this.writeStringAsSeptets(body, paddingBits, langIndex, langShiftIndex); |
michael@0 | 8042 | break; |
michael@0 | 8043 | case PDU_DCS_MSG_CODING_8BITS_ALPHABET: |
michael@0 | 8044 | // Unsupported. |
michael@0 | 8045 | break; |
michael@0 | 8046 | case PDU_DCS_MSG_CODING_16BITS_ALPHABET: |
michael@0 | 8047 | this.writeUCS2String(body); |
michael@0 | 8048 | break; |
michael@0 | 8049 | } |
michael@0 | 8050 | |
michael@0 | 8051 | // End of the string. The string length is always even by definition, so |
michael@0 | 8052 | // we write two \0 delimiters. |
michael@0 | 8053 | Buf.writeUint16(0); |
michael@0 | 8054 | Buf.writeUint16(0); |
michael@0 | 8055 | }, |
michael@0 | 8056 | |
michael@0 | 8057 | /** |
michael@0 | 8058 | * Read GSM CBS message serial number. |
michael@0 | 8059 | * |
michael@0 | 8060 | * @param msg |
michael@0 | 8061 | * message object for output. |
michael@0 | 8062 | * |
michael@0 | 8063 | * @see 3GPP TS 23.041 section 9.4.1.2.1 |
michael@0 | 8064 | */ |
michael@0 | 8065 | readCbSerialNumber: function(msg) { |
michael@0 | 8066 | let Buf = this.context.Buf; |
michael@0 | 8067 | msg.serial = Buf.readUint8() << 8 | Buf.readUint8(); |
michael@0 | 8068 | msg.geographicalScope = (msg.serial >>> 14) & 0x03; |
michael@0 | 8069 | msg.messageCode = (msg.serial >>> 4) & 0x03FF; |
michael@0 | 8070 | msg.updateNumber = msg.serial & 0x0F; |
michael@0 | 8071 | }, |
michael@0 | 8072 | |
michael@0 | 8073 | /** |
michael@0 | 8074 | * Read GSM CBS message message identifier. |
michael@0 | 8075 | * |
michael@0 | 8076 | * @param msg |
michael@0 | 8077 | * message object for output. |
michael@0 | 8078 | * |
michael@0 | 8079 | * @see 3GPP TS 23.041 section 9.4.1.2.2 |
michael@0 | 8080 | */ |
michael@0 | 8081 | readCbMessageIdentifier: function(msg) { |
michael@0 | 8082 | let Buf = this.context.Buf; |
michael@0 | 8083 | msg.messageId = Buf.readUint8() << 8 | Buf.readUint8(); |
michael@0 | 8084 | |
michael@0 | 8085 | if ((msg.format != CB_FORMAT_ETWS) |
michael@0 | 8086 | && (msg.messageId >= CB_GSM_MESSAGEID_ETWS_BEGIN) |
michael@0 | 8087 | && (msg.messageId <= CB_GSM_MESSAGEID_ETWS_END)) { |
michael@0 | 8088 | // `In the case of transmitting CBS message for ETWS, a part of |
michael@0 | 8089 | // Message Code can be used to command mobile terminals to activate |
michael@0 | 8090 | // emergency user alert and message popup in order to alert the users.` |
michael@0 | 8091 | msg.etws = { |
michael@0 | 8092 | emergencyUserAlert: msg.messageCode & 0x0200 ? true : false, |
michael@0 | 8093 | popup: msg.messageCode & 0x0100 ? true : false |
michael@0 | 8094 | }; |
michael@0 | 8095 | |
michael@0 | 8096 | let warningType = msg.messageId - CB_GSM_MESSAGEID_ETWS_BEGIN; |
michael@0 | 8097 | if (warningType < CB_ETWS_WARNING_TYPE_NAMES.length) { |
michael@0 | 8098 | msg.etws.warningType = warningType; |
michael@0 | 8099 | } |
michael@0 | 8100 | } |
michael@0 | 8101 | }, |
michael@0 | 8102 | |
michael@0 | 8103 | /** |
michael@0 | 8104 | * Read CBS Data Coding Scheme. |
michael@0 | 8105 | * |
michael@0 | 8106 | * @param msg |
michael@0 | 8107 | * message object for output. |
michael@0 | 8108 | * |
michael@0 | 8109 | * @see 3GPP TS 23.038 section 5. |
michael@0 | 8110 | */ |
michael@0 | 8111 | readCbDataCodingScheme: function(msg) { |
michael@0 | 8112 | let dcs = this.context.Buf.readUint8(); |
michael@0 | 8113 | if (DEBUG) this.context.debug("PDU: read CBS dcs: " + dcs); |
michael@0 | 8114 | |
michael@0 | 8115 | let language = null, hasLanguageIndicator = false; |
michael@0 | 8116 | // `Any reserved codings shall be assumed to be the GSM 7bit default |
michael@0 | 8117 | // alphabet.` |
michael@0 | 8118 | let encoding = PDU_DCS_MSG_CODING_7BITS_ALPHABET; |
michael@0 | 8119 | let messageClass = PDU_DCS_MSG_CLASS_NORMAL; |
michael@0 | 8120 | |
michael@0 | 8121 | switch (dcs & PDU_DCS_CODING_GROUP_BITS) { |
michael@0 | 8122 | case 0x00: // 0000 |
michael@0 | 8123 | language = CB_DCS_LANG_GROUP_1[dcs & 0x0F]; |
michael@0 | 8124 | break; |
michael@0 | 8125 | |
michael@0 | 8126 | case 0x10: // 0001 |
michael@0 | 8127 | switch (dcs & 0x0F) { |
michael@0 | 8128 | case 0x00: |
michael@0 | 8129 | hasLanguageIndicator = true; |
michael@0 | 8130 | break; |
michael@0 | 8131 | case 0x01: |
michael@0 | 8132 | encoding = PDU_DCS_MSG_CODING_16BITS_ALPHABET; |
michael@0 | 8133 | hasLanguageIndicator = true; |
michael@0 | 8134 | break; |
michael@0 | 8135 | } |
michael@0 | 8136 | break; |
michael@0 | 8137 | |
michael@0 | 8138 | case 0x20: // 0010 |
michael@0 | 8139 | language = CB_DCS_LANG_GROUP_2[dcs & 0x0F]; |
michael@0 | 8140 | break; |
michael@0 | 8141 | |
michael@0 | 8142 | case 0x40: // 01xx |
michael@0 | 8143 | case 0x50: |
michael@0 | 8144 | //case 0x60: Text Compression, not supported |
michael@0 | 8145 | //case 0x70: Text Compression, not supported |
michael@0 | 8146 | case 0x90: // 1001 |
michael@0 | 8147 | encoding = (dcs & 0x0C); |
michael@0 | 8148 | if (encoding == 0x0C) { |
michael@0 | 8149 | encoding = PDU_DCS_MSG_CODING_7BITS_ALPHABET; |
michael@0 | 8150 | } |
michael@0 | 8151 | messageClass = (dcs & PDU_DCS_MSG_CLASS_BITS); |
michael@0 | 8152 | break; |
michael@0 | 8153 | |
michael@0 | 8154 | case 0xF0: |
michael@0 | 8155 | encoding = (dcs & 0x04) ? PDU_DCS_MSG_CODING_8BITS_ALPHABET |
michael@0 | 8156 | : PDU_DCS_MSG_CODING_7BITS_ALPHABET; |
michael@0 | 8157 | switch(dcs & PDU_DCS_MSG_CLASS_BITS) { |
michael@0 | 8158 | case 0x01: messageClass = PDU_DCS_MSG_CLASS_USER_1; break; |
michael@0 | 8159 | case 0x02: messageClass = PDU_DCS_MSG_CLASS_USER_2; break; |
michael@0 | 8160 | case 0x03: messageClass = PDU_DCS_MSG_CLASS_3; break; |
michael@0 | 8161 | } |
michael@0 | 8162 | break; |
michael@0 | 8163 | |
michael@0 | 8164 | case 0x30: // 0011 (Reserved) |
michael@0 | 8165 | case 0x80: // 1000 (Reserved) |
michael@0 | 8166 | case 0xA0: // 1010..1100 (Reserved) |
michael@0 | 8167 | case 0xB0: |
michael@0 | 8168 | case 0xC0: |
michael@0 | 8169 | break; |
michael@0 | 8170 | |
michael@0 | 8171 | default: |
michael@0 | 8172 | throw new Error("Unsupported CBS data coding scheme: " + dcs); |
michael@0 | 8173 | } |
michael@0 | 8174 | |
michael@0 | 8175 | msg.dcs = dcs; |
michael@0 | 8176 | msg.encoding = encoding; |
michael@0 | 8177 | msg.language = language; |
michael@0 | 8178 | msg.messageClass = GECKO_SMS_MESSAGE_CLASSES[messageClass]; |
michael@0 | 8179 | msg.hasLanguageIndicator = hasLanguageIndicator; |
michael@0 | 8180 | }, |
michael@0 | 8181 | |
michael@0 | 8182 | /** |
michael@0 | 8183 | * Read GSM CBS message page parameter. |
michael@0 | 8184 | * |
michael@0 | 8185 | * @param msg |
michael@0 | 8186 | * message object for output. |
michael@0 | 8187 | * |
michael@0 | 8188 | * @see 3GPP TS 23.041 section 9.4.1.2.4 |
michael@0 | 8189 | */ |
michael@0 | 8190 | readCbPageParameter: function(msg) { |
michael@0 | 8191 | let octet = this.context.Buf.readUint8(); |
michael@0 | 8192 | msg.pageIndex = (octet >>> 4) & 0x0F; |
michael@0 | 8193 | msg.numPages = octet & 0x0F; |
michael@0 | 8194 | if (!msg.pageIndex || !msg.numPages) { |
michael@0 | 8195 | // `If a mobile receives the code 0000 in either the first field or the |
michael@0 | 8196 | // second field then it shall treat the CBS message exactly the same as a |
michael@0 | 8197 | // CBS message with page parameter 0001 0001 (i.e. a single page message).` |
michael@0 | 8198 | msg.pageIndex = msg.numPages = 1; |
michael@0 | 8199 | } |
michael@0 | 8200 | }, |
michael@0 | 8201 | |
michael@0 | 8202 | /** |
michael@0 | 8203 | * Read ETWS Primary Notification message warning type. |
michael@0 | 8204 | * |
michael@0 | 8205 | * @param msg |
michael@0 | 8206 | * message object for output. |
michael@0 | 8207 | * |
michael@0 | 8208 | * @see 3GPP TS 23.041 section 9.3.24 |
michael@0 | 8209 | */ |
michael@0 | 8210 | readCbWarningType: function(msg) { |
michael@0 | 8211 | let Buf = this.context.Buf; |
michael@0 | 8212 | let word = Buf.readUint8() << 8 | Buf.readUint8(); |
michael@0 | 8213 | msg.etws = { |
michael@0 | 8214 | warningType: (word >>> 9) & 0x7F, |
michael@0 | 8215 | popup: word & 0x80 ? true : false, |
michael@0 | 8216 | emergencyUserAlert: word & 0x100 ? true : false |
michael@0 | 8217 | }; |
michael@0 | 8218 | }, |
michael@0 | 8219 | |
michael@0 | 8220 | /** |
michael@0 | 8221 | * Read CBS-Message-Information-Page |
michael@0 | 8222 | * |
michael@0 | 8223 | * @param msg |
michael@0 | 8224 | * message object for output. |
michael@0 | 8225 | * @param length |
michael@0 | 8226 | * length of cell broadcast data to read in octets. |
michael@0 | 8227 | * |
michael@0 | 8228 | * @see 3GPP TS 23.041 section 9.3.19 |
michael@0 | 8229 | */ |
michael@0 | 8230 | readGsmCbData: function(msg, length) { |
michael@0 | 8231 | let Buf = this.context.Buf; |
michael@0 | 8232 | let bufAdapter = { |
michael@0 | 8233 | context: this.context, |
michael@0 | 8234 | readHexOctet: function() { |
michael@0 | 8235 | return Buf.readUint8(); |
michael@0 | 8236 | } |
michael@0 | 8237 | }; |
michael@0 | 8238 | |
michael@0 | 8239 | msg.body = null; |
michael@0 | 8240 | msg.data = null; |
michael@0 | 8241 | switch (msg.encoding) { |
michael@0 | 8242 | case PDU_DCS_MSG_CODING_7BITS_ALPHABET: |
michael@0 | 8243 | msg.body = this.readSeptetsToString.call(bufAdapter, |
michael@0 | 8244 | (length * 8 / 7), 0, |
michael@0 | 8245 | PDU_NL_IDENTIFIER_DEFAULT, |
michael@0 | 8246 | PDU_NL_IDENTIFIER_DEFAULT); |
michael@0 | 8247 | if (msg.hasLanguageIndicator) { |
michael@0 | 8248 | msg.language = msg.body.substring(0, 2); |
michael@0 | 8249 | msg.body = msg.body.substring(3); |
michael@0 | 8250 | } |
michael@0 | 8251 | break; |
michael@0 | 8252 | |
michael@0 | 8253 | case PDU_DCS_MSG_CODING_8BITS_ALPHABET: |
michael@0 | 8254 | msg.data = Buf.readUint8Array(length); |
michael@0 | 8255 | break; |
michael@0 | 8256 | |
michael@0 | 8257 | case PDU_DCS_MSG_CODING_16BITS_ALPHABET: |
michael@0 | 8258 | if (msg.hasLanguageIndicator) { |
michael@0 | 8259 | msg.language = this.readSeptetsToString.call(bufAdapter, 2, 0, |
michael@0 | 8260 | PDU_NL_IDENTIFIER_DEFAULT, |
michael@0 | 8261 | PDU_NL_IDENTIFIER_DEFAULT); |
michael@0 | 8262 | length -= 2; |
michael@0 | 8263 | } |
michael@0 | 8264 | msg.body = this.readUCS2String.call(bufAdapter, length); |
michael@0 | 8265 | break; |
michael@0 | 8266 | } |
michael@0 | 8267 | }, |
michael@0 | 8268 | |
michael@0 | 8269 | /** |
michael@0 | 8270 | * Read Cell GSM/ETWS/UMTS Broadcast Message. |
michael@0 | 8271 | * |
michael@0 | 8272 | * @param pduLength |
michael@0 | 8273 | * total length of the incoming PDU in octets. |
michael@0 | 8274 | */ |
michael@0 | 8275 | readCbMessage: function(pduLength) { |
michael@0 | 8276 | // Validity GSM ETWS UMTS |
michael@0 | 8277 | let msg = { |
michael@0 | 8278 | // Internally used in ril_worker: |
michael@0 | 8279 | serial: null, // O O O |
michael@0 | 8280 | updateNumber: null, // O O O |
michael@0 | 8281 | format: null, // O O O |
michael@0 | 8282 | dcs: 0x0F, // O X O |
michael@0 | 8283 | encoding: PDU_DCS_MSG_CODING_7BITS_ALPHABET, // O X O |
michael@0 | 8284 | hasLanguageIndicator: false, // O X O |
michael@0 | 8285 | data: null, // O X O |
michael@0 | 8286 | body: null, // O X O |
michael@0 | 8287 | pageIndex: 1, // O X X |
michael@0 | 8288 | numPages: 1, // O X X |
michael@0 | 8289 | |
michael@0 | 8290 | // DOM attributes: |
michael@0 | 8291 | geographicalScope: null, // O O O |
michael@0 | 8292 | messageCode: null, // O O O |
michael@0 | 8293 | messageId: null, // O O O |
michael@0 | 8294 | language: null, // O X O |
michael@0 | 8295 | fullBody: null, // O X O |
michael@0 | 8296 | fullData: null, // O X O |
michael@0 | 8297 | messageClass: GECKO_SMS_MESSAGE_CLASSES[PDU_DCS_MSG_CLASS_NORMAL], // O x O |
michael@0 | 8298 | etws: null // ? O ? |
michael@0 | 8299 | /*{ |
michael@0 | 8300 | warningType: null, // X O X |
michael@0 | 8301 | popup: false, // X O X |
michael@0 | 8302 | emergencyUserAlert: false, // X O X |
michael@0 | 8303 | }*/ |
michael@0 | 8304 | }; |
michael@0 | 8305 | |
michael@0 | 8306 | if (pduLength <= CB_MESSAGE_SIZE_ETWS) { |
michael@0 | 8307 | msg.format = CB_FORMAT_ETWS; |
michael@0 | 8308 | return this.readEtwsCbMessage(msg); |
michael@0 | 8309 | } |
michael@0 | 8310 | |
michael@0 | 8311 | if (pduLength <= CB_MESSAGE_SIZE_GSM) { |
michael@0 | 8312 | msg.format = CB_FORMAT_GSM; |
michael@0 | 8313 | return this.readGsmCbMessage(msg, pduLength); |
michael@0 | 8314 | } |
michael@0 | 8315 | |
michael@0 | 8316 | return null; |
michael@0 | 8317 | }, |
michael@0 | 8318 | |
michael@0 | 8319 | /** |
michael@0 | 8320 | * Read GSM Cell Broadcast Message. |
michael@0 | 8321 | * |
michael@0 | 8322 | * @param msg |
michael@0 | 8323 | * message object for output. |
michael@0 | 8324 | * @param pduLength |
michael@0 | 8325 | * total length of the incomint PDU in octets. |
michael@0 | 8326 | * |
michael@0 | 8327 | * @see 3GPP TS 23.041 clause 9.4.1.2 |
michael@0 | 8328 | */ |
michael@0 | 8329 | readGsmCbMessage: function(msg, pduLength) { |
michael@0 | 8330 | this.readCbSerialNumber(msg); |
michael@0 | 8331 | this.readCbMessageIdentifier(msg); |
michael@0 | 8332 | this.readCbDataCodingScheme(msg); |
michael@0 | 8333 | this.readCbPageParameter(msg); |
michael@0 | 8334 | |
michael@0 | 8335 | // GSM CB message header takes 6 octets. |
michael@0 | 8336 | this.readGsmCbData(msg, pduLength - 6); |
michael@0 | 8337 | |
michael@0 | 8338 | return msg; |
michael@0 | 8339 | }, |
michael@0 | 8340 | |
michael@0 | 8341 | /** |
michael@0 | 8342 | * Read ETWS Primary Notification Message. |
michael@0 | 8343 | * |
michael@0 | 8344 | * @param msg |
michael@0 | 8345 | * message object for output. |
michael@0 | 8346 | * |
michael@0 | 8347 | * @see 3GPP TS 23.041 clause 9.4.1.3 |
michael@0 | 8348 | */ |
michael@0 | 8349 | readEtwsCbMessage: function(msg) { |
michael@0 | 8350 | this.readCbSerialNumber(msg); |
michael@0 | 8351 | this.readCbMessageIdentifier(msg); |
michael@0 | 8352 | this.readCbWarningType(msg); |
michael@0 | 8353 | |
michael@0 | 8354 | // Octet 7..56 is Warning Security Information. However, according to |
michael@0 | 8355 | // section 9.4.1.3.6, `The UE shall ignore this parameter.` So we just skip |
michael@0 | 8356 | // processing it here. |
michael@0 | 8357 | |
michael@0 | 8358 | return msg; |
michael@0 | 8359 | }, |
michael@0 | 8360 | |
michael@0 | 8361 | /** |
michael@0 | 8362 | * Read network name. |
michael@0 | 8363 | * |
michael@0 | 8364 | * @param len Length of the information element. |
michael@0 | 8365 | * @return |
michael@0 | 8366 | * { |
michael@0 | 8367 | * networkName: network name. |
michael@0 | 8368 | * shouldIncludeCi: Should Country's initials included in text string. |
michael@0 | 8369 | * } |
michael@0 | 8370 | * @see TS 24.008 clause 10.5.3.5a. |
michael@0 | 8371 | */ |
michael@0 | 8372 | readNetworkName: function(len) { |
michael@0 | 8373 | // According to TS 24.008 Sec. 10.5.3.5a, the first octet is: |
michael@0 | 8374 | // bit 8: must be 1. |
michael@0 | 8375 | // bit 5-7: Text encoding. |
michael@0 | 8376 | // 000 - GSM default alphabet. |
michael@0 | 8377 | // 001 - UCS2 (16 bit). |
michael@0 | 8378 | // else - reserved. |
michael@0 | 8379 | // bit 4: MS should add the letters for Country's Initials and a space |
michael@0 | 8380 | // to the text string if this bit is true. |
michael@0 | 8381 | // bit 1-3: number of spare bits in last octet. |
michael@0 | 8382 | |
michael@0 | 8383 | let codingInfo = this.readHexOctet(); |
michael@0 | 8384 | if (!(codingInfo & 0x80)) { |
michael@0 | 8385 | return null; |
michael@0 | 8386 | } |
michael@0 | 8387 | |
michael@0 | 8388 | let textEncoding = (codingInfo & 0x70) >> 4; |
michael@0 | 8389 | let shouldIncludeCountryInitials = !!(codingInfo & 0x08); |
michael@0 | 8390 | let spareBits = codingInfo & 0x07; |
michael@0 | 8391 | let resultString; |
michael@0 | 8392 | |
michael@0 | 8393 | switch (textEncoding) { |
michael@0 | 8394 | case 0: |
michael@0 | 8395 | // GSM Default alphabet. |
michael@0 | 8396 | resultString = this.readSeptetsToString( |
michael@0 | 8397 | ((len - 1) * 8 - spareBits) / 7, 0, |
michael@0 | 8398 | PDU_NL_IDENTIFIER_DEFAULT, |
michael@0 | 8399 | PDU_NL_IDENTIFIER_DEFAULT); |
michael@0 | 8400 | break; |
michael@0 | 8401 | case 1: |
michael@0 | 8402 | // UCS2 encoded. |
michael@0 | 8403 | resultString = this.readUCS2String(len - 1); |
michael@0 | 8404 | break; |
michael@0 | 8405 | default: |
michael@0 | 8406 | // Not an available text coding. |
michael@0 | 8407 | return null; |
michael@0 | 8408 | } |
michael@0 | 8409 | |
michael@0 | 8410 | // TODO - Bug 820286: According to shouldIncludeCountryInitials, add |
michael@0 | 8411 | // country initials to the resulting string. |
michael@0 | 8412 | return resultString; |
michael@0 | 8413 | } |
michael@0 | 8414 | }; |
michael@0 | 8415 | |
michael@0 | 8416 | /** |
michael@0 | 8417 | * Provide buffer with bitwise read/write function so make encoding/decoding easier. |
michael@0 | 8418 | */ |
michael@0 | 8419 | function BitBufferHelperObject(/* unused */aContext) { |
michael@0 | 8420 | this.readBuffer = []; |
michael@0 | 8421 | this.writeBuffer = []; |
michael@0 | 8422 | } |
michael@0 | 8423 | BitBufferHelperObject.prototype = { |
michael@0 | 8424 | readCache: 0, |
michael@0 | 8425 | readCacheSize: 0, |
michael@0 | 8426 | readBuffer: null, |
michael@0 | 8427 | readIndex: 0, |
michael@0 | 8428 | writeCache: 0, |
michael@0 | 8429 | writeCacheSize: 0, |
michael@0 | 8430 | writeBuffer: null, |
michael@0 | 8431 | |
michael@0 | 8432 | // Max length is 32 because we use integer as read/write cache. |
michael@0 | 8433 | // All read/write functions are implemented based on bitwise operation. |
michael@0 | 8434 | readBits: function(length) { |
michael@0 | 8435 | if (length <= 0 || length > 32) { |
michael@0 | 8436 | return null; |
michael@0 | 8437 | } |
michael@0 | 8438 | |
michael@0 | 8439 | if (length > this.readCacheSize) { |
michael@0 | 8440 | let bytesToRead = Math.ceil((length - this.readCacheSize) / 8); |
michael@0 | 8441 | for(let i = 0; i < bytesToRead; i++) { |
michael@0 | 8442 | this.readCache = (this.readCache << 8) | (this.readBuffer[this.readIndex++] & 0xFF); |
michael@0 | 8443 | this.readCacheSize += 8; |
michael@0 | 8444 | } |
michael@0 | 8445 | } |
michael@0 | 8446 | |
michael@0 | 8447 | let bitOffset = (this.readCacheSize - length), |
michael@0 | 8448 | resultMask = (1 << length) - 1, |
michael@0 | 8449 | result = 0; |
michael@0 | 8450 | |
michael@0 | 8451 | result = (this.readCache >> bitOffset) & resultMask; |
michael@0 | 8452 | this.readCacheSize -= length; |
michael@0 | 8453 | |
michael@0 | 8454 | return result; |
michael@0 | 8455 | }, |
michael@0 | 8456 | |
michael@0 | 8457 | backwardReadPilot: function(length) { |
michael@0 | 8458 | if (length <= 0) { |
michael@0 | 8459 | return; |
michael@0 | 8460 | } |
michael@0 | 8461 | |
michael@0 | 8462 | // Zero-based position. |
michael@0 | 8463 | let bitIndexToRead = this.readIndex * 8 - this.readCacheSize - length; |
michael@0 | 8464 | |
michael@0 | 8465 | if (bitIndexToRead < 0) { |
michael@0 | 8466 | return; |
michael@0 | 8467 | } |
michael@0 | 8468 | |
michael@0 | 8469 | // Update readIndex, readCache, readCacheSize accordingly. |
michael@0 | 8470 | let readBits = bitIndexToRead % 8; |
michael@0 | 8471 | this.readIndex = Math.floor(bitIndexToRead / 8) + ((readBits) ? 1 : 0); |
michael@0 | 8472 | this.readCache = (readBits) ? this.readBuffer[this.readIndex - 1] : 0; |
michael@0 | 8473 | this.readCacheSize = (readBits) ? (8 - readBits) : 0; |
michael@0 | 8474 | }, |
michael@0 | 8475 | |
michael@0 | 8476 | writeBits: function(value, length) { |
michael@0 | 8477 | if (length <= 0 || length > 32) { |
michael@0 | 8478 | return; |
michael@0 | 8479 | } |
michael@0 | 8480 | |
michael@0 | 8481 | let totalLength = length + this.writeCacheSize; |
michael@0 | 8482 | |
michael@0 | 8483 | // 8-byte cache not full |
michael@0 | 8484 | if (totalLength < 8) { |
michael@0 | 8485 | let valueMask = (1 << length) - 1; |
michael@0 | 8486 | this.writeCache = (this.writeCache << length) | (value & valueMask); |
michael@0 | 8487 | this.writeCacheSize += length; |
michael@0 | 8488 | return; |
michael@0 | 8489 | } |
michael@0 | 8490 | |
michael@0 | 8491 | // Deal with unaligned part |
michael@0 | 8492 | if (this.writeCacheSize) { |
michael@0 | 8493 | let mergeLength = 8 - this.writeCacheSize, |
michael@0 | 8494 | valueMask = (1 << mergeLength) - 1; |
michael@0 | 8495 | |
michael@0 | 8496 | this.writeCache = (this.writeCache << mergeLength) | ((value >> (length - mergeLength)) & valueMask); |
michael@0 | 8497 | this.writeBuffer.push(this.writeCache & 0xFF); |
michael@0 | 8498 | length -= mergeLength; |
michael@0 | 8499 | } |
michael@0 | 8500 | |
michael@0 | 8501 | // Aligned part, just copy |
michael@0 | 8502 | while (length >= 8) { |
michael@0 | 8503 | length -= 8; |
michael@0 | 8504 | this.writeBuffer.push((value >> length) & 0xFF); |
michael@0 | 8505 | } |
michael@0 | 8506 | |
michael@0 | 8507 | // Rest part is saved into cache |
michael@0 | 8508 | this.writeCacheSize = length; |
michael@0 | 8509 | this.writeCache = value & ((1 << length) - 1); |
michael@0 | 8510 | |
michael@0 | 8511 | return; |
michael@0 | 8512 | }, |
michael@0 | 8513 | |
michael@0 | 8514 | // Drop what still in read cache and goto next 8-byte alignment. |
michael@0 | 8515 | // There might be a better naming. |
michael@0 | 8516 | nextOctetAlign: function() { |
michael@0 | 8517 | this.readCache = 0; |
michael@0 | 8518 | this.readCacheSize = 0; |
michael@0 | 8519 | }, |
michael@0 | 8520 | |
michael@0 | 8521 | // Flush current write cache to Buf with padding 0s. |
michael@0 | 8522 | // There might be a better naming. |
michael@0 | 8523 | flushWithPadding: function() { |
michael@0 | 8524 | if (this.writeCacheSize) { |
michael@0 | 8525 | this.writeBuffer.push(this.writeCache << (8 - this.writeCacheSize)); |
michael@0 | 8526 | } |
michael@0 | 8527 | this.writeCache = 0; |
michael@0 | 8528 | this.writeCacheSize = 0; |
michael@0 | 8529 | }, |
michael@0 | 8530 | |
michael@0 | 8531 | startWrite: function(dataBuffer) { |
michael@0 | 8532 | this.writeBuffer = dataBuffer; |
michael@0 | 8533 | this.writeCache = 0; |
michael@0 | 8534 | this.writeCacheSize = 0; |
michael@0 | 8535 | }, |
michael@0 | 8536 | |
michael@0 | 8537 | startRead: function(dataBuffer) { |
michael@0 | 8538 | this.readBuffer = dataBuffer; |
michael@0 | 8539 | this.readCache = 0; |
michael@0 | 8540 | this.readCacheSize = 0; |
michael@0 | 8541 | this.readIndex = 0; |
michael@0 | 8542 | }, |
michael@0 | 8543 | |
michael@0 | 8544 | getWriteBufferSize: function() { |
michael@0 | 8545 | return this.writeBuffer.length; |
michael@0 | 8546 | }, |
michael@0 | 8547 | |
michael@0 | 8548 | overwriteWriteBuffer: function(position, data) { |
michael@0 | 8549 | let writeLength = data.length; |
michael@0 | 8550 | if (writeLength + position >= this.writeBuffer.length) { |
michael@0 | 8551 | writeLength = this.writeBuffer.length - position; |
michael@0 | 8552 | } |
michael@0 | 8553 | for (let i = 0; i < writeLength; i++) { |
michael@0 | 8554 | this.writeBuffer[i + position] = data[i]; |
michael@0 | 8555 | } |
michael@0 | 8556 | } |
michael@0 | 8557 | }; |
michael@0 | 8558 | |
michael@0 | 8559 | /** |
michael@0 | 8560 | * Helper for CDMA PDU |
michael@0 | 8561 | * |
michael@0 | 8562 | * Currently, some function are shared with GsmPDUHelper, they should be |
michael@0 | 8563 | * moved from GsmPDUHelper to a common object shared among GsmPDUHelper and |
michael@0 | 8564 | * CdmaPDUHelper. |
michael@0 | 8565 | */ |
michael@0 | 8566 | function CdmaPDUHelperObject(aContext) { |
michael@0 | 8567 | this.context = aContext; |
michael@0 | 8568 | } |
michael@0 | 8569 | CdmaPDUHelperObject.prototype = { |
michael@0 | 8570 | context: null, |
michael@0 | 8571 | |
michael@0 | 8572 | // 1..........C |
michael@0 | 8573 | // Only "1234567890*#" is defined in C.S0005-D v2.0 |
michael@0 | 8574 | dtmfChars: ".1234567890*#...", |
michael@0 | 8575 | |
michael@0 | 8576 | /** |
michael@0 | 8577 | * Entry point for SMS encoding, the options object is made compatible |
michael@0 | 8578 | * with existing writeMessage() of GsmPDUHelper, but less key is used. |
michael@0 | 8579 | * |
michael@0 | 8580 | * Current used key in options: |
michael@0 | 8581 | * @param number |
michael@0 | 8582 | * String containing the address (number) of the SMS receiver |
michael@0 | 8583 | * @param body |
michael@0 | 8584 | * String containing the message to be sent, segmented part |
michael@0 | 8585 | * @param dcs |
michael@0 | 8586 | * Data coding scheme. One of the PDU_DCS_MSG_CODING_*BITS_ALPHABET |
michael@0 | 8587 | * constants. |
michael@0 | 8588 | * @param encodedBodyLength |
michael@0 | 8589 | * Length of the user data when encoded with the given DCS. For UCS2, |
michael@0 | 8590 | * in bytes; for 7-bit, in septets. |
michael@0 | 8591 | * @param requestStatusReport |
michael@0 | 8592 | * Request status report. |
michael@0 | 8593 | * @param segmentRef |
michael@0 | 8594 | * Reference number of concatenated SMS message |
michael@0 | 8595 | * @param segmentMaxSeq |
michael@0 | 8596 | * Total number of concatenated SMS message |
michael@0 | 8597 | * @param segmentSeq |
michael@0 | 8598 | * Sequence number of concatenated SMS message |
michael@0 | 8599 | */ |
michael@0 | 8600 | writeMessage: function(options) { |
michael@0 | 8601 | if (DEBUG) { |
michael@0 | 8602 | this.context.debug("cdma_writeMessage: " + JSON.stringify(options)); |
michael@0 | 8603 | } |
michael@0 | 8604 | |
michael@0 | 8605 | // Get encoding |
michael@0 | 8606 | options.encoding = this.gsmDcsToCdmaEncoding(options.dcs); |
michael@0 | 8607 | |
michael@0 | 8608 | // Common Header |
michael@0 | 8609 | if (options.segmentMaxSeq > 1) { |
michael@0 | 8610 | this.writeInt(PDU_CDMA_MSG_TELESERIVCIE_ID_WEMT); |
michael@0 | 8611 | } else { |
michael@0 | 8612 | this.writeInt(PDU_CDMA_MSG_TELESERIVCIE_ID_SMS); |
michael@0 | 8613 | } |
michael@0 | 8614 | |
michael@0 | 8615 | this.writeInt(0); |
michael@0 | 8616 | this.writeInt(PDU_CDMA_MSG_CATEGORY_UNSPEC); |
michael@0 | 8617 | |
michael@0 | 8618 | // Just fill out address info in byte, rild will encap them for us |
michael@0 | 8619 | let addrInfo = this.encodeAddr(options.number); |
michael@0 | 8620 | this.writeByte(addrInfo.digitMode); |
michael@0 | 8621 | this.writeByte(addrInfo.numberMode); |
michael@0 | 8622 | this.writeByte(addrInfo.numberType); |
michael@0 | 8623 | this.writeByte(addrInfo.numberPlan); |
michael@0 | 8624 | this.writeByte(addrInfo.address.length); |
michael@0 | 8625 | for (let i = 0; i < addrInfo.address.length; i++) { |
michael@0 | 8626 | this.writeByte(addrInfo.address[i]); |
michael@0 | 8627 | } |
michael@0 | 8628 | |
michael@0 | 8629 | // Subaddress, not supported |
michael@0 | 8630 | this.writeByte(0); // Subaddress : Type |
michael@0 | 8631 | this.writeByte(0); // Subaddress : Odd |
michael@0 | 8632 | this.writeByte(0); // Subaddress : length |
michael@0 | 8633 | |
michael@0 | 8634 | // User Data |
michael@0 | 8635 | let encodeResult = this.encodeUserData(options); |
michael@0 | 8636 | this.writeByte(encodeResult.length); |
michael@0 | 8637 | for (let i = 0; i < encodeResult.length; i++) { |
michael@0 | 8638 | this.writeByte(encodeResult[i]); |
michael@0 | 8639 | } |
michael@0 | 8640 | |
michael@0 | 8641 | encodeResult = null; |
michael@0 | 8642 | }, |
michael@0 | 8643 | |
michael@0 | 8644 | /** |
michael@0 | 8645 | * Data writters |
michael@0 | 8646 | */ |
michael@0 | 8647 | writeInt: function(value) { |
michael@0 | 8648 | this.context.Buf.writeInt32(value); |
michael@0 | 8649 | }, |
michael@0 | 8650 | |
michael@0 | 8651 | writeByte: function(value) { |
michael@0 | 8652 | this.context.Buf.writeInt32(value & 0xFF); |
michael@0 | 8653 | }, |
michael@0 | 8654 | |
michael@0 | 8655 | /** |
michael@0 | 8656 | * Transform GSM DCS to CDMA encoding. |
michael@0 | 8657 | */ |
michael@0 | 8658 | gsmDcsToCdmaEncoding: function(encoding) { |
michael@0 | 8659 | switch (encoding) { |
michael@0 | 8660 | case PDU_DCS_MSG_CODING_7BITS_ALPHABET: |
michael@0 | 8661 | return PDU_CDMA_MSG_CODING_7BITS_ASCII; |
michael@0 | 8662 | case PDU_DCS_MSG_CODING_8BITS_ALPHABET: |
michael@0 | 8663 | return PDU_CDMA_MSG_CODING_OCTET; |
michael@0 | 8664 | case PDU_DCS_MSG_CODING_16BITS_ALPHABET: |
michael@0 | 8665 | return PDU_CDMA_MSG_CODING_UNICODE; |
michael@0 | 8666 | } |
michael@0 | 8667 | throw new Error("gsmDcsToCdmaEncoding(): Invalid GSM SMS DCS value: " + encoding); |
michael@0 | 8668 | }, |
michael@0 | 8669 | |
michael@0 | 8670 | /** |
michael@0 | 8671 | * Encode address into CDMA address format, as a byte array. |
michael@0 | 8672 | * |
michael@0 | 8673 | * @see 3GGP2 C.S0015-B 2.0, 3.4.3.3 Address Parameters |
michael@0 | 8674 | * |
michael@0 | 8675 | * @param address |
michael@0 | 8676 | * String of address to be encoded |
michael@0 | 8677 | */ |
michael@0 | 8678 | encodeAddr: function(address) { |
michael@0 | 8679 | let result = {}; |
michael@0 | 8680 | |
michael@0 | 8681 | result.numberType = PDU_CDMA_MSG_ADDR_NUMBER_TYPE_UNKNOWN; |
michael@0 | 8682 | result.numberPlan = PDU_CDMA_MSG_ADDR_NUMBER_TYPE_UNKNOWN; |
michael@0 | 8683 | |
michael@0 | 8684 | if (address[0] === '+') { |
michael@0 | 8685 | address = address.substring(1); |
michael@0 | 8686 | } |
michael@0 | 8687 | |
michael@0 | 8688 | // Try encode with DTMF first |
michael@0 | 8689 | result.digitMode = PDU_CDMA_MSG_ADDR_DIGIT_MODE_DTMF; |
michael@0 | 8690 | result.numberMode = PDU_CDMA_MSG_ADDR_NUMBER_MODE_ANSI; |
michael@0 | 8691 | |
michael@0 | 8692 | result.address = []; |
michael@0 | 8693 | for (let i = 0; i < address.length; i++) { |
michael@0 | 8694 | let addrDigit = this.dtmfChars.indexOf(address.charAt(i)); |
michael@0 | 8695 | if (addrDigit < 0) { |
michael@0 | 8696 | result.digitMode = PDU_CDMA_MSG_ADDR_DIGIT_MODE_ASCII; |
michael@0 | 8697 | result.numberMode = PDU_CDMA_MSG_ADDR_NUMBER_MODE_ASCII; |
michael@0 | 8698 | result.address = []; |
michael@0 | 8699 | break; |
michael@0 | 8700 | } |
michael@0 | 8701 | result.address.push(addrDigit); |
michael@0 | 8702 | } |
michael@0 | 8703 | |
michael@0 | 8704 | // Address can't be encoded with DTMF, then use 7-bit ASCII |
michael@0 | 8705 | if (result.digitMode !== PDU_CDMA_MSG_ADDR_DIGIT_MODE_DTMF) { |
michael@0 | 8706 | if (address.indexOf("@") !== -1) { |
michael@0 | 8707 | result.numberType = PDU_CDMA_MSG_ADDR_NUMBER_TYPE_NATIONAL; |
michael@0 | 8708 | } |
michael@0 | 8709 | |
michael@0 | 8710 | for (let i = 0; i < address.length; i++) { |
michael@0 | 8711 | result.address.push(address.charCodeAt(i) & 0x7F); |
michael@0 | 8712 | } |
michael@0 | 8713 | } |
michael@0 | 8714 | |
michael@0 | 8715 | return result; |
michael@0 | 8716 | }, |
michael@0 | 8717 | |
michael@0 | 8718 | /** |
michael@0 | 8719 | * Encode SMS contents in options into CDMA userData field. |
michael@0 | 8720 | * Corresponding and required subparameters will be added automatically. |
michael@0 | 8721 | * |
michael@0 | 8722 | * @see 3GGP2 C.S0015-B 2.0, 3.4.3.7 Bearer Data |
michael@0 | 8723 | * 4.5 Bearer Data Parameters |
michael@0 | 8724 | * |
michael@0 | 8725 | * Current used key in options: |
michael@0 | 8726 | * @param body |
michael@0 | 8727 | * String containing the message to be sent, segmented part |
michael@0 | 8728 | * @param encoding |
michael@0 | 8729 | * Encoding method of CDMA, can be transformed from GSM DCS by function |
michael@0 | 8730 | * cdmaPduHelp.gsmDcsToCdmaEncoding() |
michael@0 | 8731 | * @param encodedBodyLength |
michael@0 | 8732 | * Length of the user data when encoded with the given DCS. For UCS2, |
michael@0 | 8733 | * in bytes; for 7-bit, in septets. |
michael@0 | 8734 | * @param requestStatusReport |
michael@0 | 8735 | * Request status report. |
michael@0 | 8736 | * @param segmentRef |
michael@0 | 8737 | * Reference number of concatenated SMS message |
michael@0 | 8738 | * @param segmentMaxSeq |
michael@0 | 8739 | * Total number of concatenated SMS message |
michael@0 | 8740 | * @param segmentSeq |
michael@0 | 8741 | * Sequence number of concatenated SMS message |
michael@0 | 8742 | */ |
michael@0 | 8743 | encodeUserData: function(options) { |
michael@0 | 8744 | let userDataBuffer = []; |
michael@0 | 8745 | this.context.BitBufferHelper.startWrite(userDataBuffer); |
michael@0 | 8746 | |
michael@0 | 8747 | // Message Identifier |
michael@0 | 8748 | this.encodeUserDataMsgId(options); |
michael@0 | 8749 | |
michael@0 | 8750 | // User Data |
michael@0 | 8751 | this.encodeUserDataMsg(options); |
michael@0 | 8752 | |
michael@0 | 8753 | // Reply Option |
michael@0 | 8754 | this.encodeUserDataReplyOption(options); |
michael@0 | 8755 | |
michael@0 | 8756 | return userDataBuffer; |
michael@0 | 8757 | }, |
michael@0 | 8758 | |
michael@0 | 8759 | /** |
michael@0 | 8760 | * User data subparameter encoder : Message Identifier |
michael@0 | 8761 | * |
michael@0 | 8762 | * @see 3GGP2 C.S0015-B 2.0, 4.5.1 Message Identifier |
michael@0 | 8763 | */ |
michael@0 | 8764 | encodeUserDataMsgId: function(options) { |
michael@0 | 8765 | let BitBufferHelper = this.context.BitBufferHelper; |
michael@0 | 8766 | BitBufferHelper.writeBits(PDU_CDMA_MSG_USERDATA_MSG_ID, 8); |
michael@0 | 8767 | BitBufferHelper.writeBits(3, 8); |
michael@0 | 8768 | BitBufferHelper.writeBits(PDU_CDMA_MSG_TYPE_SUBMIT, 4); |
michael@0 | 8769 | BitBufferHelper.writeBits(1, 16); // TODO: How to get message ID? |
michael@0 | 8770 | if (options.segmentMaxSeq > 1) { |
michael@0 | 8771 | BitBufferHelper.writeBits(1, 1); |
michael@0 | 8772 | } else { |
michael@0 | 8773 | BitBufferHelper.writeBits(0, 1); |
michael@0 | 8774 | } |
michael@0 | 8775 | |
michael@0 | 8776 | BitBufferHelper.flushWithPadding(); |
michael@0 | 8777 | }, |
michael@0 | 8778 | |
michael@0 | 8779 | /** |
michael@0 | 8780 | * User data subparameter encoder : User Data |
michael@0 | 8781 | * |
michael@0 | 8782 | * @see 3GGP2 C.S0015-B 2.0, 4.5.2 User Data |
michael@0 | 8783 | */ |
michael@0 | 8784 | encodeUserDataMsg: function(options) { |
michael@0 | 8785 | let BitBufferHelper = this.context.BitBufferHelper; |
michael@0 | 8786 | BitBufferHelper.writeBits(PDU_CDMA_MSG_USERDATA_BODY, 8); |
michael@0 | 8787 | // Reserve space for length |
michael@0 | 8788 | BitBufferHelper.writeBits(0, 8); |
michael@0 | 8789 | let lengthPosition = BitBufferHelper.getWriteBufferSize(); |
michael@0 | 8790 | |
michael@0 | 8791 | BitBufferHelper.writeBits(options.encoding, 5); |
michael@0 | 8792 | |
michael@0 | 8793 | // Add user data header for message segement |
michael@0 | 8794 | let msgBody = options.body, |
michael@0 | 8795 | msgBodySize = (options.encoding === PDU_CDMA_MSG_CODING_7BITS_ASCII ? |
michael@0 | 8796 | options.encodedBodyLength : |
michael@0 | 8797 | msgBody.length); |
michael@0 | 8798 | if (options.segmentMaxSeq > 1) { |
michael@0 | 8799 | if (options.encoding === PDU_CDMA_MSG_CODING_7BITS_ASCII) { |
michael@0 | 8800 | BitBufferHelper.writeBits(msgBodySize + 7, 8); // Required length for user data header, in septet(7-bit) |
michael@0 | 8801 | |
michael@0 | 8802 | BitBufferHelper.writeBits(5, 8); // total header length 5 bytes |
michael@0 | 8803 | BitBufferHelper.writeBits(PDU_IEI_CONCATENATED_SHORT_MESSAGES_8BIT, 8); // header id 0 |
michael@0 | 8804 | BitBufferHelper.writeBits(3, 8); // length of element for id 0 is 3 |
michael@0 | 8805 | BitBufferHelper.writeBits(options.segmentRef & 0xFF, 8); // Segement reference |
michael@0 | 8806 | BitBufferHelper.writeBits(options.segmentMaxSeq & 0xFF, 8); // Max segment |
michael@0 | 8807 | BitBufferHelper.writeBits(options.segmentSeq & 0xFF, 8); // Current segment |
michael@0 | 8808 | BitBufferHelper.writeBits(0, 1); // Padding to make header data septet(7-bit) aligned |
michael@0 | 8809 | } else { |
michael@0 | 8810 | if (options.encoding === PDU_CDMA_MSG_CODING_UNICODE) { |
michael@0 | 8811 | BitBufferHelper.writeBits(msgBodySize + 3, 8); // Required length for user data header, in 16-bit |
michael@0 | 8812 | } else { |
michael@0 | 8813 | BitBufferHelper.writeBits(msgBodySize + 6, 8); // Required length for user data header, in octet(8-bit) |
michael@0 | 8814 | } |
michael@0 | 8815 | |
michael@0 | 8816 | BitBufferHelper.writeBits(5, 8); // total header length 5 bytes |
michael@0 | 8817 | BitBufferHelper.writeBits(PDU_IEI_CONCATENATED_SHORT_MESSAGES_8BIT, 8); // header id 0 |
michael@0 | 8818 | BitBufferHelper.writeBits(3, 8); // length of element for id 0 is 3 |
michael@0 | 8819 | BitBufferHelper.writeBits(options.segmentRef & 0xFF, 8); // Segement reference |
michael@0 | 8820 | BitBufferHelper.writeBits(options.segmentMaxSeq & 0xFF, 8); // Max segment |
michael@0 | 8821 | BitBufferHelper.writeBits(options.segmentSeq & 0xFF, 8); // Current segment |
michael@0 | 8822 | } |
michael@0 | 8823 | } else { |
michael@0 | 8824 | BitBufferHelper.writeBits(msgBodySize, 8); |
michael@0 | 8825 | } |
michael@0 | 8826 | |
michael@0 | 8827 | // Encode message based on encoding method |
michael@0 | 8828 | const langTable = PDU_NL_LOCKING_SHIFT_TABLES[PDU_NL_IDENTIFIER_DEFAULT]; |
michael@0 | 8829 | const langShiftTable = PDU_NL_SINGLE_SHIFT_TABLES[PDU_NL_IDENTIFIER_DEFAULT]; |
michael@0 | 8830 | for (let i = 0; i < msgBody.length; i++) { |
michael@0 | 8831 | switch (options.encoding) { |
michael@0 | 8832 | case PDU_CDMA_MSG_CODING_OCTET: { |
michael@0 | 8833 | let msgDigit = msgBody.charCodeAt(i); |
michael@0 | 8834 | BitBufferHelper.writeBits(msgDigit, 8); |
michael@0 | 8835 | break; |
michael@0 | 8836 | } |
michael@0 | 8837 | case PDU_CDMA_MSG_CODING_7BITS_ASCII: { |
michael@0 | 8838 | let msgDigit = msgBody.charCodeAt(i), |
michael@0 | 8839 | msgDigitChar = msgBody.charAt(i); |
michael@0 | 8840 | |
michael@0 | 8841 | if (msgDigit >= 32) { |
michael@0 | 8842 | BitBufferHelper.writeBits(msgDigit, 7); |
michael@0 | 8843 | } else { |
michael@0 | 8844 | msgDigit = langTable.indexOf(msgDigitChar); |
michael@0 | 8845 | |
michael@0 | 8846 | if (msgDigit === PDU_NL_EXTENDED_ESCAPE) { |
michael@0 | 8847 | break; |
michael@0 | 8848 | } |
michael@0 | 8849 | if (msgDigit >= 0) { |
michael@0 | 8850 | BitBufferHelper.writeBits(msgDigit, 7); |
michael@0 | 8851 | } else { |
michael@0 | 8852 | msgDigit = langShiftTable.indexOf(msgDigitChar); |
michael@0 | 8853 | if (msgDigit == -1) { |
michael@0 | 8854 | throw new Error("'" + msgDigitChar + "' is not in 7 bit alphabet " |
michael@0 | 8855 | + langIndex + ":" + langShiftIndex + "!"); |
michael@0 | 8856 | } |
michael@0 | 8857 | |
michael@0 | 8858 | if (msgDigit === PDU_NL_RESERVED_CONTROL) { |
michael@0 | 8859 | break; |
michael@0 | 8860 | } |
michael@0 | 8861 | |
michael@0 | 8862 | BitBufferHelper.writeBits(PDU_NL_EXTENDED_ESCAPE, 7); |
michael@0 | 8863 | BitBufferHelper.writeBits(msgDigit, 7); |
michael@0 | 8864 | } |
michael@0 | 8865 | } |
michael@0 | 8866 | break; |
michael@0 | 8867 | } |
michael@0 | 8868 | case PDU_CDMA_MSG_CODING_UNICODE: { |
michael@0 | 8869 | let msgDigit = msgBody.charCodeAt(i); |
michael@0 | 8870 | BitBufferHelper.writeBits(msgDigit, 16); |
michael@0 | 8871 | break; |
michael@0 | 8872 | } |
michael@0 | 8873 | } |
michael@0 | 8874 | } |
michael@0 | 8875 | BitBufferHelper.flushWithPadding(); |
michael@0 | 8876 | |
michael@0 | 8877 | // Fill length |
michael@0 | 8878 | let currentPosition = BitBufferHelper.getWriteBufferSize(); |
michael@0 | 8879 | BitBufferHelper.overwriteWriteBuffer(lengthPosition - 1, [currentPosition - lengthPosition]); |
michael@0 | 8880 | }, |
michael@0 | 8881 | |
michael@0 | 8882 | /** |
michael@0 | 8883 | * User data subparameter encoder : Reply Option |
michael@0 | 8884 | * |
michael@0 | 8885 | * @see 3GGP2 C.S0015-B 2.0, 4.5.11 Reply Option |
michael@0 | 8886 | */ |
michael@0 | 8887 | encodeUserDataReplyOption: function(options) { |
michael@0 | 8888 | if (options.requestStatusReport) { |
michael@0 | 8889 | let BitBufferHelper = this.context.BitBufferHelper; |
michael@0 | 8890 | BitBufferHelper.writeBits(PDU_CDMA_MSG_USERDATA_REPLY_OPTION, 8); |
michael@0 | 8891 | BitBufferHelper.writeBits(1, 8); |
michael@0 | 8892 | BitBufferHelper.writeBits(0, 1); // USER_ACK_REQ |
michael@0 | 8893 | BitBufferHelper.writeBits(1, 1); // DAK_REQ |
michael@0 | 8894 | BitBufferHelper.flushWithPadding(); |
michael@0 | 8895 | } |
michael@0 | 8896 | }, |
michael@0 | 8897 | |
michael@0 | 8898 | /** |
michael@0 | 8899 | * Entry point for SMS decoding, the returned object is made compatible |
michael@0 | 8900 | * with existing readMessage() of GsmPDUHelper |
michael@0 | 8901 | */ |
michael@0 | 8902 | readMessage: function() { |
michael@0 | 8903 | let message = {}; |
michael@0 | 8904 | |
michael@0 | 8905 | // Teleservice Identifier |
michael@0 | 8906 | message.teleservice = this.readInt(); |
michael@0 | 8907 | |
michael@0 | 8908 | // Message Type |
michael@0 | 8909 | let isServicePresent = this.readByte(); |
michael@0 | 8910 | if (isServicePresent) { |
michael@0 | 8911 | message.messageType = PDU_CDMA_MSG_TYPE_BROADCAST; |
michael@0 | 8912 | } else { |
michael@0 | 8913 | if (message.teleservice) { |
michael@0 | 8914 | message.messageType = PDU_CDMA_MSG_TYPE_P2P; |
michael@0 | 8915 | } else { |
michael@0 | 8916 | message.messageType = PDU_CDMA_MSG_TYPE_ACK; |
michael@0 | 8917 | } |
michael@0 | 8918 | } |
michael@0 | 8919 | |
michael@0 | 8920 | // Service Category |
michael@0 | 8921 | message.service = this.readInt(); |
michael@0 | 8922 | |
michael@0 | 8923 | // Originated Address |
michael@0 | 8924 | let addrInfo = {}; |
michael@0 | 8925 | addrInfo.digitMode = (this.readInt() & 0x01); |
michael@0 | 8926 | addrInfo.numberMode = (this.readInt() & 0x01); |
michael@0 | 8927 | addrInfo.numberType = (this.readInt() & 0x01); |
michael@0 | 8928 | addrInfo.numberPlan = (this.readInt() & 0x01); |
michael@0 | 8929 | addrInfo.addrLength = this.readByte(); |
michael@0 | 8930 | addrInfo.address = []; |
michael@0 | 8931 | for (let i = 0; i < addrInfo.addrLength; i++) { |
michael@0 | 8932 | addrInfo.address.push(this.readByte()); |
michael@0 | 8933 | } |
michael@0 | 8934 | message.sender = this.decodeAddr(addrInfo); |
michael@0 | 8935 | |
michael@0 | 8936 | // Originated Subaddress |
michael@0 | 8937 | addrInfo.Type = (this.readInt() & 0x07); |
michael@0 | 8938 | addrInfo.Odd = (this.readByte() & 0x01); |
michael@0 | 8939 | addrInfo.addrLength = this.readByte(); |
michael@0 | 8940 | for (let i = 0; i < addrInfo.addrLength; i++) { |
michael@0 | 8941 | let addrDigit = this.readByte(); |
michael@0 | 8942 | message.sender += String.fromCharCode(addrDigit); |
michael@0 | 8943 | } |
michael@0 | 8944 | |
michael@0 | 8945 | // Bearer Data |
michael@0 | 8946 | this.decodeUserData(message); |
michael@0 | 8947 | |
michael@0 | 8948 | // Bearer Data Sub-Parameter: User Data |
michael@0 | 8949 | let userData = message[PDU_CDMA_MSG_USERDATA_BODY]; |
michael@0 | 8950 | [message.header, message.body, message.encoding, message.data] = |
michael@0 | 8951 | (userData) ? [userData.header, userData.body, userData.encoding, userData.data] |
michael@0 | 8952 | : [null, null, null, null]; |
michael@0 | 8953 | |
michael@0 | 8954 | // Bearer Data Sub-Parameter: Message Status |
michael@0 | 8955 | // Success Delivery (0) if both Message Status and User Data are absent. |
michael@0 | 8956 | // Message Status absent (-1) if only User Data is available. |
michael@0 | 8957 | let msgStatus = message[PDU_CDMA_MSG_USER_DATA_MSG_STATUS]; |
michael@0 | 8958 | [message.errorClass, message.msgStatus] = |
michael@0 | 8959 | (msgStatus) ? [msgStatus.errorClass, msgStatus.msgStatus] |
michael@0 | 8960 | : ((message.body) ? [-1, -1] : [0, 0]); |
michael@0 | 8961 | |
michael@0 | 8962 | // Transform message to GSM msg |
michael@0 | 8963 | let msg = { |
michael@0 | 8964 | SMSC: "", |
michael@0 | 8965 | mti: 0, |
michael@0 | 8966 | udhi: 0, |
michael@0 | 8967 | sender: message.sender, |
michael@0 | 8968 | recipient: null, |
michael@0 | 8969 | pid: PDU_PID_DEFAULT, |
michael@0 | 8970 | epid: PDU_PID_DEFAULT, |
michael@0 | 8971 | dcs: 0, |
michael@0 | 8972 | mwi: null, |
michael@0 | 8973 | replace: false, |
michael@0 | 8974 | header: message.header, |
michael@0 | 8975 | body: message.body, |
michael@0 | 8976 | data: message.data, |
michael@0 | 8977 | sentTimestamp: message[PDU_CDMA_MSG_USERDATA_TIMESTAMP], |
michael@0 | 8978 | language: message[PDU_CDMA_LANGUAGE_INDICATOR], |
michael@0 | 8979 | status: null, |
michael@0 | 8980 | scts: null, |
michael@0 | 8981 | dt: null, |
michael@0 | 8982 | encoding: message.encoding, |
michael@0 | 8983 | messageClass: GECKO_SMS_MESSAGE_CLASSES[PDU_DCS_MSG_CLASS_NORMAL], |
michael@0 | 8984 | messageType: message.messageType, |
michael@0 | 8985 | serviceCategory: message.service, |
michael@0 | 8986 | subMsgType: message[PDU_CDMA_MSG_USERDATA_MSG_ID].msgType, |
michael@0 | 8987 | msgId: message[PDU_CDMA_MSG_USERDATA_MSG_ID].msgId, |
michael@0 | 8988 | errorClass: message.errorClass, |
michael@0 | 8989 | msgStatus: message.msgStatus, |
michael@0 | 8990 | teleservice: message.teleservice |
michael@0 | 8991 | }; |
michael@0 | 8992 | |
michael@0 | 8993 | return msg; |
michael@0 | 8994 | }, |
michael@0 | 8995 | |
michael@0 | 8996 | /** |
michael@0 | 8997 | * Helper for processing received SMS parcel data. |
michael@0 | 8998 | * |
michael@0 | 8999 | * @param length |
michael@0 | 9000 | * Length of SMS string in the incoming parcel. |
michael@0 | 9001 | * |
michael@0 | 9002 | * @return Message parsed or null for invalid message. |
michael@0 | 9003 | */ |
michael@0 | 9004 | processReceivedSms: function(length) { |
michael@0 | 9005 | if (!length) { |
michael@0 | 9006 | if (DEBUG) this.context.debug("Received empty SMS!"); |
michael@0 | 9007 | return [null, PDU_FCS_UNSPECIFIED]; |
michael@0 | 9008 | } |
michael@0 | 9009 | |
michael@0 | 9010 | let message = this.readMessage(); |
michael@0 | 9011 | if (DEBUG) this.context.debug("Got new SMS: " + JSON.stringify(message)); |
michael@0 | 9012 | |
michael@0 | 9013 | // Determine result |
michael@0 | 9014 | if (!message) { |
michael@0 | 9015 | return [null, PDU_FCS_UNSPECIFIED]; |
michael@0 | 9016 | } |
michael@0 | 9017 | |
michael@0 | 9018 | return [message, PDU_FCS_OK]; |
michael@0 | 9019 | }, |
michael@0 | 9020 | |
michael@0 | 9021 | /** |
michael@0 | 9022 | * Data readers |
michael@0 | 9023 | */ |
michael@0 | 9024 | readInt: function() { |
michael@0 | 9025 | return this.context.Buf.readInt32(); |
michael@0 | 9026 | }, |
michael@0 | 9027 | |
michael@0 | 9028 | readByte: function() { |
michael@0 | 9029 | return (this.context.Buf.readInt32() & 0xFF); |
michael@0 | 9030 | }, |
michael@0 | 9031 | |
michael@0 | 9032 | /** |
michael@0 | 9033 | * Decode CDMA address data into address string |
michael@0 | 9034 | * |
michael@0 | 9035 | * @see 3GGP2 C.S0015-B 2.0, 3.4.3.3 Address Parameters |
michael@0 | 9036 | * |
michael@0 | 9037 | * Required key in addrInfo |
michael@0 | 9038 | * @param addrLength |
michael@0 | 9039 | * Length of address |
michael@0 | 9040 | * @param digitMode |
michael@0 | 9041 | * Address encoding method |
michael@0 | 9042 | * @param address |
michael@0 | 9043 | * Array of encoded address data |
michael@0 | 9044 | */ |
michael@0 | 9045 | decodeAddr: function(addrInfo) { |
michael@0 | 9046 | let result = ""; |
michael@0 | 9047 | for (let i = 0; i < addrInfo.addrLength; i++) { |
michael@0 | 9048 | if (addrInfo.digitMode === PDU_CDMA_MSG_ADDR_DIGIT_MODE_DTMF) { |
michael@0 | 9049 | result += this.dtmfChars.charAt(addrInfo.address[i]); |
michael@0 | 9050 | } else { |
michael@0 | 9051 | result += String.fromCharCode(addrInfo.address[i]); |
michael@0 | 9052 | } |
michael@0 | 9053 | } |
michael@0 | 9054 | return result; |
michael@0 | 9055 | }, |
michael@0 | 9056 | |
michael@0 | 9057 | /** |
michael@0 | 9058 | * Read userData in parcel buffer and decode into message object. |
michael@0 | 9059 | * Each subparameter will be stored in corresponding key. |
michael@0 | 9060 | * |
michael@0 | 9061 | * @see 3GGP2 C.S0015-B 2.0, 3.4.3.7 Bearer Data |
michael@0 | 9062 | * 4.5 Bearer Data Parameters |
michael@0 | 9063 | */ |
michael@0 | 9064 | decodeUserData: function(message) { |
michael@0 | 9065 | let userDataLength = this.readInt(); |
michael@0 | 9066 | |
michael@0 | 9067 | while (userDataLength > 0) { |
michael@0 | 9068 | let id = this.readByte(), |
michael@0 | 9069 | length = this.readByte(), |
michael@0 | 9070 | userDataBuffer = []; |
michael@0 | 9071 | |
michael@0 | 9072 | for (let i = 0; i < length; i++) { |
michael@0 | 9073 | userDataBuffer.push(this.readByte()); |
michael@0 | 9074 | } |
michael@0 | 9075 | |
michael@0 | 9076 | this.context.BitBufferHelper.startRead(userDataBuffer); |
michael@0 | 9077 | |
michael@0 | 9078 | switch (id) { |
michael@0 | 9079 | case PDU_CDMA_MSG_USERDATA_MSG_ID: |
michael@0 | 9080 | message[id] = this.decodeUserDataMsgId(); |
michael@0 | 9081 | break; |
michael@0 | 9082 | case PDU_CDMA_MSG_USERDATA_BODY: |
michael@0 | 9083 | message[id] = this.decodeUserDataMsg(message[PDU_CDMA_MSG_USERDATA_MSG_ID].userHeader); |
michael@0 | 9084 | break; |
michael@0 | 9085 | case PDU_CDMA_MSG_USERDATA_TIMESTAMP: |
michael@0 | 9086 | message[id] = this.decodeUserDataTimestamp(); |
michael@0 | 9087 | break; |
michael@0 | 9088 | case PDU_CDMA_MSG_USERDATA_REPLY_OPTION: |
michael@0 | 9089 | message[id] = this.decodeUserDataReplyOption(); |
michael@0 | 9090 | break; |
michael@0 | 9091 | case PDU_CDMA_LANGUAGE_INDICATOR: |
michael@0 | 9092 | message[id] = this.decodeLanguageIndicator(); |
michael@0 | 9093 | break; |
michael@0 | 9094 | case PDU_CDMA_MSG_USERDATA_CALLBACK_NUMBER: |
michael@0 | 9095 | message[id] = this.decodeUserDataCallbackNumber(); |
michael@0 | 9096 | break; |
michael@0 | 9097 | case PDU_CDMA_MSG_USER_DATA_MSG_STATUS: |
michael@0 | 9098 | message[id] = this.decodeUserDataMsgStatus(); |
michael@0 | 9099 | break; |
michael@0 | 9100 | } |
michael@0 | 9101 | |
michael@0 | 9102 | userDataLength -= (length + 2); |
michael@0 | 9103 | userDataBuffer = []; |
michael@0 | 9104 | } |
michael@0 | 9105 | }, |
michael@0 | 9106 | |
michael@0 | 9107 | /** |
michael@0 | 9108 | * User data subparameter decoder: Message Identifier |
michael@0 | 9109 | * |
michael@0 | 9110 | * @see 3GGP2 C.S0015-B 2.0, 4.5.1 Message Identifier |
michael@0 | 9111 | */ |
michael@0 | 9112 | decodeUserDataMsgId: function() { |
michael@0 | 9113 | let result = {}; |
michael@0 | 9114 | let BitBufferHelper = this.context.BitBufferHelper; |
michael@0 | 9115 | result.msgType = BitBufferHelper.readBits(4); |
michael@0 | 9116 | result.msgId = BitBufferHelper.readBits(16); |
michael@0 | 9117 | result.userHeader = BitBufferHelper.readBits(1); |
michael@0 | 9118 | |
michael@0 | 9119 | return result; |
michael@0 | 9120 | }, |
michael@0 | 9121 | |
michael@0 | 9122 | /** |
michael@0 | 9123 | * Decode user data header, we only care about segment information |
michael@0 | 9124 | * on CDMA. |
michael@0 | 9125 | * |
michael@0 | 9126 | * This function is mostly copied from gsmPduHelper.readUserDataHeader() but |
michael@0 | 9127 | * change the read function, because CDMA user header decoding is't byte-wise |
michael@0 | 9128 | * aligned. |
michael@0 | 9129 | */ |
michael@0 | 9130 | decodeUserDataHeader: function(encoding) { |
michael@0 | 9131 | let BitBufferHelper = this.context.BitBufferHelper; |
michael@0 | 9132 | let header = {}, |
michael@0 | 9133 | headerSize = BitBufferHelper.readBits(8), |
michael@0 | 9134 | userDataHeaderSize = headerSize + 1, |
michael@0 | 9135 | headerPaddingBits = 0; |
michael@0 | 9136 | |
michael@0 | 9137 | // Calculate header size |
michael@0 | 9138 | if (encoding === PDU_DCS_MSG_CODING_7BITS_ALPHABET) { |
michael@0 | 9139 | // Length is in 7-bit |
michael@0 | 9140 | header.length = Math.ceil(userDataHeaderSize * 8 / 7); |
michael@0 | 9141 | // Calulate padding length |
michael@0 | 9142 | headerPaddingBits = (header.length * 7) - (userDataHeaderSize * 8); |
michael@0 | 9143 | } else if (encoding === PDU_DCS_MSG_CODING_8BITS_ALPHABET) { |
michael@0 | 9144 | header.length = userDataHeaderSize; |
michael@0 | 9145 | } else { |
michael@0 | 9146 | header.length = userDataHeaderSize / 2; |
michael@0 | 9147 | } |
michael@0 | 9148 | |
michael@0 | 9149 | while (headerSize) { |
michael@0 | 9150 | let identifier = BitBufferHelper.readBits(8), |
michael@0 | 9151 | length = BitBufferHelper.readBits(8); |
michael@0 | 9152 | |
michael@0 | 9153 | headerSize -= (2 + length); |
michael@0 | 9154 | |
michael@0 | 9155 | switch (identifier) { |
michael@0 | 9156 | case PDU_IEI_CONCATENATED_SHORT_MESSAGES_8BIT: { |
michael@0 | 9157 | let ref = BitBufferHelper.readBits(8), |
michael@0 | 9158 | max = BitBufferHelper.readBits(8), |
michael@0 | 9159 | seq = BitBufferHelper.readBits(8); |
michael@0 | 9160 | if (max && seq && (seq <= max)) { |
michael@0 | 9161 | header.segmentRef = ref; |
michael@0 | 9162 | header.segmentMaxSeq = max; |
michael@0 | 9163 | header.segmentSeq = seq; |
michael@0 | 9164 | } |
michael@0 | 9165 | break; |
michael@0 | 9166 | } |
michael@0 | 9167 | case PDU_IEI_APPLICATION_PORT_ADDRESSING_SCHEME_8BIT: { |
michael@0 | 9168 | let dstp = BitBufferHelper.readBits(8), |
michael@0 | 9169 | orip = BitBufferHelper.readBits(8); |
michael@0 | 9170 | if ((dstp < PDU_APA_RESERVED_8BIT_PORTS) |
michael@0 | 9171 | || (orip < PDU_APA_RESERVED_8BIT_PORTS)) { |
michael@0 | 9172 | // 3GPP TS 23.040 clause 9.2.3.24.3: "A receiving entity shall |
michael@0 | 9173 | // ignore any information element where the value of the |
michael@0 | 9174 | // Information-Element-Data is Reserved or not supported" |
michael@0 | 9175 | break; |
michael@0 | 9176 | } |
michael@0 | 9177 | header.destinationPort = dstp; |
michael@0 | 9178 | header.originatorPort = orip; |
michael@0 | 9179 | break; |
michael@0 | 9180 | } |
michael@0 | 9181 | case PDU_IEI_APPLICATION_PORT_ADDRESSING_SCHEME_16BIT: { |
michael@0 | 9182 | let dstp = (BitBufferHelper.readBits(8) << 8) | BitBufferHelper.readBits(8), |
michael@0 | 9183 | orip = (BitBufferHelper.readBits(8) << 8) | BitBufferHelper.readBits(8); |
michael@0 | 9184 | // 3GPP TS 23.040 clause 9.2.3.24.4: "A receiving entity shall |
michael@0 | 9185 | // ignore any information element where the value of the |
michael@0 | 9186 | // Information-Element-Data is Reserved or not supported" |
michael@0 | 9187 | if ((dstp < PDU_APA_VALID_16BIT_PORTS) |
michael@0 | 9188 | && (orip < PDU_APA_VALID_16BIT_PORTS)) { |
michael@0 | 9189 | header.destinationPort = dstp; |
michael@0 | 9190 | header.originatorPort = orip; |
michael@0 | 9191 | } |
michael@0 | 9192 | break; |
michael@0 | 9193 | } |
michael@0 | 9194 | case PDU_IEI_CONCATENATED_SHORT_MESSAGES_16BIT: { |
michael@0 | 9195 | let ref = (BitBufferHelper.readBits(8) << 8) | BitBufferHelper.readBits(8), |
michael@0 | 9196 | max = BitBufferHelper.readBits(8), |
michael@0 | 9197 | seq = BitBufferHelper.readBits(8); |
michael@0 | 9198 | if (max && seq && (seq <= max)) { |
michael@0 | 9199 | header.segmentRef = ref; |
michael@0 | 9200 | header.segmentMaxSeq = max; |
michael@0 | 9201 | header.segmentSeq = seq; |
michael@0 | 9202 | } |
michael@0 | 9203 | break; |
michael@0 | 9204 | } |
michael@0 | 9205 | case PDU_IEI_NATIONAL_LANGUAGE_SINGLE_SHIFT: { |
michael@0 | 9206 | let langShiftIndex = BitBufferHelper.readBits(8); |
michael@0 | 9207 | if (langShiftIndex < PDU_NL_SINGLE_SHIFT_TABLES.length) { |
michael@0 | 9208 | header.langShiftIndex = langShiftIndex; |
michael@0 | 9209 | } |
michael@0 | 9210 | break; |
michael@0 | 9211 | } |
michael@0 | 9212 | case PDU_IEI_NATIONAL_LANGUAGE_LOCKING_SHIFT: { |
michael@0 | 9213 | let langIndex = BitBufferHelper.readBits(8); |
michael@0 | 9214 | if (langIndex < PDU_NL_LOCKING_SHIFT_TABLES.length) { |
michael@0 | 9215 | header.langIndex = langIndex; |
michael@0 | 9216 | } |
michael@0 | 9217 | break; |
michael@0 | 9218 | } |
michael@0 | 9219 | case PDU_IEI_SPECIAL_SMS_MESSAGE_INDICATION: { |
michael@0 | 9220 | let msgInd = BitBufferHelper.readBits(8) & 0xFF, |
michael@0 | 9221 | msgCount = BitBufferHelper.readBits(8); |
michael@0 | 9222 | /* |
michael@0 | 9223 | * TS 23.040 V6.8.1 Sec 9.2.3.24.2 |
michael@0 | 9224 | * bits 1 0 : basic message indication type |
michael@0 | 9225 | * bits 4 3 2 : extended message indication type |
michael@0 | 9226 | * bits 6 5 : Profile id |
michael@0 | 9227 | * bit 7 : storage type |
michael@0 | 9228 | */ |
michael@0 | 9229 | let storeType = msgInd & PDU_MWI_STORE_TYPE_BIT; |
michael@0 | 9230 | header.mwi = {}; |
michael@0 | 9231 | mwi = header.mwi; |
michael@0 | 9232 | |
michael@0 | 9233 | if (storeType == PDU_MWI_STORE_TYPE_STORE) { |
michael@0 | 9234 | // Store message because TP_UDH indicates so, note this may override |
michael@0 | 9235 | // the setting in DCS, but that is expected |
michael@0 | 9236 | mwi.discard = false; |
michael@0 | 9237 | } else if (mwi.discard === undefined) { |
michael@0 | 9238 | // storeType == PDU_MWI_STORE_TYPE_DISCARD |
michael@0 | 9239 | // only override mwi.discard here if it hasn't already been set |
michael@0 | 9240 | mwi.discard = true; |
michael@0 | 9241 | } |
michael@0 | 9242 | |
michael@0 | 9243 | mwi.msgCount = msgCount & 0xFF; |
michael@0 | 9244 | mwi.active = mwi.msgCount > 0; |
michael@0 | 9245 | |
michael@0 | 9246 | if (DEBUG) { |
michael@0 | 9247 | this.context.debug("MWI in TP_UDH received: " + JSON.stringify(mwi)); |
michael@0 | 9248 | } |
michael@0 | 9249 | break; |
michael@0 | 9250 | } |
michael@0 | 9251 | default: |
michael@0 | 9252 | // Drop unsupported id |
michael@0 | 9253 | for (let i = 0; i < length; i++) { |
michael@0 | 9254 | BitBufferHelper.readBits(8); |
michael@0 | 9255 | } |
michael@0 | 9256 | } |
michael@0 | 9257 | } |
michael@0 | 9258 | |
michael@0 | 9259 | // Consume padding bits |
michael@0 | 9260 | if (headerPaddingBits) { |
michael@0 | 9261 | BitBufferHelper.readBits(headerPaddingBits); |
michael@0 | 9262 | } |
michael@0 | 9263 | |
michael@0 | 9264 | return header; |
michael@0 | 9265 | }, |
michael@0 | 9266 | |
michael@0 | 9267 | getCdmaMsgEncoding: function(encoding) { |
michael@0 | 9268 | // Determine encoding method |
michael@0 | 9269 | switch (encoding) { |
michael@0 | 9270 | case PDU_CDMA_MSG_CODING_7BITS_ASCII: |
michael@0 | 9271 | case PDU_CDMA_MSG_CODING_IA5: |
michael@0 | 9272 | case PDU_CDMA_MSG_CODING_7BITS_GSM: |
michael@0 | 9273 | return PDU_DCS_MSG_CODING_7BITS_ALPHABET; |
michael@0 | 9274 | case PDU_CDMA_MSG_CODING_OCTET: |
michael@0 | 9275 | case PDU_CDMA_MSG_CODING_IS_91: |
michael@0 | 9276 | case PDU_CDMA_MSG_CODING_LATIN_HEBREW: |
michael@0 | 9277 | case PDU_CDMA_MSG_CODING_LATIN: |
michael@0 | 9278 | return PDU_DCS_MSG_CODING_8BITS_ALPHABET; |
michael@0 | 9279 | case PDU_CDMA_MSG_CODING_UNICODE: |
michael@0 | 9280 | case PDU_CDMA_MSG_CODING_SHIFT_JIS: |
michael@0 | 9281 | case PDU_CDMA_MSG_CODING_KOREAN: |
michael@0 | 9282 | return PDU_DCS_MSG_CODING_16BITS_ALPHABET; |
michael@0 | 9283 | } |
michael@0 | 9284 | return null; |
michael@0 | 9285 | }, |
michael@0 | 9286 | |
michael@0 | 9287 | decodeCdmaPDUMsg: function(encoding, msgType, msgBodySize) { |
michael@0 | 9288 | const langTable = PDU_NL_LOCKING_SHIFT_TABLES[PDU_NL_IDENTIFIER_DEFAULT]; |
michael@0 | 9289 | const langShiftTable = PDU_NL_SINGLE_SHIFT_TABLES[PDU_NL_IDENTIFIER_DEFAULT]; |
michael@0 | 9290 | let BitBufferHelper = this.context.BitBufferHelper; |
michael@0 | 9291 | let result = ""; |
michael@0 | 9292 | let msgDigit; |
michael@0 | 9293 | switch (encoding) { |
michael@0 | 9294 | case PDU_CDMA_MSG_CODING_OCTET: // TODO : Require Test |
michael@0 | 9295 | while(msgBodySize > 0) { |
michael@0 | 9296 | msgDigit = String.fromCharCode(BitBufferHelper.readBits(8)); |
michael@0 | 9297 | result += msgDigit; |
michael@0 | 9298 | msgBodySize--; |
michael@0 | 9299 | } |
michael@0 | 9300 | break; |
michael@0 | 9301 | case PDU_CDMA_MSG_CODING_IS_91: // TODO : Require Test |
michael@0 | 9302 | // Referenced from android code |
michael@0 | 9303 | switch (msgType) { |
michael@0 | 9304 | case PDU_CDMA_MSG_CODING_IS_91_TYPE_SMS: |
michael@0 | 9305 | case PDU_CDMA_MSG_CODING_IS_91_TYPE_SMS_FULL: |
michael@0 | 9306 | case PDU_CDMA_MSG_CODING_IS_91_TYPE_VOICEMAIL_STATUS: |
michael@0 | 9307 | while(msgBodySize > 0) { |
michael@0 | 9308 | msgDigit = String.fromCharCode(BitBufferHelper.readBits(6) + 0x20); |
michael@0 | 9309 | result += msgDigit; |
michael@0 | 9310 | msgBodySize--; |
michael@0 | 9311 | } |
michael@0 | 9312 | break; |
michael@0 | 9313 | case PDU_CDMA_MSG_CODING_IS_91_TYPE_CLI: |
michael@0 | 9314 | let addrInfo = {}; |
michael@0 | 9315 | addrInfo.digitMode = PDU_CDMA_MSG_ADDR_DIGIT_MODE_DTMF; |
michael@0 | 9316 | addrInfo.numberMode = PDU_CDMA_MSG_ADDR_NUMBER_MODE_ANSI; |
michael@0 | 9317 | addrInfo.numberType = PDU_CDMA_MSG_ADDR_NUMBER_TYPE_UNKNOWN; |
michael@0 | 9318 | addrInfo.numberPlan = PDU_CDMA_MSG_ADDR_NUMBER_PLAN_UNKNOWN; |
michael@0 | 9319 | addrInfo.addrLength = msgBodySize; |
michael@0 | 9320 | addrInfo.address = []; |
michael@0 | 9321 | for (let i = 0; i < addrInfo.addrLength; i++) { |
michael@0 | 9322 | addrInfo.address.push(BitBufferHelper.readBits(4)); |
michael@0 | 9323 | } |
michael@0 | 9324 | result = this.decodeAddr(addrInfo); |
michael@0 | 9325 | break; |
michael@0 | 9326 | } |
michael@0 | 9327 | // Fall through. |
michael@0 | 9328 | case PDU_CDMA_MSG_CODING_7BITS_ASCII: |
michael@0 | 9329 | case PDU_CDMA_MSG_CODING_IA5: // TODO : Require Test |
michael@0 | 9330 | while(msgBodySize > 0) { |
michael@0 | 9331 | msgDigit = BitBufferHelper.readBits(7); |
michael@0 | 9332 | if (msgDigit >= 32) { |
michael@0 | 9333 | msgDigit = String.fromCharCode(msgDigit); |
michael@0 | 9334 | } else { |
michael@0 | 9335 | if (msgDigit !== PDU_NL_EXTENDED_ESCAPE) { |
michael@0 | 9336 | msgDigit = langTable[msgDigit]; |
michael@0 | 9337 | } else { |
michael@0 | 9338 | msgDigit = BitBufferHelper.readBits(7); |
michael@0 | 9339 | msgBodySize--; |
michael@0 | 9340 | msgDigit = langShiftTable[msgDigit]; |
michael@0 | 9341 | } |
michael@0 | 9342 | } |
michael@0 | 9343 | result += msgDigit; |
michael@0 | 9344 | msgBodySize--; |
michael@0 | 9345 | } |
michael@0 | 9346 | break; |
michael@0 | 9347 | case PDU_CDMA_MSG_CODING_UNICODE: |
michael@0 | 9348 | while(msgBodySize > 0) { |
michael@0 | 9349 | msgDigit = String.fromCharCode(BitBufferHelper.readBits(16)); |
michael@0 | 9350 | result += msgDigit; |
michael@0 | 9351 | msgBodySize--; |
michael@0 | 9352 | } |
michael@0 | 9353 | break; |
michael@0 | 9354 | case PDU_CDMA_MSG_CODING_7BITS_GSM: // TODO : Require Test |
michael@0 | 9355 | while(msgBodySize > 0) { |
michael@0 | 9356 | msgDigit = BitBufferHelper.readBits(7); |
michael@0 | 9357 | if (msgDigit !== PDU_NL_EXTENDED_ESCAPE) { |
michael@0 | 9358 | msgDigit = langTable[msgDigit]; |
michael@0 | 9359 | } else { |
michael@0 | 9360 | msgDigit = BitBufferHelper.readBits(7); |
michael@0 | 9361 | msgBodySize--; |
michael@0 | 9362 | msgDigit = langShiftTable[msgDigit]; |
michael@0 | 9363 | } |
michael@0 | 9364 | result += msgDigit; |
michael@0 | 9365 | msgBodySize--; |
michael@0 | 9366 | } |
michael@0 | 9367 | break; |
michael@0 | 9368 | case PDU_CDMA_MSG_CODING_LATIN: // TODO : Require Test |
michael@0 | 9369 | // Reference : http://en.wikipedia.org/wiki/ISO/IEC_8859-1 |
michael@0 | 9370 | while(msgBodySize > 0) { |
michael@0 | 9371 | msgDigit = String.fromCharCode(BitBufferHelper.readBits(8)); |
michael@0 | 9372 | result += msgDigit; |
michael@0 | 9373 | msgBodySize--; |
michael@0 | 9374 | } |
michael@0 | 9375 | break; |
michael@0 | 9376 | case PDU_CDMA_MSG_CODING_LATIN_HEBREW: // TODO : Require Test |
michael@0 | 9377 | // Reference : http://en.wikipedia.org/wiki/ISO/IEC_8859-8 |
michael@0 | 9378 | while(msgBodySize > 0) { |
michael@0 | 9379 | msgDigit = BitBufferHelper.readBits(8); |
michael@0 | 9380 | if (msgDigit === 0xDF) { |
michael@0 | 9381 | msgDigit = String.fromCharCode(0x2017); |
michael@0 | 9382 | } else if (msgDigit === 0xFD) { |
michael@0 | 9383 | msgDigit = String.fromCharCode(0x200E); |
michael@0 | 9384 | } else if (msgDigit === 0xFE) { |
michael@0 | 9385 | msgDigit = String.fromCharCode(0x200F); |
michael@0 | 9386 | } else if (msgDigit >= 0xE0 && msgDigit <= 0xFA) { |
michael@0 | 9387 | msgDigit = String.fromCharCode(0x4F0 + msgDigit); |
michael@0 | 9388 | } else { |
michael@0 | 9389 | msgDigit = String.fromCharCode(msgDigit); |
michael@0 | 9390 | } |
michael@0 | 9391 | result += msgDigit; |
michael@0 | 9392 | msgBodySize--; |
michael@0 | 9393 | } |
michael@0 | 9394 | break; |
michael@0 | 9395 | case PDU_CDMA_MSG_CODING_SHIFT_JIS: |
michael@0 | 9396 | // Reference : http://msdn.microsoft.com/en-US/goglobal/cc305152.aspx |
michael@0 | 9397 | // http://demo.icu-project.org/icu-bin/convexp?conv=Shift_JIS |
michael@0 | 9398 | let shift_jis_message = []; |
michael@0 | 9399 | |
michael@0 | 9400 | while (msgBodySize > 0) { |
michael@0 | 9401 | shift_jis_message.push(BitBufferHelper.readBits(8)); |
michael@0 | 9402 | msgBodySize--; |
michael@0 | 9403 | } |
michael@0 | 9404 | |
michael@0 | 9405 | let decoder = new TextDecoder("shift_jis"); |
michael@0 | 9406 | result = decoder.decode(new Uint8Array(shift_jis_message)); |
michael@0 | 9407 | break; |
michael@0 | 9408 | case PDU_CDMA_MSG_CODING_KOREAN: |
michael@0 | 9409 | case PDU_CDMA_MSG_CODING_GSM_DCS: |
michael@0 | 9410 | // Fall through. |
michael@0 | 9411 | default: |
michael@0 | 9412 | break; |
michael@0 | 9413 | } |
michael@0 | 9414 | return result; |
michael@0 | 9415 | }, |
michael@0 | 9416 | |
michael@0 | 9417 | /** |
michael@0 | 9418 | * User data subparameter decoder : User Data |
michael@0 | 9419 | * |
michael@0 | 9420 | * @see 3GGP2 C.S0015-B 2.0, 4.5.2 User Data |
michael@0 | 9421 | */ |
michael@0 | 9422 | decodeUserDataMsg: function(hasUserHeader) { |
michael@0 | 9423 | let BitBufferHelper = this.context.BitBufferHelper; |
michael@0 | 9424 | let result = {}, |
michael@0 | 9425 | encoding = BitBufferHelper.readBits(5), |
michael@0 | 9426 | msgType; |
michael@0 | 9427 | |
michael@0 | 9428 | if (encoding === PDU_CDMA_MSG_CODING_IS_91) { |
michael@0 | 9429 | msgType = BitBufferHelper.readBits(8); |
michael@0 | 9430 | } |
michael@0 | 9431 | result.encoding = this.getCdmaMsgEncoding(encoding); |
michael@0 | 9432 | |
michael@0 | 9433 | let msgBodySize = BitBufferHelper.readBits(8); |
michael@0 | 9434 | |
michael@0 | 9435 | // For segmented SMS, a user header is included before sms content |
michael@0 | 9436 | if (hasUserHeader) { |
michael@0 | 9437 | result.header = this.decodeUserDataHeader(result.encoding); |
michael@0 | 9438 | // header size is included in body size, they are decoded |
michael@0 | 9439 | msgBodySize -= result.header.length; |
michael@0 | 9440 | } |
michael@0 | 9441 | |
michael@0 | 9442 | // Store original payload if enconding is OCTET for further handling of WAP Push, etc. |
michael@0 | 9443 | if (encoding === PDU_CDMA_MSG_CODING_OCTET && msgBodySize > 0) { |
michael@0 | 9444 | result.data = new Uint8Array(msgBodySize); |
michael@0 | 9445 | for (let i = 0; i < msgBodySize; i++) { |
michael@0 | 9446 | result.data[i] = BitBufferHelper.readBits(8); |
michael@0 | 9447 | } |
michael@0 | 9448 | BitBufferHelper.backwardReadPilot(8 * msgBodySize); |
michael@0 | 9449 | } |
michael@0 | 9450 | |
michael@0 | 9451 | // Decode sms content |
michael@0 | 9452 | result.body = this.decodeCdmaPDUMsg(encoding, msgType, msgBodySize); |
michael@0 | 9453 | |
michael@0 | 9454 | return result; |
michael@0 | 9455 | }, |
michael@0 | 9456 | |
michael@0 | 9457 | decodeBcd: function(value) { |
michael@0 | 9458 | return ((value >> 4) & 0xF) * 10 + (value & 0x0F); |
michael@0 | 9459 | }, |
michael@0 | 9460 | |
michael@0 | 9461 | /** |
michael@0 | 9462 | * User data subparameter decoder : Time Stamp |
michael@0 | 9463 | * |
michael@0 | 9464 | * @see 3GGP2 C.S0015-B 2.0, 4.5.4 Message Center Time Stamp |
michael@0 | 9465 | */ |
michael@0 | 9466 | decodeUserDataTimestamp: function() { |
michael@0 | 9467 | let BitBufferHelper = this.context.BitBufferHelper; |
michael@0 | 9468 | let year = this.decodeBcd(BitBufferHelper.readBits(8)), |
michael@0 | 9469 | month = this.decodeBcd(BitBufferHelper.readBits(8)) - 1, |
michael@0 | 9470 | date = this.decodeBcd(BitBufferHelper.readBits(8)), |
michael@0 | 9471 | hour = this.decodeBcd(BitBufferHelper.readBits(8)), |
michael@0 | 9472 | min = this.decodeBcd(BitBufferHelper.readBits(8)), |
michael@0 | 9473 | sec = this.decodeBcd(BitBufferHelper.readBits(8)); |
michael@0 | 9474 | |
michael@0 | 9475 | if (year >= 96 && year <= 99) { |
michael@0 | 9476 | year += 1900; |
michael@0 | 9477 | } else { |
michael@0 | 9478 | year += 2000; |
michael@0 | 9479 | } |
michael@0 | 9480 | |
michael@0 | 9481 | let result = (new Date(year, month, date, hour, min, sec, 0)).valueOf(); |
michael@0 | 9482 | |
michael@0 | 9483 | return result; |
michael@0 | 9484 | }, |
michael@0 | 9485 | |
michael@0 | 9486 | /** |
michael@0 | 9487 | * User data subparameter decoder : Reply Option |
michael@0 | 9488 | * |
michael@0 | 9489 | * @see 3GGP2 C.S0015-B 2.0, 4.5.11 Reply Option |
michael@0 | 9490 | */ |
michael@0 | 9491 | decodeUserDataReplyOption: function() { |
michael@0 | 9492 | let replyAction = this.context.BitBufferHelper.readBits(4), |
michael@0 | 9493 | result = { userAck: (replyAction & 0x8) ? true : false, |
michael@0 | 9494 | deliverAck: (replyAction & 0x4) ? true : false, |
michael@0 | 9495 | readAck: (replyAction & 0x2) ? true : false, |
michael@0 | 9496 | report: (replyAction & 0x1) ? true : false |
michael@0 | 9497 | }; |
michael@0 | 9498 | |
michael@0 | 9499 | return result; |
michael@0 | 9500 | }, |
michael@0 | 9501 | |
michael@0 | 9502 | /** |
michael@0 | 9503 | * User data subparameter decoder : Language Indicator |
michael@0 | 9504 | * |
michael@0 | 9505 | * @see 3GGP2 C.S0015-B 2.0, 4.5.14 Language Indicator |
michael@0 | 9506 | */ |
michael@0 | 9507 | decodeLanguageIndicator: function() { |
michael@0 | 9508 | let language = this.context.BitBufferHelper.readBits(8); |
michael@0 | 9509 | let result = CB_CDMA_LANG_GROUP[language]; |
michael@0 | 9510 | return result; |
michael@0 | 9511 | }, |
michael@0 | 9512 | |
michael@0 | 9513 | /** |
michael@0 | 9514 | * User data subparameter decoder : Call-Back Number |
michael@0 | 9515 | * |
michael@0 | 9516 | * @see 3GGP2 C.S0015-B 2.0, 4.5.15 Call-Back Number |
michael@0 | 9517 | */ |
michael@0 | 9518 | decodeUserDataCallbackNumber: function() { |
michael@0 | 9519 | let BitBufferHelper = this.context.BitBufferHelper; |
michael@0 | 9520 | let digitMode = BitBufferHelper.readBits(1); |
michael@0 | 9521 | if (digitMode) { |
michael@0 | 9522 | let numberType = BitBufferHelper.readBits(3), |
michael@0 | 9523 | numberPlan = BitBufferHelper.readBits(4); |
michael@0 | 9524 | } |
michael@0 | 9525 | let numberFields = BitBufferHelper.readBits(8), |
michael@0 | 9526 | result = ""; |
michael@0 | 9527 | for (let i = 0; i < numberFields; i++) { |
michael@0 | 9528 | if (digitMode === PDU_CDMA_MSG_ADDR_DIGIT_MODE_DTMF) { |
michael@0 | 9529 | let addrDigit = BitBufferHelper.readBits(4); |
michael@0 | 9530 | result += this.dtmfChars.charAt(addrDigit); |
michael@0 | 9531 | } else { |
michael@0 | 9532 | let addrDigit = BitBufferHelper.readBits(8); |
michael@0 | 9533 | result += String.fromCharCode(addrDigit); |
michael@0 | 9534 | } |
michael@0 | 9535 | } |
michael@0 | 9536 | |
michael@0 | 9537 | return result; |
michael@0 | 9538 | }, |
michael@0 | 9539 | |
michael@0 | 9540 | /** |
michael@0 | 9541 | * User data subparameter decoder : Message Status |
michael@0 | 9542 | * |
michael@0 | 9543 | * @see 3GGP2 C.S0015-B 2.0, 4.5.21 Message Status |
michael@0 | 9544 | */ |
michael@0 | 9545 | decodeUserDataMsgStatus: function() { |
michael@0 | 9546 | let BitBufferHelper = this.context.BitBufferHelper; |
michael@0 | 9547 | let result = { |
michael@0 | 9548 | errorClass: BitBufferHelper.readBits(2), |
michael@0 | 9549 | msgStatus: BitBufferHelper.readBits(6) |
michael@0 | 9550 | }; |
michael@0 | 9551 | |
michael@0 | 9552 | return result; |
michael@0 | 9553 | }, |
michael@0 | 9554 | |
michael@0 | 9555 | /** |
michael@0 | 9556 | * Decode information record parcel. |
michael@0 | 9557 | */ |
michael@0 | 9558 | decodeInformationRecord: function() { |
michael@0 | 9559 | let Buf = this.context.Buf; |
michael@0 | 9560 | let record = {}; |
michael@0 | 9561 | let numOfRecords = Buf.readInt32(); |
michael@0 | 9562 | |
michael@0 | 9563 | let type; |
michael@0 | 9564 | for (let i = 0; i < numOfRecords; i++) { |
michael@0 | 9565 | type = Buf.readInt32(); |
michael@0 | 9566 | |
michael@0 | 9567 | switch (type) { |
michael@0 | 9568 | /* |
michael@0 | 9569 | * Every type is encaped by ril, except extended display |
michael@0 | 9570 | */ |
michael@0 | 9571 | case PDU_CDMA_INFO_REC_TYPE_DISPLAY: |
michael@0 | 9572 | record.display = Buf.readString(); |
michael@0 | 9573 | break; |
michael@0 | 9574 | case PDU_CDMA_INFO_REC_TYPE_CALLED_PARTY_NUMBER: |
michael@0 | 9575 | record.calledNumber = {}; |
michael@0 | 9576 | record.calledNumber.number = Buf.readString(); |
michael@0 | 9577 | record.calledNumber.type = Buf.readInt32(); |
michael@0 | 9578 | record.calledNumber.plan = Buf.readInt32(); |
michael@0 | 9579 | record.calledNumber.pi = Buf.readInt32(); |
michael@0 | 9580 | record.calledNumber.si = Buf.readInt32(); |
michael@0 | 9581 | break; |
michael@0 | 9582 | case PDU_CDMA_INFO_REC_TYPE_CALLING_PARTY_NUMBER: |
michael@0 | 9583 | record.callingNumber = {}; |
michael@0 | 9584 | record.callingNumber.number = Buf.readString(); |
michael@0 | 9585 | record.callingNumber.type = Buf.readInt32(); |
michael@0 | 9586 | record.callingNumber.plan = Buf.readInt32(); |
michael@0 | 9587 | record.callingNumber.pi = Buf.readInt32(); |
michael@0 | 9588 | record.callingNumber.si = Buf.readInt32(); |
michael@0 | 9589 | break; |
michael@0 | 9590 | case PDU_CDMA_INFO_REC_TYPE_CONNECTED_NUMBER: |
michael@0 | 9591 | record.connectedNumber = {}; |
michael@0 | 9592 | record.connectedNumber.number = Buf.readString(); |
michael@0 | 9593 | record.connectedNumber.type = Buf.readInt32(); |
michael@0 | 9594 | record.connectedNumber.plan = Buf.readInt32(); |
michael@0 | 9595 | record.connectedNumber.pi = Buf.readInt32(); |
michael@0 | 9596 | record.connectedNumber.si = Buf.readInt32(); |
michael@0 | 9597 | break; |
michael@0 | 9598 | case PDU_CDMA_INFO_REC_TYPE_SIGNAL: |
michael@0 | 9599 | record.signal = {}; |
michael@0 | 9600 | record.signal.present = Buf.readInt32(); |
michael@0 | 9601 | record.signal.type = Buf.readInt32(); |
michael@0 | 9602 | record.signal.alertPitch = Buf.readInt32(); |
michael@0 | 9603 | record.signal.signal = Buf.readInt32(); |
michael@0 | 9604 | break; |
michael@0 | 9605 | case PDU_CDMA_INFO_REC_TYPE_REDIRECTING_NUMBER: |
michael@0 | 9606 | record.redirect = {}; |
michael@0 | 9607 | record.redirect.number = Buf.readString(); |
michael@0 | 9608 | record.redirect.type = Buf.readInt32(); |
michael@0 | 9609 | record.redirect.plan = Buf.readInt32(); |
michael@0 | 9610 | record.redirect.pi = Buf.readInt32(); |
michael@0 | 9611 | record.redirect.si = Buf.readInt32(); |
michael@0 | 9612 | record.redirect.reason = Buf.readInt32(); |
michael@0 | 9613 | break; |
michael@0 | 9614 | case PDU_CDMA_INFO_REC_TYPE_LINE_CONTROL: |
michael@0 | 9615 | record.lineControl = {}; |
michael@0 | 9616 | record.lineControl.polarityIncluded = Buf.readInt32(); |
michael@0 | 9617 | record.lineControl.toggle = Buf.readInt32(); |
michael@0 | 9618 | record.lineControl.recerse = Buf.readInt32(); |
michael@0 | 9619 | record.lineControl.powerDenial = Buf.readInt32(); |
michael@0 | 9620 | break; |
michael@0 | 9621 | case PDU_CDMA_INFO_REC_TYPE_EXTENDED_DISPLAY: |
michael@0 | 9622 | let length = Buf.readInt32(); |
michael@0 | 9623 | /* |
michael@0 | 9624 | * Extended display is still in format defined in |
michael@0 | 9625 | * C.S0005-F v1.0, 3.7.5.16 |
michael@0 | 9626 | */ |
michael@0 | 9627 | record.extendedDisplay = {}; |
michael@0 | 9628 | |
michael@0 | 9629 | let headerByte = Buf.readInt32(); |
michael@0 | 9630 | length--; |
michael@0 | 9631 | // Based on spec, headerByte must be 0x80 now |
michael@0 | 9632 | record.extendedDisplay.indicator = (headerByte >> 7); |
michael@0 | 9633 | record.extendedDisplay.type = (headerByte & 0x7F); |
michael@0 | 9634 | record.extendedDisplay.records = []; |
michael@0 | 9635 | |
michael@0 | 9636 | while (length > 0) { |
michael@0 | 9637 | let display = {}; |
michael@0 | 9638 | |
michael@0 | 9639 | display.tag = Buf.readInt32(); |
michael@0 | 9640 | length--; |
michael@0 | 9641 | if (display.tag !== INFO_REC_EXTENDED_DISPLAY_BLANK && |
michael@0 | 9642 | display.tag !== INFO_REC_EXTENDED_DISPLAY_SKIP) { |
michael@0 | 9643 | display.content = Buf.readString(); |
michael@0 | 9644 | length -= (display.content.length + 1); |
michael@0 | 9645 | } |
michael@0 | 9646 | |
michael@0 | 9647 | record.extendedDisplay.records.push(display); |
michael@0 | 9648 | } |
michael@0 | 9649 | break; |
michael@0 | 9650 | case PDU_CDMA_INFO_REC_TYPE_T53_CLIR: |
michael@0 | 9651 | record.cause = Buf.readInt32(); |
michael@0 | 9652 | break; |
michael@0 | 9653 | case PDU_CDMA_INFO_REC_TYPE_T53_AUDIO_CONTROL: |
michael@0 | 9654 | record.audioControl = {}; |
michael@0 | 9655 | record.audioControl.upLink = Buf.readInt32(); |
michael@0 | 9656 | record.audioControl.downLink = Buf.readInt32(); |
michael@0 | 9657 | break; |
michael@0 | 9658 | case PDU_CDMA_INFO_REC_TYPE_T53_RELEASE: |
michael@0 | 9659 | // Fall through |
michael@0 | 9660 | default: |
michael@0 | 9661 | throw new Error("UNSOLICITED_CDMA_INFO_REC(), Unsupported information record type " + record.type + "\n"); |
michael@0 | 9662 | } |
michael@0 | 9663 | } |
michael@0 | 9664 | |
michael@0 | 9665 | return record; |
michael@0 | 9666 | } |
michael@0 | 9667 | }; |
michael@0 | 9668 | |
michael@0 | 9669 | /** |
michael@0 | 9670 | * Helper for processing ICC PDUs. |
michael@0 | 9671 | */ |
michael@0 | 9672 | function ICCPDUHelperObject(aContext) { |
michael@0 | 9673 | this.context = aContext; |
michael@0 | 9674 | } |
michael@0 | 9675 | ICCPDUHelperObject.prototype = { |
michael@0 | 9676 | context: null, |
michael@0 | 9677 | |
michael@0 | 9678 | /** |
michael@0 | 9679 | * Read GSM 8-bit unpacked octets, |
michael@0 | 9680 | * which are default 7-bit alphabets with bit 8 set to 0. |
michael@0 | 9681 | * |
michael@0 | 9682 | * @param numOctets |
michael@0 | 9683 | * Number of octets to be read. |
michael@0 | 9684 | */ |
michael@0 | 9685 | read8BitUnpackedToString: function(numOctets) { |
michael@0 | 9686 | let GsmPDUHelper = this.context.GsmPDUHelper; |
michael@0 | 9687 | |
michael@0 | 9688 | let ret = ""; |
michael@0 | 9689 | let escapeFound = false; |
michael@0 | 9690 | let i; |
michael@0 | 9691 | const langTable = PDU_NL_LOCKING_SHIFT_TABLES[PDU_NL_IDENTIFIER_DEFAULT]; |
michael@0 | 9692 | const langShiftTable = PDU_NL_SINGLE_SHIFT_TABLES[PDU_NL_IDENTIFIER_DEFAULT]; |
michael@0 | 9693 | |
michael@0 | 9694 | for(i = 0; i < numOctets; i++) { |
michael@0 | 9695 | let octet = GsmPDUHelper.readHexOctet(); |
michael@0 | 9696 | if (octet == 0xff) { |
michael@0 | 9697 | i++; |
michael@0 | 9698 | break; |
michael@0 | 9699 | } |
michael@0 | 9700 | |
michael@0 | 9701 | if (escapeFound) { |
michael@0 | 9702 | escapeFound = false; |
michael@0 | 9703 | if (octet == PDU_NL_EXTENDED_ESCAPE) { |
michael@0 | 9704 | // According to 3GPP TS 23.038, section 6.2.1.1, NOTE 1, "On |
michael@0 | 9705 | // receipt of this code, a receiving entity shall display a space |
michael@0 | 9706 | // until another extensiion table is defined." |
michael@0 | 9707 | ret += " "; |
michael@0 | 9708 | } else if (octet == PDU_NL_RESERVED_CONTROL) { |
michael@0 | 9709 | // According to 3GPP TS 23.038 B.2, "This code represents a control |
michael@0 | 9710 | // character and therefore must not be used for language specific |
michael@0 | 9711 | // characters." |
michael@0 | 9712 | ret += " "; |
michael@0 | 9713 | } else { |
michael@0 | 9714 | ret += langShiftTable[octet]; |
michael@0 | 9715 | } |
michael@0 | 9716 | } else if (octet == PDU_NL_EXTENDED_ESCAPE) { |
michael@0 | 9717 | escapeFound = true; |
michael@0 | 9718 | } else { |
michael@0 | 9719 | ret += langTable[octet]; |
michael@0 | 9720 | } |
michael@0 | 9721 | } |
michael@0 | 9722 | |
michael@0 | 9723 | let Buf = this.context.Buf; |
michael@0 | 9724 | Buf.seekIncoming((numOctets - i) * Buf.PDU_HEX_OCTET_SIZE); |
michael@0 | 9725 | return ret; |
michael@0 | 9726 | }, |
michael@0 | 9727 | |
michael@0 | 9728 | /** |
michael@0 | 9729 | * Write GSM 8-bit unpacked octets. |
michael@0 | 9730 | * |
michael@0 | 9731 | * @param numOctets Number of total octets to be writen, including trailing |
michael@0 | 9732 | * 0xff. |
michael@0 | 9733 | * @param str String to be written. Could be null. |
michael@0 | 9734 | */ |
michael@0 | 9735 | writeStringTo8BitUnpacked: function(numOctets, str) { |
michael@0 | 9736 | const langTable = PDU_NL_LOCKING_SHIFT_TABLES[PDU_NL_IDENTIFIER_DEFAULT]; |
michael@0 | 9737 | const langShiftTable = PDU_NL_SINGLE_SHIFT_TABLES[PDU_NL_IDENTIFIER_DEFAULT]; |
michael@0 | 9738 | |
michael@0 | 9739 | let GsmPDUHelper = this.context.GsmPDUHelper; |
michael@0 | 9740 | |
michael@0 | 9741 | // If the character is GSM extended alphabet, two octets will be written. |
michael@0 | 9742 | // So we need to keep track of number of octets to be written. |
michael@0 | 9743 | let i, j; |
michael@0 | 9744 | let len = str ? str.length : 0; |
michael@0 | 9745 | for (i = 0, j = 0; i < len && j < numOctets; i++) { |
michael@0 | 9746 | let c = str.charAt(i); |
michael@0 | 9747 | let octet = langTable.indexOf(c); |
michael@0 | 9748 | |
michael@0 | 9749 | if (octet == -1) { |
michael@0 | 9750 | // Make sure we still have enough space to write two octets. |
michael@0 | 9751 | if (j + 2 > numOctets) { |
michael@0 | 9752 | break; |
michael@0 | 9753 | } |
michael@0 | 9754 | |
michael@0 | 9755 | octet = langShiftTable.indexOf(c); |
michael@0 | 9756 | if (octet == -1) { |
michael@0 | 9757 | // Fallback to ASCII space. |
michael@0 | 9758 | octet = langTable.indexOf(' '); |
michael@0 | 9759 | } |
michael@0 | 9760 | GsmPDUHelper.writeHexOctet(PDU_NL_EXTENDED_ESCAPE); |
michael@0 | 9761 | j++; |
michael@0 | 9762 | } |
michael@0 | 9763 | GsmPDUHelper.writeHexOctet(octet); |
michael@0 | 9764 | j++; |
michael@0 | 9765 | } |
michael@0 | 9766 | |
michael@0 | 9767 | // trailing 0xff |
michael@0 | 9768 | while (j++ < numOctets) { |
michael@0 | 9769 | GsmPDUHelper.writeHexOctet(0xff); |
michael@0 | 9770 | } |
michael@0 | 9771 | }, |
michael@0 | 9772 | |
michael@0 | 9773 | /** |
michael@0 | 9774 | * Read UCS2 String on UICC. |
michael@0 | 9775 | * |
michael@0 | 9776 | * @see TS 101.221, Annex A. |
michael@0 | 9777 | * @param scheme |
michael@0 | 9778 | * Coding scheme for UCS2 on UICC. One of 0x80, 0x81 or 0x82. |
michael@0 | 9779 | * @param numOctets |
michael@0 | 9780 | * Number of octets to be read as UCS2 string. |
michael@0 | 9781 | */ |
michael@0 | 9782 | readICCUCS2String: function(scheme, numOctets) { |
michael@0 | 9783 | let Buf = this.context.Buf; |
michael@0 | 9784 | let GsmPDUHelper = this.context.GsmPDUHelper; |
michael@0 | 9785 | |
michael@0 | 9786 | let str = ""; |
michael@0 | 9787 | switch (scheme) { |
michael@0 | 9788 | /** |
michael@0 | 9789 | * +------+---------+---------+---------+---------+------+------+ |
michael@0 | 9790 | * | 0x80 | Ch1_msb | Ch1_lsb | Ch2_msb | Ch2_lsb | 0xff | 0xff | |
michael@0 | 9791 | * +------+---------+---------+---------+---------+------+------+ |
michael@0 | 9792 | */ |
michael@0 | 9793 | case 0x80: |
michael@0 | 9794 | let isOdd = numOctets % 2; |
michael@0 | 9795 | let i; |
michael@0 | 9796 | for (i = 0; i < numOctets - isOdd; i += 2) { |
michael@0 | 9797 | let code = (GsmPDUHelper.readHexOctet() << 8) | GsmPDUHelper.readHexOctet(); |
michael@0 | 9798 | if (code == 0xffff) { |
michael@0 | 9799 | i += 2; |
michael@0 | 9800 | break; |
michael@0 | 9801 | } |
michael@0 | 9802 | str += String.fromCharCode(code); |
michael@0 | 9803 | } |
michael@0 | 9804 | |
michael@0 | 9805 | // Skip trailing 0xff |
michael@0 | 9806 | Buf.seekIncoming((numOctets - i) * Buf.PDU_HEX_OCTET_SIZE); |
michael@0 | 9807 | break; |
michael@0 | 9808 | case 0x81: // Fall through |
michael@0 | 9809 | case 0x82: |
michael@0 | 9810 | /** |
michael@0 | 9811 | * +------+-----+--------+-----+-----+-----+--------+------+ |
michael@0 | 9812 | * | 0x81 | len | offset | Ch1 | Ch2 | ... | Ch_len | 0xff | |
michael@0 | 9813 | * +------+-----+--------+-----+-----+-----+--------+------+ |
michael@0 | 9814 | * |
michael@0 | 9815 | * len : The length of characters. |
michael@0 | 9816 | * offset : 0hhh hhhh h000 0000 |
michael@0 | 9817 | * Ch_n: bit 8 = 0 |
michael@0 | 9818 | * GSM default alphabets |
michael@0 | 9819 | * bit 8 = 1 |
michael@0 | 9820 | * UCS2 character whose char code is (Ch_n & 0x7f) + offset |
michael@0 | 9821 | * |
michael@0 | 9822 | * +------+-----+------------+------------+-----+-----+-----+--------+ |
michael@0 | 9823 | * | 0x82 | len | offset_msb | offset_lsb | Ch1 | Ch2 | ... | Ch_len | |
michael@0 | 9824 | * +------+-----+------------+------------+-----+-----+-----+--------+ |
michael@0 | 9825 | * |
michael@0 | 9826 | * len : The length of characters. |
michael@0 | 9827 | * offset_msb, offset_lsn: offset |
michael@0 | 9828 | * Ch_n: bit 8 = 0 |
michael@0 | 9829 | * GSM default alphabets |
michael@0 | 9830 | * bit 8 = 1 |
michael@0 | 9831 | * UCS2 character whose char code is (Ch_n & 0x7f) + offset |
michael@0 | 9832 | */ |
michael@0 | 9833 | let len = GsmPDUHelper.readHexOctet(); |
michael@0 | 9834 | let offset, headerLen; |
michael@0 | 9835 | if (scheme == 0x81) { |
michael@0 | 9836 | offset = GsmPDUHelper.readHexOctet() << 7; |
michael@0 | 9837 | headerLen = 2; |
michael@0 | 9838 | } else { |
michael@0 | 9839 | offset = (GsmPDUHelper.readHexOctet() << 8) | GsmPDUHelper.readHexOctet(); |
michael@0 | 9840 | headerLen = 3; |
michael@0 | 9841 | } |
michael@0 | 9842 | |
michael@0 | 9843 | for (let i = 0; i < len; i++) { |
michael@0 | 9844 | let ch = GsmPDUHelper.readHexOctet(); |
michael@0 | 9845 | if (ch & 0x80) { |
michael@0 | 9846 | // UCS2 |
michael@0 | 9847 | str += String.fromCharCode((ch & 0x7f) + offset); |
michael@0 | 9848 | } else { |
michael@0 | 9849 | // GSM 8bit |
michael@0 | 9850 | let count = 0, gotUCS2 = 0; |
michael@0 | 9851 | while ((i + count + 1 < len)) { |
michael@0 | 9852 | count++; |
michael@0 | 9853 | if (GsmPDUHelper.readHexOctet() & 0x80) { |
michael@0 | 9854 | gotUCS2 = 1; |
michael@0 | 9855 | break; |
michael@0 | 9856 | } |
michael@0 | 9857 | } |
michael@0 | 9858 | // Unread. |
michael@0 | 9859 | // +1 for the GSM alphabet indexed at i, |
michael@0 | 9860 | Buf.seekIncoming(-1 * (count + 1) * Buf.PDU_HEX_OCTET_SIZE); |
michael@0 | 9861 | str += this.read8BitUnpackedToString(count + 1 - gotUCS2); |
michael@0 | 9862 | i += count - gotUCS2; |
michael@0 | 9863 | } |
michael@0 | 9864 | } |
michael@0 | 9865 | |
michael@0 | 9866 | // Skipping trailing 0xff |
michael@0 | 9867 | Buf.seekIncoming((numOctets - len - headerLen) * Buf.PDU_HEX_OCTET_SIZE); |
michael@0 | 9868 | break; |
michael@0 | 9869 | } |
michael@0 | 9870 | return str; |
michael@0 | 9871 | }, |
michael@0 | 9872 | |
michael@0 | 9873 | /** |
michael@0 | 9874 | * Read Alpha Id and Dialling number from TS TS 151.011 clause 10.5.1 |
michael@0 | 9875 | * |
michael@0 | 9876 | * @param recordSize The size of linear fixed record. |
michael@0 | 9877 | */ |
michael@0 | 9878 | readAlphaIdDiallingNumber: function(recordSize) { |
michael@0 | 9879 | let Buf = this.context.Buf; |
michael@0 | 9880 | let length = Buf.readInt32(); |
michael@0 | 9881 | |
michael@0 | 9882 | let alphaLen = recordSize - ADN_FOOTER_SIZE_BYTES; |
michael@0 | 9883 | let alphaId = this.readAlphaIdentifier(alphaLen); |
michael@0 | 9884 | |
michael@0 | 9885 | let number = this.readNumberWithLength(); |
michael@0 | 9886 | |
michael@0 | 9887 | // Skip 2 unused octets, CCP and EXT1. |
michael@0 | 9888 | Buf.seekIncoming(2 * Buf.PDU_HEX_OCTET_SIZE); |
michael@0 | 9889 | Buf.readStringDelimiter(length); |
michael@0 | 9890 | |
michael@0 | 9891 | let contact = null; |
michael@0 | 9892 | if (alphaId || number) { |
michael@0 | 9893 | contact = {alphaId: alphaId, |
michael@0 | 9894 | number: number}; |
michael@0 | 9895 | } |
michael@0 | 9896 | return contact; |
michael@0 | 9897 | }, |
michael@0 | 9898 | |
michael@0 | 9899 | /** |
michael@0 | 9900 | * Write Alpha Identifier and Dialling number from TS 151.011 clause 10.5.1 |
michael@0 | 9901 | * |
michael@0 | 9902 | * @param recordSize The size of linear fixed record. |
michael@0 | 9903 | * @param alphaId Alpha Identifier to be written. |
michael@0 | 9904 | * @param number Dialling Number to be written. |
michael@0 | 9905 | */ |
michael@0 | 9906 | writeAlphaIdDiallingNumber: function(recordSize, alphaId, number) { |
michael@0 | 9907 | let Buf = this.context.Buf; |
michael@0 | 9908 | let GsmPDUHelper = this.context.GsmPDUHelper; |
michael@0 | 9909 | |
michael@0 | 9910 | // Write String length |
michael@0 | 9911 | let strLen = recordSize * 2; |
michael@0 | 9912 | Buf.writeInt32(strLen); |
michael@0 | 9913 | |
michael@0 | 9914 | let alphaLen = recordSize - ADN_FOOTER_SIZE_BYTES; |
michael@0 | 9915 | this.writeAlphaIdentifier(alphaLen, alphaId); |
michael@0 | 9916 | this.writeNumberWithLength(number); |
michael@0 | 9917 | |
michael@0 | 9918 | // Write unused octets 0xff, CCP and EXT1. |
michael@0 | 9919 | GsmPDUHelper.writeHexOctet(0xff); |
michael@0 | 9920 | GsmPDUHelper.writeHexOctet(0xff); |
michael@0 | 9921 | Buf.writeStringDelimiter(strLen); |
michael@0 | 9922 | }, |
michael@0 | 9923 | |
michael@0 | 9924 | /** |
michael@0 | 9925 | * Read Alpha Identifier. |
michael@0 | 9926 | * |
michael@0 | 9927 | * @see TS 131.102 |
michael@0 | 9928 | * |
michael@0 | 9929 | * @param numOctets |
michael@0 | 9930 | * Number of octets to be read. |
michael@0 | 9931 | * |
michael@0 | 9932 | * It uses either |
michael@0 | 9933 | * 1. SMS default 7-bit alphabet with bit 8 set to 0. |
michael@0 | 9934 | * 2. UCS2 string. |
michael@0 | 9935 | * |
michael@0 | 9936 | * Unused bytes should be set to 0xff. |
michael@0 | 9937 | */ |
michael@0 | 9938 | readAlphaIdentifier: function(numOctets) { |
michael@0 | 9939 | if (numOctets === 0) { |
michael@0 | 9940 | return ""; |
michael@0 | 9941 | } |
michael@0 | 9942 | |
michael@0 | 9943 | let temp; |
michael@0 | 9944 | // Read the 1st octet to determine the encoding. |
michael@0 | 9945 | if ((temp = this.context.GsmPDUHelper.readHexOctet()) == 0x80 || |
michael@0 | 9946 | temp == 0x81 || |
michael@0 | 9947 | temp == 0x82) { |
michael@0 | 9948 | numOctets--; |
michael@0 | 9949 | return this.readICCUCS2String(temp, numOctets); |
michael@0 | 9950 | } else { |
michael@0 | 9951 | let Buf = this.context.Buf; |
michael@0 | 9952 | Buf.seekIncoming(-1 * Buf.PDU_HEX_OCTET_SIZE); |
michael@0 | 9953 | return this.read8BitUnpackedToString(numOctets); |
michael@0 | 9954 | } |
michael@0 | 9955 | }, |
michael@0 | 9956 | |
michael@0 | 9957 | /** |
michael@0 | 9958 | * Write Alpha Identifier. |
michael@0 | 9959 | * |
michael@0 | 9960 | * @param numOctets |
michael@0 | 9961 | * Total number of octets to be written. This includes the length of |
michael@0 | 9962 | * alphaId and the length of trailing unused octets(0xff). |
michael@0 | 9963 | * @param alphaId |
michael@0 | 9964 | * Alpha Identifier to be written. |
michael@0 | 9965 | * |
michael@0 | 9966 | * Unused octets will be written as 0xff. |
michael@0 | 9967 | */ |
michael@0 | 9968 | writeAlphaIdentifier: function(numOctets, alphaId) { |
michael@0 | 9969 | if (numOctets === 0) { |
michael@0 | 9970 | return; |
michael@0 | 9971 | } |
michael@0 | 9972 | |
michael@0 | 9973 | // If alphaId is empty or it's of GSM 8 bit. |
michael@0 | 9974 | if (!alphaId || this.context.ICCUtilsHelper.isGsm8BitAlphabet(alphaId)) { |
michael@0 | 9975 | this.writeStringTo8BitUnpacked(numOctets, alphaId); |
michael@0 | 9976 | } else { |
michael@0 | 9977 | let GsmPDUHelper = this.context.GsmPDUHelper; |
michael@0 | 9978 | |
michael@0 | 9979 | // Currently only support UCS2 coding scheme 0x80. |
michael@0 | 9980 | GsmPDUHelper.writeHexOctet(0x80); |
michael@0 | 9981 | numOctets--; |
michael@0 | 9982 | // Now the alphaId is UCS2 string, each character will take 2 octets. |
michael@0 | 9983 | if (alphaId.length * 2 > numOctets) { |
michael@0 | 9984 | alphaId = alphaId.substring(0, Math.floor(numOctets / 2)); |
michael@0 | 9985 | } |
michael@0 | 9986 | GsmPDUHelper.writeUCS2String(alphaId); |
michael@0 | 9987 | for (let i = alphaId.length * 2; i < numOctets; i++) { |
michael@0 | 9988 | GsmPDUHelper.writeHexOctet(0xff); |
michael@0 | 9989 | } |
michael@0 | 9990 | } |
michael@0 | 9991 | }, |
michael@0 | 9992 | |
michael@0 | 9993 | /** |
michael@0 | 9994 | * Read Dialling number. |
michael@0 | 9995 | * |
michael@0 | 9996 | * @see TS 131.102 |
michael@0 | 9997 | * |
michael@0 | 9998 | * @param len |
michael@0 | 9999 | * The Length of BCD number. |
michael@0 | 10000 | * |
michael@0 | 10001 | * From TS 131.102, in EF_ADN, EF_FDN, the field 'Length of BCD number' |
michael@0 | 10002 | * means the total bytes should be allocated to store the TON/NPI and |
michael@0 | 10003 | * the dialing number. |
michael@0 | 10004 | * For example, if the dialing number is 1234567890, |
michael@0 | 10005 | * and the TON/NPI is 0x81, |
michael@0 | 10006 | * The field 'Length of BCD number' should be 06, which is |
michael@0 | 10007 | * 1 byte to store the TON/NPI, 0x81 |
michael@0 | 10008 | * 5 bytes to store the BCD number 2143658709. |
michael@0 | 10009 | * |
michael@0 | 10010 | * Here the definition of the length is different from SMS spec, |
michael@0 | 10011 | * TS 23.040 9.1.2.5, which the length means |
michael@0 | 10012 | * "number of useful semi-octets within the Address-Value field". |
michael@0 | 10013 | */ |
michael@0 | 10014 | readDiallingNumber: function(len) { |
michael@0 | 10015 | if (DEBUG) this.context.debug("PDU: Going to read Dialling number: " + len); |
michael@0 | 10016 | if (len === 0) { |
michael@0 | 10017 | return ""; |
michael@0 | 10018 | } |
michael@0 | 10019 | |
michael@0 | 10020 | let GsmPDUHelper = this.context.GsmPDUHelper; |
michael@0 | 10021 | |
michael@0 | 10022 | // TOA = TON + NPI |
michael@0 | 10023 | let toa = GsmPDUHelper.readHexOctet(); |
michael@0 | 10024 | |
michael@0 | 10025 | let number = GsmPDUHelper.readSwappedNibbleBcdString(len - 1); |
michael@0 | 10026 | if (number.length <= 0) { |
michael@0 | 10027 | if (DEBUG) this.context.debug("No number provided"); |
michael@0 | 10028 | return ""; |
michael@0 | 10029 | } |
michael@0 | 10030 | if ((toa >> 4) == (PDU_TOA_INTERNATIONAL >> 4)) { |
michael@0 | 10031 | number = '+' + number; |
michael@0 | 10032 | } |
michael@0 | 10033 | return number; |
michael@0 | 10034 | }, |
michael@0 | 10035 | |
michael@0 | 10036 | /** |
michael@0 | 10037 | * Write Dialling Number. |
michael@0 | 10038 | * |
michael@0 | 10039 | * @param number The Dialling number |
michael@0 | 10040 | */ |
michael@0 | 10041 | writeDiallingNumber: function(number) { |
michael@0 | 10042 | let GsmPDUHelper = this.context.GsmPDUHelper; |
michael@0 | 10043 | |
michael@0 | 10044 | let toa = PDU_TOA_ISDN; // 81 |
michael@0 | 10045 | if (number[0] == '+') { |
michael@0 | 10046 | toa = PDU_TOA_INTERNATIONAL | PDU_TOA_ISDN; // 91 |
michael@0 | 10047 | number = number.substring(1); |
michael@0 | 10048 | } |
michael@0 | 10049 | GsmPDUHelper.writeHexOctet(toa); |
michael@0 | 10050 | GsmPDUHelper.writeSwappedNibbleBCD(number); |
michael@0 | 10051 | }, |
michael@0 | 10052 | |
michael@0 | 10053 | readNumberWithLength: function() { |
michael@0 | 10054 | let Buf = this.context.Buf; |
michael@0 | 10055 | let number; |
michael@0 | 10056 | let numLen = this.context.GsmPDUHelper.readHexOctet(); |
michael@0 | 10057 | if (numLen != 0xff) { |
michael@0 | 10058 | if (numLen > ADN_MAX_BCD_NUMBER_BYTES) { |
michael@0 | 10059 | throw new Error("invalid length of BCD number/SSC contents - " + numLen); |
michael@0 | 10060 | } |
michael@0 | 10061 | |
michael@0 | 10062 | number = this.readDiallingNumber(numLen); |
michael@0 | 10063 | Buf.seekIncoming((ADN_MAX_BCD_NUMBER_BYTES - numLen) * Buf.PDU_HEX_OCTET_SIZE); |
michael@0 | 10064 | } else { |
michael@0 | 10065 | Buf.seekIncoming(ADN_MAX_BCD_NUMBER_BYTES * Buf.PDU_HEX_OCTET_SIZE); |
michael@0 | 10066 | } |
michael@0 | 10067 | |
michael@0 | 10068 | return number; |
michael@0 | 10069 | }, |
michael@0 | 10070 | |
michael@0 | 10071 | writeNumberWithLength: function(number) { |
michael@0 | 10072 | let GsmPDUHelper = this.context.GsmPDUHelper; |
michael@0 | 10073 | |
michael@0 | 10074 | if (number) { |
michael@0 | 10075 | let numStart = number[0] == "+" ? 1 : 0; |
michael@0 | 10076 | number = number.substring(0, numStart) + |
michael@0 | 10077 | number.substring(numStart) |
michael@0 | 10078 | .replace(/[^0-9*#,]/g, "") |
michael@0 | 10079 | .replace(/\*/g, "a") |
michael@0 | 10080 | .replace(/\#/g, "b") |
michael@0 | 10081 | .replace(/\,/g, "c"); |
michael@0 | 10082 | |
michael@0 | 10083 | let numDigits = number.length - numStart; |
michael@0 | 10084 | if (numDigits > ADN_MAX_NUMBER_DIGITS) { |
michael@0 | 10085 | number = number.substring(0, ADN_MAX_NUMBER_DIGITS + numStart); |
michael@0 | 10086 | numDigits = number.length - numStart; |
michael@0 | 10087 | } |
michael@0 | 10088 | |
michael@0 | 10089 | // +1 for TON/NPI |
michael@0 | 10090 | let numLen = Math.ceil(numDigits / 2) + 1; |
michael@0 | 10091 | GsmPDUHelper.writeHexOctet(numLen); |
michael@0 | 10092 | this.writeDiallingNumber(number); |
michael@0 | 10093 | // Write trailing 0xff of Dialling Number. |
michael@0 | 10094 | for (let i = 0; i < ADN_MAX_BCD_NUMBER_BYTES - numLen; i++) { |
michael@0 | 10095 | GsmPDUHelper.writeHexOctet(0xff); |
michael@0 | 10096 | } |
michael@0 | 10097 | } else { |
michael@0 | 10098 | // +1 for numLen |
michael@0 | 10099 | for (let i = 0; i < ADN_MAX_BCD_NUMBER_BYTES + 1; i++) { |
michael@0 | 10100 | GsmPDUHelper.writeHexOctet(0xff); |
michael@0 | 10101 | } |
michael@0 | 10102 | } |
michael@0 | 10103 | } |
michael@0 | 10104 | }; |
michael@0 | 10105 | |
michael@0 | 10106 | function StkCommandParamsFactoryObject(aContext) { |
michael@0 | 10107 | this.context = aContext; |
michael@0 | 10108 | } |
michael@0 | 10109 | StkCommandParamsFactoryObject.prototype = { |
michael@0 | 10110 | context: null, |
michael@0 | 10111 | |
michael@0 | 10112 | createParam: function(cmdDetails, ctlvs) { |
michael@0 | 10113 | let method = this[cmdDetails.typeOfCommand]; |
michael@0 | 10114 | if (typeof method != "function") { |
michael@0 | 10115 | if (DEBUG) { |
michael@0 | 10116 | this.context.debug("Unknown proactive command " + |
michael@0 | 10117 | cmdDetails.typeOfCommand.toString(16)); |
michael@0 | 10118 | } |
michael@0 | 10119 | return null; |
michael@0 | 10120 | } |
michael@0 | 10121 | return method.call(this, cmdDetails, ctlvs); |
michael@0 | 10122 | }, |
michael@0 | 10123 | |
michael@0 | 10124 | /** |
michael@0 | 10125 | * Construct a param for Refresh. |
michael@0 | 10126 | * |
michael@0 | 10127 | * @param cmdDetails |
michael@0 | 10128 | * The value object of CommandDetails TLV. |
michael@0 | 10129 | * @param ctlvs |
michael@0 | 10130 | * The all TLVs in this proactive command. |
michael@0 | 10131 | */ |
michael@0 | 10132 | processRefresh: function(cmdDetails, ctlvs) { |
michael@0 | 10133 | let refreshType = cmdDetails.commandQualifier; |
michael@0 | 10134 | switch (refreshType) { |
michael@0 | 10135 | case STK_REFRESH_FILE_CHANGE: |
michael@0 | 10136 | case STK_REFRESH_NAA_INIT_AND_FILE_CHANGE: |
michael@0 | 10137 | let ctlv = this.context.StkProactiveCmdHelper.searchForTag( |
michael@0 | 10138 | COMPREHENSIONTLV_TAG_FILE_LIST, ctlvs); |
michael@0 | 10139 | if (ctlv) { |
michael@0 | 10140 | let list = ctlv.value.fileList; |
michael@0 | 10141 | if (DEBUG) { |
michael@0 | 10142 | this.context.debug("Refresh, list = " + list); |
michael@0 | 10143 | } |
michael@0 | 10144 | this.context.ICCRecordHelper.fetchICCRecords(); |
michael@0 | 10145 | } |
michael@0 | 10146 | break; |
michael@0 | 10147 | } |
michael@0 | 10148 | return null; |
michael@0 | 10149 | }, |
michael@0 | 10150 | |
michael@0 | 10151 | /** |
michael@0 | 10152 | * Construct a param for Poll Interval. |
michael@0 | 10153 | * |
michael@0 | 10154 | * @param cmdDetails |
michael@0 | 10155 | * The value object of CommandDetails TLV. |
michael@0 | 10156 | * @param ctlvs |
michael@0 | 10157 | * The all TLVs in this proactive command. |
michael@0 | 10158 | */ |
michael@0 | 10159 | processPollInterval: function(cmdDetails, ctlvs) { |
michael@0 | 10160 | let ctlv = this.context.StkProactiveCmdHelper.searchForTag( |
michael@0 | 10161 | COMPREHENSIONTLV_TAG_DURATION, ctlvs); |
michael@0 | 10162 | if (!ctlv) { |
michael@0 | 10163 | this.context.RIL.sendStkTerminalResponse({ |
michael@0 | 10164 | command: cmdDetails, |
michael@0 | 10165 | resultCode: STK_RESULT_REQUIRED_VALUES_MISSING}); |
michael@0 | 10166 | throw new Error("Stk Poll Interval: Required value missing : Duration"); |
michael@0 | 10167 | } |
michael@0 | 10168 | |
michael@0 | 10169 | return ctlv.value; |
michael@0 | 10170 | }, |
michael@0 | 10171 | |
michael@0 | 10172 | /** |
michael@0 | 10173 | * Construct a param for Poll Off. |
michael@0 | 10174 | * |
michael@0 | 10175 | * @param cmdDetails |
michael@0 | 10176 | * The value object of CommandDetails TLV. |
michael@0 | 10177 | * @param ctlvs |
michael@0 | 10178 | * The all TLVs in this proactive command. |
michael@0 | 10179 | */ |
michael@0 | 10180 | processPollOff: function(cmdDetails, ctlvs) { |
michael@0 | 10181 | return null; |
michael@0 | 10182 | }, |
michael@0 | 10183 | |
michael@0 | 10184 | /** |
michael@0 | 10185 | * Construct a param for Set Up Event list. |
michael@0 | 10186 | * |
michael@0 | 10187 | * @param cmdDetails |
michael@0 | 10188 | * The value object of CommandDetails TLV. |
michael@0 | 10189 | * @param ctlvs |
michael@0 | 10190 | * The all TLVs in this proactive command. |
michael@0 | 10191 | */ |
michael@0 | 10192 | processSetUpEventList: function(cmdDetails, ctlvs) { |
michael@0 | 10193 | let ctlv = this.context.StkProactiveCmdHelper.searchForTag( |
michael@0 | 10194 | COMPREHENSIONTLV_TAG_EVENT_LIST, ctlvs); |
michael@0 | 10195 | if (!ctlv) { |
michael@0 | 10196 | this.context.RIL.sendStkTerminalResponse({ |
michael@0 | 10197 | command: cmdDetails, |
michael@0 | 10198 | resultCode: STK_RESULT_REQUIRED_VALUES_MISSING}); |
michael@0 | 10199 | throw new Error("Stk Event List: Required value missing : Event List"); |
michael@0 | 10200 | } |
michael@0 | 10201 | |
michael@0 | 10202 | return ctlv.value || {eventList: null}; |
michael@0 | 10203 | }, |
michael@0 | 10204 | |
michael@0 | 10205 | /** |
michael@0 | 10206 | * Construct a param for Select Item. |
michael@0 | 10207 | * |
michael@0 | 10208 | * @param cmdDetails |
michael@0 | 10209 | * The value object of CommandDetails TLV. |
michael@0 | 10210 | * @param ctlvs |
michael@0 | 10211 | * The all TLVs in this proactive command. |
michael@0 | 10212 | */ |
michael@0 | 10213 | processSelectItem: function(cmdDetails, ctlvs) { |
michael@0 | 10214 | let StkProactiveCmdHelper = this.context.StkProactiveCmdHelper; |
michael@0 | 10215 | let menu = {}; |
michael@0 | 10216 | |
michael@0 | 10217 | let ctlv = StkProactiveCmdHelper.searchForTag(COMPREHENSIONTLV_TAG_ALPHA_ID, ctlvs); |
michael@0 | 10218 | if (ctlv) { |
michael@0 | 10219 | menu.title = ctlv.value.identifier; |
michael@0 | 10220 | } |
michael@0 | 10221 | |
michael@0 | 10222 | menu.items = []; |
michael@0 | 10223 | for (let i = 0; i < ctlvs.length; i++) { |
michael@0 | 10224 | let ctlv = ctlvs[i]; |
michael@0 | 10225 | if (ctlv.tag == COMPREHENSIONTLV_TAG_ITEM) { |
michael@0 | 10226 | menu.items.push(ctlv.value); |
michael@0 | 10227 | } |
michael@0 | 10228 | } |
michael@0 | 10229 | |
michael@0 | 10230 | if (menu.items.length === 0) { |
michael@0 | 10231 | this.context.RIL.sendStkTerminalResponse({ |
michael@0 | 10232 | command: cmdDetails, |
michael@0 | 10233 | resultCode: STK_RESULT_REQUIRED_VALUES_MISSING}); |
michael@0 | 10234 | throw new Error("Stk Menu: Required value missing : items"); |
michael@0 | 10235 | } |
michael@0 | 10236 | |
michael@0 | 10237 | ctlv = StkProactiveCmdHelper.searchForTag(COMPREHENSIONTLV_TAG_ITEM_ID, ctlvs); |
michael@0 | 10238 | if (ctlv) { |
michael@0 | 10239 | menu.defaultItem = ctlv.value.identifier - 1; |
michael@0 | 10240 | } |
michael@0 | 10241 | |
michael@0 | 10242 | // The 1st bit and 2nd bit determines the presentation type. |
michael@0 | 10243 | menu.presentationType = cmdDetails.commandQualifier & 0x03; |
michael@0 | 10244 | |
michael@0 | 10245 | // Help information available. |
michael@0 | 10246 | if (cmdDetails.commandQualifier & 0x80) { |
michael@0 | 10247 | menu.isHelpAvailable = true; |
michael@0 | 10248 | } |
michael@0 | 10249 | |
michael@0 | 10250 | ctlv = StkProactiveCmdHelper.searchForTag(COMPREHENSIONTLV_TAG_NEXT_ACTION_IND, ctlvs); |
michael@0 | 10251 | if (ctlv) { |
michael@0 | 10252 | menu.nextActionList = ctlv.value; |
michael@0 | 10253 | } |
michael@0 | 10254 | |
michael@0 | 10255 | return menu; |
michael@0 | 10256 | }, |
michael@0 | 10257 | |
michael@0 | 10258 | processDisplayText: function(cmdDetails, ctlvs) { |
michael@0 | 10259 | let StkProactiveCmdHelper = this.context.StkProactiveCmdHelper; |
michael@0 | 10260 | let textMsg = {}; |
michael@0 | 10261 | |
michael@0 | 10262 | let ctlv = StkProactiveCmdHelper.searchForTag(COMPREHENSIONTLV_TAG_TEXT_STRING, ctlvs); |
michael@0 | 10263 | if (!ctlv) { |
michael@0 | 10264 | this.context.RIL.sendStkTerminalResponse({ |
michael@0 | 10265 | command: cmdDetails, |
michael@0 | 10266 | resultCode: STK_RESULT_REQUIRED_VALUES_MISSING}); |
michael@0 | 10267 | throw new Error("Stk Display Text: Required value missing : Text String"); |
michael@0 | 10268 | } |
michael@0 | 10269 | textMsg.text = ctlv.value.textString; |
michael@0 | 10270 | |
michael@0 | 10271 | ctlv = StkProactiveCmdHelper.searchForTag(COMPREHENSIONTLV_TAG_IMMEDIATE_RESPONSE, ctlvs); |
michael@0 | 10272 | if (ctlv) { |
michael@0 | 10273 | textMsg.responseNeeded = true; |
michael@0 | 10274 | } |
michael@0 | 10275 | |
michael@0 | 10276 | ctlv = StkProactiveCmdHelper.searchForTag(COMPREHENSIONTLV_TAG_DURATION, ctlvs); |
michael@0 | 10277 | if (ctlv) { |
michael@0 | 10278 | textMsg.duration = ctlv.value; |
michael@0 | 10279 | } |
michael@0 | 10280 | |
michael@0 | 10281 | // High priority. |
michael@0 | 10282 | if (cmdDetails.commandQualifier & 0x01) { |
michael@0 | 10283 | textMsg.isHighPriority = true; |
michael@0 | 10284 | } |
michael@0 | 10285 | |
michael@0 | 10286 | // User clear. |
michael@0 | 10287 | if (cmdDetails.commandQualifier & 0x80) { |
michael@0 | 10288 | textMsg.userClear = true; |
michael@0 | 10289 | } |
michael@0 | 10290 | |
michael@0 | 10291 | return textMsg; |
michael@0 | 10292 | }, |
michael@0 | 10293 | |
michael@0 | 10294 | processSetUpIdleModeText: function(cmdDetails, ctlvs) { |
michael@0 | 10295 | let textMsg = {}; |
michael@0 | 10296 | |
michael@0 | 10297 | let ctlv = this.context.StkProactiveCmdHelper.searchForTag( |
michael@0 | 10298 | COMPREHENSIONTLV_TAG_TEXT_STRING, ctlvs); |
michael@0 | 10299 | if (!ctlv) { |
michael@0 | 10300 | this.context.RIL.sendStkTerminalResponse({ |
michael@0 | 10301 | command: cmdDetails, |
michael@0 | 10302 | resultCode: STK_RESULT_REQUIRED_VALUES_MISSING}); |
michael@0 | 10303 | throw new Error("Stk Set Up Idle Text: Required value missing : Text String"); |
michael@0 | 10304 | } |
michael@0 | 10305 | textMsg.text = ctlv.value.textString; |
michael@0 | 10306 | |
michael@0 | 10307 | return textMsg; |
michael@0 | 10308 | }, |
michael@0 | 10309 | |
michael@0 | 10310 | processGetInkey: function(cmdDetails, ctlvs) { |
michael@0 | 10311 | let StkProactiveCmdHelper = this.context.StkProactiveCmdHelper; |
michael@0 | 10312 | let input = {}; |
michael@0 | 10313 | |
michael@0 | 10314 | let ctlv = StkProactiveCmdHelper.searchForTag(COMPREHENSIONTLV_TAG_TEXT_STRING, ctlvs); |
michael@0 | 10315 | if (!ctlv) { |
michael@0 | 10316 | this.context.RIL.sendStkTerminalResponse({ |
michael@0 | 10317 | command: cmdDetails, |
michael@0 | 10318 | resultCode: STK_RESULT_REQUIRED_VALUES_MISSING}); |
michael@0 | 10319 | throw new Error("Stk Get InKey: Required value missing : Text String"); |
michael@0 | 10320 | } |
michael@0 | 10321 | input.text = ctlv.value.textString; |
michael@0 | 10322 | |
michael@0 | 10323 | // duration |
michael@0 | 10324 | ctlv = StkProactiveCmdHelper.searchForTag( |
michael@0 | 10325 | COMPREHENSIONTLV_TAG_DURATION, ctlvs); |
michael@0 | 10326 | if (ctlv) { |
michael@0 | 10327 | input.duration = ctlv.value; |
michael@0 | 10328 | } |
michael@0 | 10329 | |
michael@0 | 10330 | input.minLength = 1; |
michael@0 | 10331 | input.maxLength = 1; |
michael@0 | 10332 | |
michael@0 | 10333 | // isAlphabet |
michael@0 | 10334 | if (cmdDetails.commandQualifier & 0x01) { |
michael@0 | 10335 | input.isAlphabet = true; |
michael@0 | 10336 | } |
michael@0 | 10337 | |
michael@0 | 10338 | // UCS2 |
michael@0 | 10339 | if (cmdDetails.commandQualifier & 0x02) { |
michael@0 | 10340 | input.isUCS2 = true; |
michael@0 | 10341 | } |
michael@0 | 10342 | |
michael@0 | 10343 | // Character sets defined in bit 1 and bit 2 are disable and |
michael@0 | 10344 | // the YES/NO reponse is required. |
michael@0 | 10345 | if (cmdDetails.commandQualifier & 0x04) { |
michael@0 | 10346 | input.isYesNoRequested = true; |
michael@0 | 10347 | } |
michael@0 | 10348 | |
michael@0 | 10349 | // Help information available. |
michael@0 | 10350 | if (cmdDetails.commandQualifier & 0x80) { |
michael@0 | 10351 | input.isHelpAvailable = true; |
michael@0 | 10352 | } |
michael@0 | 10353 | |
michael@0 | 10354 | return input; |
michael@0 | 10355 | }, |
michael@0 | 10356 | |
michael@0 | 10357 | processGetInput: function(cmdDetails, ctlvs) { |
michael@0 | 10358 | let StkProactiveCmdHelper = this.context.StkProactiveCmdHelper; |
michael@0 | 10359 | let input = {}; |
michael@0 | 10360 | |
michael@0 | 10361 | let ctlv = StkProactiveCmdHelper.searchForTag(COMPREHENSIONTLV_TAG_TEXT_STRING, ctlvs); |
michael@0 | 10362 | if (!ctlv) { |
michael@0 | 10363 | this.context.RIL.sendStkTerminalResponse({ |
michael@0 | 10364 | command: cmdDetails, |
michael@0 | 10365 | resultCode: STK_RESULT_REQUIRED_VALUES_MISSING}); |
michael@0 | 10366 | throw new Error("Stk Get Input: Required value missing : Text String"); |
michael@0 | 10367 | } |
michael@0 | 10368 | input.text = ctlv.value.textString; |
michael@0 | 10369 | |
michael@0 | 10370 | ctlv = StkProactiveCmdHelper.searchForTag(COMPREHENSIONTLV_TAG_RESPONSE_LENGTH, ctlvs); |
michael@0 | 10371 | if (ctlv) { |
michael@0 | 10372 | input.minLength = ctlv.value.minLength; |
michael@0 | 10373 | input.maxLength = ctlv.value.maxLength; |
michael@0 | 10374 | } |
michael@0 | 10375 | |
michael@0 | 10376 | ctlv = StkProactiveCmdHelper.searchForTag(COMPREHENSIONTLV_TAG_DEFAULT_TEXT, ctlvs); |
michael@0 | 10377 | if (ctlv) { |
michael@0 | 10378 | input.defaultText = ctlv.value.textString; |
michael@0 | 10379 | } |
michael@0 | 10380 | |
michael@0 | 10381 | // Alphabet only |
michael@0 | 10382 | if (cmdDetails.commandQualifier & 0x01) { |
michael@0 | 10383 | input.isAlphabet = true; |
michael@0 | 10384 | } |
michael@0 | 10385 | |
michael@0 | 10386 | // UCS2 |
michael@0 | 10387 | if (cmdDetails.commandQualifier & 0x02) { |
michael@0 | 10388 | input.isUCS2 = true; |
michael@0 | 10389 | } |
michael@0 | 10390 | |
michael@0 | 10391 | // User input shall not be revealed |
michael@0 | 10392 | if (cmdDetails.commandQualifier & 0x04) { |
michael@0 | 10393 | input.hideInput = true; |
michael@0 | 10394 | } |
michael@0 | 10395 | |
michael@0 | 10396 | // User input in SMS packed format |
michael@0 | 10397 | if (cmdDetails.commandQualifier & 0x08) { |
michael@0 | 10398 | input.isPacked = true; |
michael@0 | 10399 | } |
michael@0 | 10400 | |
michael@0 | 10401 | // Help information available. |
michael@0 | 10402 | if (cmdDetails.commandQualifier & 0x80) { |
michael@0 | 10403 | input.isHelpAvailable = true; |
michael@0 | 10404 | } |
michael@0 | 10405 | |
michael@0 | 10406 | return input; |
michael@0 | 10407 | }, |
michael@0 | 10408 | |
michael@0 | 10409 | processEventNotify: function(cmdDetails, ctlvs) { |
michael@0 | 10410 | let textMsg = {}; |
michael@0 | 10411 | |
michael@0 | 10412 | let ctlv = this.context.StkProactiveCmdHelper.searchForTag( |
michael@0 | 10413 | COMPREHENSIONTLV_TAG_ALPHA_ID, ctlvs); |
michael@0 | 10414 | if (!ctlv) { |
michael@0 | 10415 | this.context.RIL.sendStkTerminalResponse({ |
michael@0 | 10416 | command: cmdDetails, |
michael@0 | 10417 | resultCode: STK_RESULT_REQUIRED_VALUES_MISSING}); |
michael@0 | 10418 | throw new Error("Stk Event Notfiy: Required value missing : Alpha ID"); |
michael@0 | 10419 | } |
michael@0 | 10420 | textMsg.text = ctlv.value.identifier; |
michael@0 | 10421 | |
michael@0 | 10422 | return textMsg; |
michael@0 | 10423 | }, |
michael@0 | 10424 | |
michael@0 | 10425 | processSetupCall: function(cmdDetails, ctlvs) { |
michael@0 | 10426 | let StkProactiveCmdHelper = this.context.StkProactiveCmdHelper; |
michael@0 | 10427 | let call = {}; |
michael@0 | 10428 | let iter = Iterator(ctlvs); |
michael@0 | 10429 | |
michael@0 | 10430 | let ctlv = StkProactiveCmdHelper.searchForNextTag(COMPREHENSIONTLV_TAG_ALPHA_ID, iter); |
michael@0 | 10431 | if (ctlv) { |
michael@0 | 10432 | call.confirmMessage = ctlv.value.identifier; |
michael@0 | 10433 | } |
michael@0 | 10434 | |
michael@0 | 10435 | ctlv = StkProactiveCmdHelper.searchForNextTag(COMPREHENSIONTLV_TAG_ALPHA_ID, iter); |
michael@0 | 10436 | if (ctlv) { |
michael@0 | 10437 | call.callMessage = ctlv.value.identifier; |
michael@0 | 10438 | } |
michael@0 | 10439 | |
michael@0 | 10440 | ctlv = StkProactiveCmdHelper.searchForTag(COMPREHENSIONTLV_TAG_ADDRESS, ctlvs); |
michael@0 | 10441 | if (!ctlv) { |
michael@0 | 10442 | this.context.RIL.sendStkTerminalResponse({ |
michael@0 | 10443 | command: cmdDetails, |
michael@0 | 10444 | resultCode: STK_RESULT_REQUIRED_VALUES_MISSING}); |
michael@0 | 10445 | throw new Error("Stk Set Up Call: Required value missing : Adress"); |
michael@0 | 10446 | } |
michael@0 | 10447 | call.address = ctlv.value.number; |
michael@0 | 10448 | |
michael@0 | 10449 | // see 3GPP TS 31.111 section 6.4.13 |
michael@0 | 10450 | ctlv = StkProactiveCmdHelper.searchForTag(COMPREHENSIONTLV_TAG_DURATION, ctlvs); |
michael@0 | 10451 | if (ctlv) { |
michael@0 | 10452 | call.duration = ctlv.value; |
michael@0 | 10453 | } |
michael@0 | 10454 | |
michael@0 | 10455 | return call; |
michael@0 | 10456 | }, |
michael@0 | 10457 | |
michael@0 | 10458 | processLaunchBrowser: function(cmdDetails, ctlvs) { |
michael@0 | 10459 | let StkProactiveCmdHelper = this.context.StkProactiveCmdHelper; |
michael@0 | 10460 | let browser = {}; |
michael@0 | 10461 | |
michael@0 | 10462 | let ctlv = StkProactiveCmdHelper.searchForTag(COMPREHENSIONTLV_TAG_URL, ctlvs); |
michael@0 | 10463 | if (!ctlv) { |
michael@0 | 10464 | this.context.RIL.sendStkTerminalResponse({ |
michael@0 | 10465 | command: cmdDetails, |
michael@0 | 10466 | resultCode: STK_RESULT_REQUIRED_VALUES_MISSING}); |
michael@0 | 10467 | throw new Error("Stk Launch Browser: Required value missing : URL"); |
michael@0 | 10468 | } |
michael@0 | 10469 | browser.url = ctlv.value.url; |
michael@0 | 10470 | |
michael@0 | 10471 | ctlv = StkProactiveCmdHelper.searchForTag(COMPREHENSIONTLV_TAG_ALPHA_ID, ctlvs); |
michael@0 | 10472 | if (ctlv) { |
michael@0 | 10473 | browser.confirmMessage = ctlv.value.identifier; |
michael@0 | 10474 | } |
michael@0 | 10475 | |
michael@0 | 10476 | browser.mode = cmdDetails.commandQualifier & 0x03; |
michael@0 | 10477 | |
michael@0 | 10478 | return browser; |
michael@0 | 10479 | }, |
michael@0 | 10480 | |
michael@0 | 10481 | processPlayTone: function(cmdDetails, ctlvs) { |
michael@0 | 10482 | let StkProactiveCmdHelper = this.context.StkProactiveCmdHelper; |
michael@0 | 10483 | let playTone = {}; |
michael@0 | 10484 | |
michael@0 | 10485 | let ctlv = StkProactiveCmdHelper.searchForTag( |
michael@0 | 10486 | COMPREHENSIONTLV_TAG_ALPHA_ID, ctlvs); |
michael@0 | 10487 | if (ctlv) { |
michael@0 | 10488 | playTone.text = ctlv.value.identifier; |
michael@0 | 10489 | } |
michael@0 | 10490 | |
michael@0 | 10491 | ctlv = StkProactiveCmdHelper.searchForTag(COMPREHENSIONTLV_TAG_TONE, ctlvs); |
michael@0 | 10492 | if (ctlv) { |
michael@0 | 10493 | playTone.tone = ctlv.value.tone; |
michael@0 | 10494 | } |
michael@0 | 10495 | |
michael@0 | 10496 | ctlv = StkProactiveCmdHelper.searchForTag( |
michael@0 | 10497 | COMPREHENSIONTLV_TAG_DURATION, ctlvs); |
michael@0 | 10498 | if (ctlv) { |
michael@0 | 10499 | playTone.duration = ctlv.value; |
michael@0 | 10500 | } |
michael@0 | 10501 | |
michael@0 | 10502 | // vibrate is only defined in TS 102.223 |
michael@0 | 10503 | playTone.isVibrate = (cmdDetails.commandQualifier & 0x01) !== 0x00; |
michael@0 | 10504 | |
michael@0 | 10505 | return playTone; |
michael@0 | 10506 | }, |
michael@0 | 10507 | |
michael@0 | 10508 | /** |
michael@0 | 10509 | * Construct a param for Provide Local Information |
michael@0 | 10510 | * |
michael@0 | 10511 | * @param cmdDetails |
michael@0 | 10512 | * The value object of CommandDetails TLV. |
michael@0 | 10513 | * @param ctlvs |
michael@0 | 10514 | * The all TLVs in this proactive command. |
michael@0 | 10515 | */ |
michael@0 | 10516 | processProvideLocalInfo: function(cmdDetails, ctlvs) { |
michael@0 | 10517 | let provideLocalInfo = { |
michael@0 | 10518 | localInfoType: cmdDetails.commandQualifier |
michael@0 | 10519 | }; |
michael@0 | 10520 | return provideLocalInfo; |
michael@0 | 10521 | }, |
michael@0 | 10522 | |
michael@0 | 10523 | processTimerManagement: function(cmdDetails, ctlvs) { |
michael@0 | 10524 | let StkProactiveCmdHelper = this.context.StkProactiveCmdHelper; |
michael@0 | 10525 | let timer = { |
michael@0 | 10526 | timerAction: cmdDetails.commandQualifier |
michael@0 | 10527 | }; |
michael@0 | 10528 | |
michael@0 | 10529 | let ctlv = StkProactiveCmdHelper.searchForTag( |
michael@0 | 10530 | COMPREHENSIONTLV_TAG_TIMER_IDENTIFIER, ctlvs); |
michael@0 | 10531 | if (ctlv) { |
michael@0 | 10532 | timer.timerId = ctlv.value.timerId; |
michael@0 | 10533 | } |
michael@0 | 10534 | |
michael@0 | 10535 | ctlv = StkProactiveCmdHelper.searchForTag( |
michael@0 | 10536 | COMPREHENSIONTLV_TAG_TIMER_VALUE, ctlvs); |
michael@0 | 10537 | if (ctlv) { |
michael@0 | 10538 | timer.timerValue = ctlv.value.timerValue; |
michael@0 | 10539 | } |
michael@0 | 10540 | |
michael@0 | 10541 | return timer; |
michael@0 | 10542 | }, |
michael@0 | 10543 | |
michael@0 | 10544 | /** |
michael@0 | 10545 | * Construct a param for BIP commands. |
michael@0 | 10546 | * |
michael@0 | 10547 | * @param cmdDetails |
michael@0 | 10548 | * The value object of CommandDetails TLV. |
michael@0 | 10549 | * @param ctlvs |
michael@0 | 10550 | * The all TLVs in this proactive command. |
michael@0 | 10551 | */ |
michael@0 | 10552 | processBipMessage: function(cmdDetails, ctlvs) { |
michael@0 | 10553 | let bipMsg = {}; |
michael@0 | 10554 | |
michael@0 | 10555 | let ctlv = this.context.StkProactiveCmdHelper.searchForTag( |
michael@0 | 10556 | COMPREHENSIONTLV_TAG_ALPHA_ID, ctlvs); |
michael@0 | 10557 | if (ctlv) { |
michael@0 | 10558 | bipMsg.text = ctlv.value.identifier; |
michael@0 | 10559 | } |
michael@0 | 10560 | |
michael@0 | 10561 | return bipMsg; |
michael@0 | 10562 | } |
michael@0 | 10563 | }; |
michael@0 | 10564 | StkCommandParamsFactoryObject.prototype[STK_CMD_REFRESH] = function STK_CMD_REFRESH(cmdDetails, ctlvs) { |
michael@0 | 10565 | return this.processRefresh(cmdDetails, ctlvs); |
michael@0 | 10566 | }; |
michael@0 | 10567 | StkCommandParamsFactoryObject.prototype[STK_CMD_POLL_INTERVAL] = function STK_CMD_POLL_INTERVAL(cmdDetails, ctlvs) { |
michael@0 | 10568 | return this.processPollInterval(cmdDetails, ctlvs); |
michael@0 | 10569 | }; |
michael@0 | 10570 | StkCommandParamsFactoryObject.prototype[STK_CMD_POLL_OFF] = function STK_CMD_POLL_OFF(cmdDetails, ctlvs) { |
michael@0 | 10571 | return this.processPollOff(cmdDetails, ctlvs); |
michael@0 | 10572 | }; |
michael@0 | 10573 | StkCommandParamsFactoryObject.prototype[STK_CMD_PROVIDE_LOCAL_INFO] = function STK_CMD_PROVIDE_LOCAL_INFO(cmdDetails, ctlvs) { |
michael@0 | 10574 | return this.processProvideLocalInfo(cmdDetails, ctlvs); |
michael@0 | 10575 | }; |
michael@0 | 10576 | StkCommandParamsFactoryObject.prototype[STK_CMD_SET_UP_EVENT_LIST] = function STK_CMD_SET_UP_EVENT_LIST(cmdDetails, ctlvs) { |
michael@0 | 10577 | return this.processSetUpEventList(cmdDetails, ctlvs); |
michael@0 | 10578 | }; |
michael@0 | 10579 | StkCommandParamsFactoryObject.prototype[STK_CMD_SET_UP_MENU] = function STK_CMD_SET_UP_MENU(cmdDetails, ctlvs) { |
michael@0 | 10580 | return this.processSelectItem(cmdDetails, ctlvs); |
michael@0 | 10581 | }; |
michael@0 | 10582 | StkCommandParamsFactoryObject.prototype[STK_CMD_SELECT_ITEM] = function STK_CMD_SELECT_ITEM(cmdDetails, ctlvs) { |
michael@0 | 10583 | return this.processSelectItem(cmdDetails, ctlvs); |
michael@0 | 10584 | }; |
michael@0 | 10585 | StkCommandParamsFactoryObject.prototype[STK_CMD_DISPLAY_TEXT] = function STK_CMD_DISPLAY_TEXT(cmdDetails, ctlvs) { |
michael@0 | 10586 | return this.processDisplayText(cmdDetails, ctlvs); |
michael@0 | 10587 | }; |
michael@0 | 10588 | StkCommandParamsFactoryObject.prototype[STK_CMD_SET_UP_IDLE_MODE_TEXT] = function STK_CMD_SET_UP_IDLE_MODE_TEXT(cmdDetails, ctlvs) { |
michael@0 | 10589 | return this.processSetUpIdleModeText(cmdDetails, ctlvs); |
michael@0 | 10590 | }; |
michael@0 | 10591 | StkCommandParamsFactoryObject.prototype[STK_CMD_GET_INKEY] = function STK_CMD_GET_INKEY(cmdDetails, ctlvs) { |
michael@0 | 10592 | return this.processGetInkey(cmdDetails, ctlvs); |
michael@0 | 10593 | }; |
michael@0 | 10594 | StkCommandParamsFactoryObject.prototype[STK_CMD_GET_INPUT] = function STK_CMD_GET_INPUT(cmdDetails, ctlvs) { |
michael@0 | 10595 | return this.processGetInput(cmdDetails, ctlvs); |
michael@0 | 10596 | }; |
michael@0 | 10597 | StkCommandParamsFactoryObject.prototype[STK_CMD_SEND_SS] = function STK_CMD_SEND_SS(cmdDetails, ctlvs) { |
michael@0 | 10598 | return this.processEventNotify(cmdDetails, ctlvs); |
michael@0 | 10599 | }; |
michael@0 | 10600 | StkCommandParamsFactoryObject.prototype[STK_CMD_SEND_USSD] = function STK_CMD_SEND_USSD(cmdDetails, ctlvs) { |
michael@0 | 10601 | return this.processEventNotify(cmdDetails, ctlvs); |
michael@0 | 10602 | }; |
michael@0 | 10603 | StkCommandParamsFactoryObject.prototype[STK_CMD_SEND_SMS] = function STK_CMD_SEND_SMS(cmdDetails, ctlvs) { |
michael@0 | 10604 | return this.processEventNotify(cmdDetails, ctlvs); |
michael@0 | 10605 | }; |
michael@0 | 10606 | StkCommandParamsFactoryObject.prototype[STK_CMD_SEND_DTMF] = function STK_CMD_SEND_DTMF(cmdDetails, ctlvs) { |
michael@0 | 10607 | return this.processEventNotify(cmdDetails, ctlvs); |
michael@0 | 10608 | }; |
michael@0 | 10609 | StkCommandParamsFactoryObject.prototype[STK_CMD_SET_UP_CALL] = function STK_CMD_SET_UP_CALL(cmdDetails, ctlvs) { |
michael@0 | 10610 | return this.processSetupCall(cmdDetails, ctlvs); |
michael@0 | 10611 | }; |
michael@0 | 10612 | StkCommandParamsFactoryObject.prototype[STK_CMD_LAUNCH_BROWSER] = function STK_CMD_LAUNCH_BROWSER(cmdDetails, ctlvs) { |
michael@0 | 10613 | return this.processLaunchBrowser(cmdDetails, ctlvs); |
michael@0 | 10614 | }; |
michael@0 | 10615 | StkCommandParamsFactoryObject.prototype[STK_CMD_PLAY_TONE] = function STK_CMD_PLAY_TONE(cmdDetails, ctlvs) { |
michael@0 | 10616 | return this.processPlayTone(cmdDetails, ctlvs); |
michael@0 | 10617 | }; |
michael@0 | 10618 | StkCommandParamsFactoryObject.prototype[STK_CMD_TIMER_MANAGEMENT] = function STK_CMD_TIMER_MANAGEMENT(cmdDetails, ctlvs) { |
michael@0 | 10619 | return this.processTimerManagement(cmdDetails, ctlvs); |
michael@0 | 10620 | }; |
michael@0 | 10621 | StkCommandParamsFactoryObject.prototype[STK_CMD_OPEN_CHANNEL] = function STK_CMD_OPEN_CHANNEL(cmdDetails, ctlvs) { |
michael@0 | 10622 | return this.processBipMessage(cmdDetails, ctlvs); |
michael@0 | 10623 | }; |
michael@0 | 10624 | StkCommandParamsFactoryObject.prototype[STK_CMD_CLOSE_CHANNEL] = function STK_CMD_CLOSE_CHANNEL(cmdDetails, ctlvs) { |
michael@0 | 10625 | return this.processBipMessage(cmdDetails, ctlvs); |
michael@0 | 10626 | }; |
michael@0 | 10627 | StkCommandParamsFactoryObject.prototype[STK_CMD_RECEIVE_DATA] = function STK_CMD_RECEIVE_DATA(cmdDetails, ctlvs) { |
michael@0 | 10628 | return this.processBipMessage(cmdDetails, ctlvs); |
michael@0 | 10629 | }; |
michael@0 | 10630 | StkCommandParamsFactoryObject.prototype[STK_CMD_SEND_DATA] = function STK_CMD_SEND_DATA(cmdDetails, ctlvs) { |
michael@0 | 10631 | return this.processBipMessage(cmdDetails, ctlvs); |
michael@0 | 10632 | }; |
michael@0 | 10633 | |
michael@0 | 10634 | function StkProactiveCmdHelperObject(aContext) { |
michael@0 | 10635 | this.context = aContext; |
michael@0 | 10636 | } |
michael@0 | 10637 | StkProactiveCmdHelperObject.prototype = { |
michael@0 | 10638 | context: null, |
michael@0 | 10639 | |
michael@0 | 10640 | retrieve: function(tag, length) { |
michael@0 | 10641 | let method = this[tag]; |
michael@0 | 10642 | if (typeof method != "function") { |
michael@0 | 10643 | if (DEBUG) { |
michael@0 | 10644 | this.context.debug("Unknown comprehension tag " + tag.toString(16)); |
michael@0 | 10645 | } |
michael@0 | 10646 | let Buf = this.context.Buf; |
michael@0 | 10647 | Buf.seekIncoming(length * Buf.PDU_HEX_OCTET_SIZE); |
michael@0 | 10648 | return null; |
michael@0 | 10649 | } |
michael@0 | 10650 | return method.call(this, length); |
michael@0 | 10651 | }, |
michael@0 | 10652 | |
michael@0 | 10653 | /** |
michael@0 | 10654 | * Command Details. |
michael@0 | 10655 | * |
michael@0 | 10656 | * | Byte | Description | Length | |
michael@0 | 10657 | * | 1 | Command details Tag | 1 | |
michael@0 | 10658 | * | 2 | Length = 03 | 1 | |
michael@0 | 10659 | * | 3 | Command number | 1 | |
michael@0 | 10660 | * | 4 | Type of Command | 1 | |
michael@0 | 10661 | * | 5 | Command Qualifier | 1 | |
michael@0 | 10662 | */ |
michael@0 | 10663 | retrieveCommandDetails: function(length) { |
michael@0 | 10664 | let GsmPDUHelper = this.context.GsmPDUHelper; |
michael@0 | 10665 | let cmdDetails = { |
michael@0 | 10666 | commandNumber: GsmPDUHelper.readHexOctet(), |
michael@0 | 10667 | typeOfCommand: GsmPDUHelper.readHexOctet(), |
michael@0 | 10668 | commandQualifier: GsmPDUHelper.readHexOctet() |
michael@0 | 10669 | }; |
michael@0 | 10670 | return cmdDetails; |
michael@0 | 10671 | }, |
michael@0 | 10672 | |
michael@0 | 10673 | /** |
michael@0 | 10674 | * Device Identities. |
michael@0 | 10675 | * |
michael@0 | 10676 | * | Byte | Description | Length | |
michael@0 | 10677 | * | 1 | Device Identity Tag | 1 | |
michael@0 | 10678 | * | 2 | Length = 02 | 1 | |
michael@0 | 10679 | * | 3 | Source device Identity | 1 | |
michael@0 | 10680 | * | 4 | Destination device Id | 1 | |
michael@0 | 10681 | */ |
michael@0 | 10682 | retrieveDeviceId: function(length) { |
michael@0 | 10683 | let GsmPDUHelper = this.context.GsmPDUHelper; |
michael@0 | 10684 | let deviceId = { |
michael@0 | 10685 | sourceId: GsmPDUHelper.readHexOctet(), |
michael@0 | 10686 | destinationId: GsmPDUHelper.readHexOctet() |
michael@0 | 10687 | }; |
michael@0 | 10688 | return deviceId; |
michael@0 | 10689 | }, |
michael@0 | 10690 | |
michael@0 | 10691 | /** |
michael@0 | 10692 | * Alpha Identifier. |
michael@0 | 10693 | * |
michael@0 | 10694 | * | Byte | Description | Length | |
michael@0 | 10695 | * | 1 | Alpha Identifier Tag | 1 | |
michael@0 | 10696 | * | 2 ~ (Y-1)+2 | Length (X) | Y | |
michael@0 | 10697 | * | (Y-1)+3 ~ | Alpha identfier | X | |
michael@0 | 10698 | * | (Y-1)+X+2 | | | |
michael@0 | 10699 | */ |
michael@0 | 10700 | retrieveAlphaId: function(length) { |
michael@0 | 10701 | let alphaId = { |
michael@0 | 10702 | identifier: this.context.ICCPDUHelper.readAlphaIdentifier(length) |
michael@0 | 10703 | }; |
michael@0 | 10704 | return alphaId; |
michael@0 | 10705 | }, |
michael@0 | 10706 | |
michael@0 | 10707 | /** |
michael@0 | 10708 | * Duration. |
michael@0 | 10709 | * |
michael@0 | 10710 | * | Byte | Description | Length | |
michael@0 | 10711 | * | 1 | Response Length Tag | 1 | |
michael@0 | 10712 | * | 2 | Lenth = 02 | 1 | |
michael@0 | 10713 | * | 3 | Time unit | 1 | |
michael@0 | 10714 | * | 4 | Time interval | 1 | |
michael@0 | 10715 | */ |
michael@0 | 10716 | retrieveDuration: function(length) { |
michael@0 | 10717 | let GsmPDUHelper = this.context.GsmPDUHelper; |
michael@0 | 10718 | let duration = { |
michael@0 | 10719 | timeUnit: GsmPDUHelper.readHexOctet(), |
michael@0 | 10720 | timeInterval: GsmPDUHelper.readHexOctet(), |
michael@0 | 10721 | }; |
michael@0 | 10722 | return duration; |
michael@0 | 10723 | }, |
michael@0 | 10724 | |
michael@0 | 10725 | /** |
michael@0 | 10726 | * Address. |
michael@0 | 10727 | * |
michael@0 | 10728 | * | Byte | Description | Length | |
michael@0 | 10729 | * | 1 | Alpha Identifier Tag | 1 | |
michael@0 | 10730 | * | 2 ~ (Y-1)+2 | Length (X) | Y | |
michael@0 | 10731 | * | (Y-1)+3 | TON and NPI | 1 | |
michael@0 | 10732 | * | (Y-1)+4 ~ | Dialling number | X | |
michael@0 | 10733 | * | (Y-1)+X+2 | | | |
michael@0 | 10734 | */ |
michael@0 | 10735 | retrieveAddress: function(length) { |
michael@0 | 10736 | let address = { |
michael@0 | 10737 | number : this.context.ICCPDUHelper.readDiallingNumber(length) |
michael@0 | 10738 | }; |
michael@0 | 10739 | return address; |
michael@0 | 10740 | }, |
michael@0 | 10741 | |
michael@0 | 10742 | /** |
michael@0 | 10743 | * Text String. |
michael@0 | 10744 | * |
michael@0 | 10745 | * | Byte | Description | Length | |
michael@0 | 10746 | * | 1 | Text String Tag | 1 | |
michael@0 | 10747 | * | 2 ~ (Y-1)+2 | Length (X) | Y | |
michael@0 | 10748 | * | (Y-1)+3 | Data coding scheme | 1 | |
michael@0 | 10749 | * | (Y-1)+4~ | Text String | X | |
michael@0 | 10750 | * | (Y-1)+X+2 | | | |
michael@0 | 10751 | */ |
michael@0 | 10752 | retrieveTextString: function(length) { |
michael@0 | 10753 | if (!length) { |
michael@0 | 10754 | // null string. |
michael@0 | 10755 | return {textString: null}; |
michael@0 | 10756 | } |
michael@0 | 10757 | |
michael@0 | 10758 | let GsmPDUHelper = this.context.GsmPDUHelper; |
michael@0 | 10759 | let text = { |
michael@0 | 10760 | codingScheme: GsmPDUHelper.readHexOctet() |
michael@0 | 10761 | }; |
michael@0 | 10762 | |
michael@0 | 10763 | length--; // -1 for the codingScheme. |
michael@0 | 10764 | switch (text.codingScheme & 0x0f) { |
michael@0 | 10765 | case STK_TEXT_CODING_GSM_7BIT_PACKED: |
michael@0 | 10766 | text.textString = GsmPDUHelper.readSeptetsToString(length * 8 / 7, 0, 0, 0); |
michael@0 | 10767 | break; |
michael@0 | 10768 | case STK_TEXT_CODING_GSM_8BIT: |
michael@0 | 10769 | text.textString = |
michael@0 | 10770 | this.context.ICCPDUHelper.read8BitUnpackedToString(length); |
michael@0 | 10771 | break; |
michael@0 | 10772 | case STK_TEXT_CODING_UCS2: |
michael@0 | 10773 | text.textString = GsmPDUHelper.readUCS2String(length); |
michael@0 | 10774 | break; |
michael@0 | 10775 | } |
michael@0 | 10776 | return text; |
michael@0 | 10777 | }, |
michael@0 | 10778 | |
michael@0 | 10779 | /** |
michael@0 | 10780 | * Tone. |
michael@0 | 10781 | * |
michael@0 | 10782 | * | Byte | Description | Length | |
michael@0 | 10783 | * | 1 | Tone Tag | 1 | |
michael@0 | 10784 | * | 2 | Lenth = 01 | 1 | |
michael@0 | 10785 | * | 3 | Tone | 1 | |
michael@0 | 10786 | */ |
michael@0 | 10787 | retrieveTone: function(length) { |
michael@0 | 10788 | let tone = { |
michael@0 | 10789 | tone: this.context.GsmPDUHelper.readHexOctet(), |
michael@0 | 10790 | }; |
michael@0 | 10791 | return tone; |
michael@0 | 10792 | }, |
michael@0 | 10793 | |
michael@0 | 10794 | /** |
michael@0 | 10795 | * Item. |
michael@0 | 10796 | * |
michael@0 | 10797 | * | Byte | Description | Length | |
michael@0 | 10798 | * | 1 | Item Tag | 1 | |
michael@0 | 10799 | * | 2 ~ (Y-1)+2 | Length (X) | Y | |
michael@0 | 10800 | * | (Y-1)+3 | Identifier of item | 1 | |
michael@0 | 10801 | * | (Y-1)+4 ~ | Text string of item | X | |
michael@0 | 10802 | * | (Y-1)+X+2 | | | |
michael@0 | 10803 | */ |
michael@0 | 10804 | retrieveItem: function(length) { |
michael@0 | 10805 | // TS 102.223 ,clause 6.6.7 SET-UP MENU |
michael@0 | 10806 | // If the "Item data object for item 1" is a null data object |
michael@0 | 10807 | // (i.e. length = '00' and no value part), this is an indication to the ME |
michael@0 | 10808 | // to remove the existing menu from the menu system in the ME. |
michael@0 | 10809 | if (!length) { |
michael@0 | 10810 | return null; |
michael@0 | 10811 | } |
michael@0 | 10812 | let item = { |
michael@0 | 10813 | identifier: this.context.GsmPDUHelper.readHexOctet(), |
michael@0 | 10814 | text: this.context.ICCPDUHelper.readAlphaIdentifier(length - 1) |
michael@0 | 10815 | }; |
michael@0 | 10816 | return item; |
michael@0 | 10817 | }, |
michael@0 | 10818 | |
michael@0 | 10819 | /** |
michael@0 | 10820 | * Item Identifier. |
michael@0 | 10821 | * |
michael@0 | 10822 | * | Byte | Description | Length | |
michael@0 | 10823 | * | 1 | Item Identifier Tag | 1 | |
michael@0 | 10824 | * | 2 | Lenth = 01 | 1 | |
michael@0 | 10825 | * | 3 | Identifier of Item chosen | 1 | |
michael@0 | 10826 | */ |
michael@0 | 10827 | retrieveItemId: function(length) { |
michael@0 | 10828 | let itemId = { |
michael@0 | 10829 | identifier: this.context.GsmPDUHelper.readHexOctet() |
michael@0 | 10830 | }; |
michael@0 | 10831 | return itemId; |
michael@0 | 10832 | }, |
michael@0 | 10833 | |
michael@0 | 10834 | /** |
michael@0 | 10835 | * Response Length. |
michael@0 | 10836 | * |
michael@0 | 10837 | * | Byte | Description | Length | |
michael@0 | 10838 | * | 1 | Response Length Tag | 1 | |
michael@0 | 10839 | * | 2 | Lenth = 02 | 1 | |
michael@0 | 10840 | * | 3 | Minimum length of response | 1 | |
michael@0 | 10841 | * | 4 | Maximum length of response | 1 | |
michael@0 | 10842 | */ |
michael@0 | 10843 | retrieveResponseLength: function(length) { |
michael@0 | 10844 | let GsmPDUHelper = this.context.GsmPDUHelper; |
michael@0 | 10845 | let rspLength = { |
michael@0 | 10846 | minLength : GsmPDUHelper.readHexOctet(), |
michael@0 | 10847 | maxLength : GsmPDUHelper.readHexOctet() |
michael@0 | 10848 | }; |
michael@0 | 10849 | return rspLength; |
michael@0 | 10850 | }, |
michael@0 | 10851 | |
michael@0 | 10852 | /** |
michael@0 | 10853 | * File List. |
michael@0 | 10854 | * |
michael@0 | 10855 | * | Byte | Description | Length | |
michael@0 | 10856 | * | 1 | File List Tag | 1 | |
michael@0 | 10857 | * | 2 ~ (Y-1)+2 | Length (X) | Y | |
michael@0 | 10858 | * | (Y-1)+3 | Number of files | 1 | |
michael@0 | 10859 | * | (Y-1)+4 ~ | Files | X | |
michael@0 | 10860 | * | (Y-1)+X+2 | | | |
michael@0 | 10861 | */ |
michael@0 | 10862 | retrieveFileList: function(length) { |
michael@0 | 10863 | let num = this.context.GsmPDUHelper.readHexOctet(); |
michael@0 | 10864 | let fileList = ""; |
michael@0 | 10865 | length--; // -1 for the num octet. |
michael@0 | 10866 | for (let i = 0; i < 2 * length; i++) { |
michael@0 | 10867 | // Didn't use readHexOctet here, |
michael@0 | 10868 | // otherwise 0x00 will be "0", not "00" |
michael@0 | 10869 | fileList += String.fromCharCode(this.context.Buf.readUint16()); |
michael@0 | 10870 | } |
michael@0 | 10871 | return { |
michael@0 | 10872 | fileList: fileList |
michael@0 | 10873 | }; |
michael@0 | 10874 | }, |
michael@0 | 10875 | |
michael@0 | 10876 | /** |
michael@0 | 10877 | * Default Text. |
michael@0 | 10878 | * |
michael@0 | 10879 | * Same as Text String. |
michael@0 | 10880 | */ |
michael@0 | 10881 | retrieveDefaultText: function(length) { |
michael@0 | 10882 | return this.retrieveTextString(length); |
michael@0 | 10883 | }, |
michael@0 | 10884 | |
michael@0 | 10885 | /** |
michael@0 | 10886 | * Event List. |
michael@0 | 10887 | */ |
michael@0 | 10888 | retrieveEventList: function(length) { |
michael@0 | 10889 | if (!length) { |
michael@0 | 10890 | // null means an indication to ME to remove the existing list of events |
michael@0 | 10891 | // in ME. |
michael@0 | 10892 | return null; |
michael@0 | 10893 | } |
michael@0 | 10894 | |
michael@0 | 10895 | let GsmPDUHelper = this.context.GsmPDUHelper; |
michael@0 | 10896 | let eventList = []; |
michael@0 | 10897 | for (let i = 0; i < length; i++) { |
michael@0 | 10898 | eventList.push(GsmPDUHelper.readHexOctet()); |
michael@0 | 10899 | } |
michael@0 | 10900 | return { |
michael@0 | 10901 | eventList: eventList |
michael@0 | 10902 | }; |
michael@0 | 10903 | }, |
michael@0 | 10904 | |
michael@0 | 10905 | /** |
michael@0 | 10906 | * Timer Identifier. |
michael@0 | 10907 | * |
michael@0 | 10908 | * | Byte | Description | Length | |
michael@0 | 10909 | * | 1 | Timer Identifier Tag | 1 | |
michael@0 | 10910 | * | 2 | Length = 01 | 1 | |
michael@0 | 10911 | * | 3 | Timer Identifier | 1 | |
michael@0 | 10912 | */ |
michael@0 | 10913 | retrieveTimerId: function(length) { |
michael@0 | 10914 | let id = { |
michael@0 | 10915 | timerId: this.context.GsmPDUHelper.readHexOctet() |
michael@0 | 10916 | }; |
michael@0 | 10917 | return id; |
michael@0 | 10918 | }, |
michael@0 | 10919 | |
michael@0 | 10920 | /** |
michael@0 | 10921 | * Timer Value. |
michael@0 | 10922 | * |
michael@0 | 10923 | * | Byte | Description | Length | |
michael@0 | 10924 | * | 1 | Timer Value Tag | 1 | |
michael@0 | 10925 | * | 2 | Length = 03 | 1 | |
michael@0 | 10926 | * | 3 | Hour | 1 | |
michael@0 | 10927 | * | 4 | Minute | 1 | |
michael@0 | 10928 | * | 5 | Second | 1 | |
michael@0 | 10929 | */ |
michael@0 | 10930 | retrieveTimerValue: function(length) { |
michael@0 | 10931 | let GsmPDUHelper = this.context.GsmPDUHelper; |
michael@0 | 10932 | let value = { |
michael@0 | 10933 | timerValue: (GsmPDUHelper.readSwappedNibbleBcdNum(1) * 60 * 60) + |
michael@0 | 10934 | (GsmPDUHelper.readSwappedNibbleBcdNum(1) * 60) + |
michael@0 | 10935 | (GsmPDUHelper.readSwappedNibbleBcdNum(1)) |
michael@0 | 10936 | }; |
michael@0 | 10937 | return value; |
michael@0 | 10938 | }, |
michael@0 | 10939 | |
michael@0 | 10940 | /** |
michael@0 | 10941 | * Immediate Response. |
michael@0 | 10942 | * |
michael@0 | 10943 | * | Byte | Description | Length | |
michael@0 | 10944 | * | 1 | Immediate Response Tag | 1 | |
michael@0 | 10945 | * | 2 | Length = 00 | 1 | |
michael@0 | 10946 | */ |
michael@0 | 10947 | retrieveImmediaResponse: function(length) { |
michael@0 | 10948 | return {}; |
michael@0 | 10949 | }, |
michael@0 | 10950 | |
michael@0 | 10951 | /** |
michael@0 | 10952 | * URL |
michael@0 | 10953 | * |
michael@0 | 10954 | * | Byte | Description | Length | |
michael@0 | 10955 | * | 1 | URL Tag | 1 | |
michael@0 | 10956 | * | 2 ~ (Y+1) | Length(X) | Y | |
michael@0 | 10957 | * | (Y+2) ~ | URL | X | |
michael@0 | 10958 | * | (Y+1+X) | | | |
michael@0 | 10959 | */ |
michael@0 | 10960 | retrieveUrl: function(length) { |
michael@0 | 10961 | let GsmPDUHelper = this.context.GsmPDUHelper; |
michael@0 | 10962 | let s = ""; |
michael@0 | 10963 | for (let i = 0; i < length; i++) { |
michael@0 | 10964 | s += String.fromCharCode(GsmPDUHelper.readHexOctet()); |
michael@0 | 10965 | } |
michael@0 | 10966 | return {url: s}; |
michael@0 | 10967 | }, |
michael@0 | 10968 | |
michael@0 | 10969 | /** |
michael@0 | 10970 | * Next Action Indicator List. |
michael@0 | 10971 | * |
michael@0 | 10972 | * | Byte | Description | Length | |
michael@0 | 10973 | * | 1 | Next Action tag | 1 | |
michael@0 | 10974 | * | 1 | Length(X) | 1 | |
michael@0 | 10975 | * | 3~ | Next Action List | X | |
michael@0 | 10976 | * | 3+X-1 | | | |
michael@0 | 10977 | */ |
michael@0 | 10978 | retrieveNextActionList: function(length) { |
michael@0 | 10979 | let GsmPDUHelper = this.context.GsmPDUHelper; |
michael@0 | 10980 | let nextActionList = []; |
michael@0 | 10981 | for (let i = 0; i < length; i++) { |
michael@0 | 10982 | nextActionList.push(GsmPDUHelper.readHexOctet()); |
michael@0 | 10983 | } |
michael@0 | 10984 | return nextActionList; |
michael@0 | 10985 | }, |
michael@0 | 10986 | |
michael@0 | 10987 | searchForTag: function(tag, ctlvs) { |
michael@0 | 10988 | let iter = Iterator(ctlvs); |
michael@0 | 10989 | return this.searchForNextTag(tag, iter); |
michael@0 | 10990 | }, |
michael@0 | 10991 | |
michael@0 | 10992 | searchForNextTag: function(tag, iter) { |
michael@0 | 10993 | for (let [index, ctlv] in iter) { |
michael@0 | 10994 | if ((ctlv.tag & ~COMPREHENSIONTLV_FLAG_CR) == tag) { |
michael@0 | 10995 | return ctlv; |
michael@0 | 10996 | } |
michael@0 | 10997 | } |
michael@0 | 10998 | return null; |
michael@0 | 10999 | }, |
michael@0 | 11000 | }; |
michael@0 | 11001 | StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_COMMAND_DETAILS] = function COMPREHENSIONTLV_TAG_COMMAND_DETAILS(length) { |
michael@0 | 11002 | return this.retrieveCommandDetails(length); |
michael@0 | 11003 | }; |
michael@0 | 11004 | StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_DEVICE_ID] = function COMPREHENSIONTLV_TAG_DEVICE_ID(length) { |
michael@0 | 11005 | return this.retrieveDeviceId(length); |
michael@0 | 11006 | }; |
michael@0 | 11007 | StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_ALPHA_ID] = function COMPREHENSIONTLV_TAG_ALPHA_ID(length) { |
michael@0 | 11008 | return this.retrieveAlphaId(length); |
michael@0 | 11009 | }; |
michael@0 | 11010 | StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_DURATION] = function COMPREHENSIONTLV_TAG_DURATION(length) { |
michael@0 | 11011 | return this.retrieveDuration(length); |
michael@0 | 11012 | }; |
michael@0 | 11013 | StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_ADDRESS] = function COMPREHENSIONTLV_TAG_ADDRESS(length) { |
michael@0 | 11014 | return this.retrieveAddress(length); |
michael@0 | 11015 | }; |
michael@0 | 11016 | StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_TEXT_STRING] = function COMPREHENSIONTLV_TAG_TEXT_STRING(length) { |
michael@0 | 11017 | return this.retrieveTextString(length); |
michael@0 | 11018 | }; |
michael@0 | 11019 | StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_TONE] = function COMPREHENSIONTLV_TAG_TONE(length) { |
michael@0 | 11020 | return this.retrieveTone(length); |
michael@0 | 11021 | }; |
michael@0 | 11022 | StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_ITEM] = function COMPREHENSIONTLV_TAG_ITEM(length) { |
michael@0 | 11023 | return this.retrieveItem(length); |
michael@0 | 11024 | }; |
michael@0 | 11025 | StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_ITEM_ID] = function COMPREHENSIONTLV_TAG_ITEM_ID(length) { |
michael@0 | 11026 | return this.retrieveItemId(length); |
michael@0 | 11027 | }; |
michael@0 | 11028 | StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_RESPONSE_LENGTH] = function COMPREHENSIONTLV_TAG_RESPONSE_LENGTH(length) { |
michael@0 | 11029 | return this.retrieveResponseLength(length); |
michael@0 | 11030 | }; |
michael@0 | 11031 | StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_FILE_LIST] = function COMPREHENSIONTLV_TAG_FILE_LIST(length) { |
michael@0 | 11032 | return this.retrieveFileList(length); |
michael@0 | 11033 | }; |
michael@0 | 11034 | StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_DEFAULT_TEXT] = function COMPREHENSIONTLV_TAG_DEFAULT_TEXT(length) { |
michael@0 | 11035 | return this.retrieveDefaultText(length); |
michael@0 | 11036 | }; |
michael@0 | 11037 | StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_EVENT_LIST] = function COMPREHENSIONTLV_TAG_EVENT_LIST(length) { |
michael@0 | 11038 | return this.retrieveEventList(length); |
michael@0 | 11039 | }; |
michael@0 | 11040 | StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_TIMER_IDENTIFIER] = function COMPREHENSIONTLV_TAG_TIMER_IDENTIFIER(length) { |
michael@0 | 11041 | return this.retrieveTimerId(length); |
michael@0 | 11042 | }; |
michael@0 | 11043 | StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_TIMER_VALUE] = function COMPREHENSIONTLV_TAG_TIMER_VALUE(length) { |
michael@0 | 11044 | return this.retrieveTimerValue(length); |
michael@0 | 11045 | }; |
michael@0 | 11046 | StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_IMMEDIATE_RESPONSE] = function COMPREHENSIONTLV_TAG_IMMEDIATE_RESPONSE(length) { |
michael@0 | 11047 | return this.retrieveImmediaResponse(length); |
michael@0 | 11048 | }; |
michael@0 | 11049 | StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_URL] = function COMPREHENSIONTLV_TAG_URL(length) { |
michael@0 | 11050 | return this.retrieveUrl(length); |
michael@0 | 11051 | }; |
michael@0 | 11052 | StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_NEXT_ACTION_IND] = function COMPREHENSIONTLV_TAG_NEXT_ACTION_IND(length) { |
michael@0 | 11053 | return this.retrieveNextActionList(length); |
michael@0 | 11054 | }; |
michael@0 | 11055 | |
michael@0 | 11056 | function ComprehensionTlvHelperObject(aContext) { |
michael@0 | 11057 | this.context = aContext; |
michael@0 | 11058 | } |
michael@0 | 11059 | ComprehensionTlvHelperObject.prototype = { |
michael@0 | 11060 | context: null, |
michael@0 | 11061 | |
michael@0 | 11062 | /** |
michael@0 | 11063 | * Decode raw data to a Comprehension-TLV. |
michael@0 | 11064 | */ |
michael@0 | 11065 | decode: function() { |
michael@0 | 11066 | let GsmPDUHelper = this.context.GsmPDUHelper; |
michael@0 | 11067 | |
michael@0 | 11068 | let hlen = 0; // For header(tag field + length field) length. |
michael@0 | 11069 | let temp = GsmPDUHelper.readHexOctet(); |
michael@0 | 11070 | hlen++; |
michael@0 | 11071 | |
michael@0 | 11072 | // TS 101.220, clause 7.1.1 |
michael@0 | 11073 | let tag, cr; |
michael@0 | 11074 | switch (temp) { |
michael@0 | 11075 | // TS 101.220, clause 7.1.1 |
michael@0 | 11076 | case 0x0: // Not used. |
michael@0 | 11077 | case 0xff: // Not used. |
michael@0 | 11078 | case 0x80: // Reserved for future use. |
michael@0 | 11079 | throw new Error("Invalid octet when parsing Comprehension TLV :" + temp); |
michael@0 | 11080 | case 0x7f: // Tag is three byte format. |
michael@0 | 11081 | // TS 101.220 clause 7.1.1.2. |
michael@0 | 11082 | // | Byte 1 | Byte 2 | Byte 3 | |
michael@0 | 11083 | // | | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | | |
michael@0 | 11084 | // | 0x7f |CR | Tag Value | |
michael@0 | 11085 | tag = (GsmPDUHelper.readHexOctet() << 8) | GsmPDUHelper.readHexOctet(); |
michael@0 | 11086 | hlen += 2; |
michael@0 | 11087 | cr = (tag & 0x8000) !== 0; |
michael@0 | 11088 | tag &= ~0x8000; |
michael@0 | 11089 | break; |
michael@0 | 11090 | default: // Tag is single byte format. |
michael@0 | 11091 | tag = temp; |
michael@0 | 11092 | // TS 101.220 clause 7.1.1.1. |
michael@0 | 11093 | // | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | |
michael@0 | 11094 | // |CR | Tag Value | |
michael@0 | 11095 | cr = (tag & 0x80) !== 0; |
michael@0 | 11096 | tag &= ~0x80; |
michael@0 | 11097 | } |
michael@0 | 11098 | |
michael@0 | 11099 | // TS 101.220 clause 7.1.2, Length Encoding. |
michael@0 | 11100 | // Length | Byte 1 | Byte 2 | Byte 3 | Byte 4 | |
michael@0 | 11101 | // 0 - 127 | 00 - 7f | N/A | N/A | N/A | |
michael@0 | 11102 | // 128-255 | 81 | 80 - ff| N/A | N/A | |
michael@0 | 11103 | // 256-65535| 82 | 0100 - ffff | N/A | |
michael@0 | 11104 | // 65536- | 83 | 010000 - ffffff | |
michael@0 | 11105 | // 16777215 |
michael@0 | 11106 | // |
michael@0 | 11107 | // Length errors: TS 11.14, clause 6.10.6 |
michael@0 | 11108 | |
michael@0 | 11109 | let length; // Data length. |
michael@0 | 11110 | temp = GsmPDUHelper.readHexOctet(); |
michael@0 | 11111 | hlen++; |
michael@0 | 11112 | if (temp < 0x80) { |
michael@0 | 11113 | length = temp; |
michael@0 | 11114 | } else if (temp == 0x81) { |
michael@0 | 11115 | length = GsmPDUHelper.readHexOctet(); |
michael@0 | 11116 | hlen++; |
michael@0 | 11117 | if (length < 0x80) { |
michael@0 | 11118 | throw new Error("Invalid length in Comprehension TLV :" + length); |
michael@0 | 11119 | } |
michael@0 | 11120 | } else if (temp == 0x82) { |
michael@0 | 11121 | length = (GsmPDUHelper.readHexOctet() << 8) | GsmPDUHelper.readHexOctet(); |
michael@0 | 11122 | hlen += 2; |
michael@0 | 11123 | if (lenth < 0x0100) { |
michael@0 | 11124 | throw new Error("Invalid length in 3-byte Comprehension TLV :" + length); |
michael@0 | 11125 | } |
michael@0 | 11126 | } else if (temp == 0x83) { |
michael@0 | 11127 | length = (GsmPDUHelper.readHexOctet() << 16) | |
michael@0 | 11128 | (GsmPDUHelper.readHexOctet() << 8) | |
michael@0 | 11129 | GsmPDUHelper.readHexOctet(); |
michael@0 | 11130 | hlen += 3; |
michael@0 | 11131 | if (length < 0x010000) { |
michael@0 | 11132 | throw new Error("Invalid length in 4-byte Comprehension TLV :" + length); |
michael@0 | 11133 | } |
michael@0 | 11134 | } else { |
michael@0 | 11135 | throw new Error("Invalid octet in Comprehension TLV :" + temp); |
michael@0 | 11136 | } |
michael@0 | 11137 | |
michael@0 | 11138 | let ctlv = { |
michael@0 | 11139 | tag: tag, |
michael@0 | 11140 | length: length, |
michael@0 | 11141 | value: this.context.StkProactiveCmdHelper.retrieve(tag, length), |
michael@0 | 11142 | cr: cr, |
michael@0 | 11143 | hlen: hlen |
michael@0 | 11144 | }; |
michael@0 | 11145 | return ctlv; |
michael@0 | 11146 | }, |
michael@0 | 11147 | |
michael@0 | 11148 | decodeChunks: function(length) { |
michael@0 | 11149 | let chunks = []; |
michael@0 | 11150 | let index = 0; |
michael@0 | 11151 | while (index < length) { |
michael@0 | 11152 | let tlv = this.decode(); |
michael@0 | 11153 | chunks.push(tlv); |
michael@0 | 11154 | index += tlv.length; |
michael@0 | 11155 | index += tlv.hlen; |
michael@0 | 11156 | } |
michael@0 | 11157 | return chunks; |
michael@0 | 11158 | }, |
michael@0 | 11159 | |
michael@0 | 11160 | /** |
michael@0 | 11161 | * Write Location Info Comprehension TLV. |
michael@0 | 11162 | * |
michael@0 | 11163 | * @param loc location Information. |
michael@0 | 11164 | */ |
michael@0 | 11165 | writeLocationInfoTlv: function(loc) { |
michael@0 | 11166 | let GsmPDUHelper = this.context.GsmPDUHelper; |
michael@0 | 11167 | |
michael@0 | 11168 | GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_LOCATION_INFO | |
michael@0 | 11169 | COMPREHENSIONTLV_FLAG_CR); |
michael@0 | 11170 | GsmPDUHelper.writeHexOctet(loc.gsmCellId > 0xffff ? 9 : 7); |
michael@0 | 11171 | // From TS 11.14, clause 12.19 |
michael@0 | 11172 | // "The mobile country code (MCC), the mobile network code (MNC), |
michael@0 | 11173 | // the location area code (LAC) and the cell ID are |
michael@0 | 11174 | // coded as in TS 04.08." |
michael@0 | 11175 | // And from TS 04.08 and TS 24.008, |
michael@0 | 11176 | // the format is as follows: |
michael@0 | 11177 | // |
michael@0 | 11178 | // MCC = MCC_digit_1 + MCC_digit_2 + MCC_digit_3 |
michael@0 | 11179 | // |
michael@0 | 11180 | // 8 7 6 5 4 3 2 1 |
michael@0 | 11181 | // +-------------+-------------+ |
michael@0 | 11182 | // | MCC digit 2 | MCC digit 1 | octet 2 |
michael@0 | 11183 | // | MNC digit 3 | MCC digit 3 | octet 3 |
michael@0 | 11184 | // | MNC digit 2 | MNC digit 1 | octet 4 |
michael@0 | 11185 | // +-------------+-------------+ |
michael@0 | 11186 | // |
michael@0 | 11187 | // Also in TS 24.008 |
michael@0 | 11188 | // "However a network operator may decide to |
michael@0 | 11189 | // use only two digits in the MNC in the LAI over the |
michael@0 | 11190 | // radio interface. In this case, bits 5 to 8 of octet 3 |
michael@0 | 11191 | // shall be coded as '1111'". |
michael@0 | 11192 | |
michael@0 | 11193 | // MCC & MNC, 3 octets |
michael@0 | 11194 | let mcc = loc.mcc, mnc; |
michael@0 | 11195 | if (loc.mnc.length == 2) { |
michael@0 | 11196 | mnc = "F" + loc.mnc; |
michael@0 | 11197 | } else { |
michael@0 | 11198 | mnc = loc.mnc[2] + loc.mnc[0] + loc.mnc[1]; |
michael@0 | 11199 | } |
michael@0 | 11200 | GsmPDUHelper.writeSwappedNibbleBCD(mcc + mnc); |
michael@0 | 11201 | |
michael@0 | 11202 | // LAC, 2 octets |
michael@0 | 11203 | GsmPDUHelper.writeHexOctet((loc.gsmLocationAreaCode >> 8) & 0xff); |
michael@0 | 11204 | GsmPDUHelper.writeHexOctet(loc.gsmLocationAreaCode & 0xff); |
michael@0 | 11205 | |
michael@0 | 11206 | // Cell Id |
michael@0 | 11207 | if (loc.gsmCellId > 0xffff) { |
michael@0 | 11208 | // UMTS/WCDMA, gsmCellId is 28 bits. |
michael@0 | 11209 | GsmPDUHelper.writeHexOctet((loc.gsmCellId >> 24) & 0xff); |
michael@0 | 11210 | GsmPDUHelper.writeHexOctet((loc.gsmCellId >> 16) & 0xff); |
michael@0 | 11211 | GsmPDUHelper.writeHexOctet((loc.gsmCellId >> 8) & 0xff); |
michael@0 | 11212 | GsmPDUHelper.writeHexOctet(loc.gsmCellId & 0xff); |
michael@0 | 11213 | } else { |
michael@0 | 11214 | // GSM, gsmCellId is 16 bits. |
michael@0 | 11215 | GsmPDUHelper.writeHexOctet((loc.gsmCellId >> 8) & 0xff); |
michael@0 | 11216 | GsmPDUHelper.writeHexOctet(loc.gsmCellId & 0xff); |
michael@0 | 11217 | } |
michael@0 | 11218 | }, |
michael@0 | 11219 | |
michael@0 | 11220 | /** |
michael@0 | 11221 | * Given a geckoError string, this function translates it into cause value |
michael@0 | 11222 | * and write the value into buffer. |
michael@0 | 11223 | * |
michael@0 | 11224 | * @param geckoError Error string that is passed to gecko. |
michael@0 | 11225 | */ |
michael@0 | 11226 | writeCauseTlv: function(geckoError) { |
michael@0 | 11227 | let GsmPDUHelper = this.context.GsmPDUHelper; |
michael@0 | 11228 | |
michael@0 | 11229 | let cause = -1; |
michael@0 | 11230 | for (let errorNo in RIL_ERROR_TO_GECKO_ERROR) { |
michael@0 | 11231 | if (geckoError == RIL_ERROR_TO_GECKO_ERROR[errorNo]) { |
michael@0 | 11232 | cause = errorNo; |
michael@0 | 11233 | break; |
michael@0 | 11234 | } |
michael@0 | 11235 | } |
michael@0 | 11236 | cause = (cause == -1) ? ERROR_SUCCESS : cause; |
michael@0 | 11237 | |
michael@0 | 11238 | GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_CAUSE | |
michael@0 | 11239 | COMPREHENSIONTLV_FLAG_CR); |
michael@0 | 11240 | GsmPDUHelper.writeHexOctet(2); // For single cause value. |
michael@0 | 11241 | |
michael@0 | 11242 | // TS 04.08, clause 10.5.4.11: National standard code + user location. |
michael@0 | 11243 | GsmPDUHelper.writeHexOctet(0x60); |
michael@0 | 11244 | |
michael@0 | 11245 | // TS 04.08, clause 10.5.4.11: ext bit = 1 + 7 bits for cause. |
michael@0 | 11246 | // +-----------------+----------------------------------+ |
michael@0 | 11247 | // | Ext = 1 (1 bit) | Cause (7 bits) | |
michael@0 | 11248 | // +-----------------+----------------------------------+ |
michael@0 | 11249 | GsmPDUHelper.writeHexOctet(0x80 | cause); |
michael@0 | 11250 | }, |
michael@0 | 11251 | |
michael@0 | 11252 | writeDateTimeZoneTlv: function(date) { |
michael@0 | 11253 | let GsmPDUHelper = this.context.GsmPDUHelper; |
michael@0 | 11254 | |
michael@0 | 11255 | GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_DATE_TIME_ZONE); |
michael@0 | 11256 | GsmPDUHelper.writeHexOctet(7); |
michael@0 | 11257 | GsmPDUHelper.writeTimestamp(date); |
michael@0 | 11258 | }, |
michael@0 | 11259 | |
michael@0 | 11260 | writeLanguageTlv: function(language) { |
michael@0 | 11261 | let GsmPDUHelper = this.context.GsmPDUHelper; |
michael@0 | 11262 | |
michael@0 | 11263 | GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_LANGUAGE); |
michael@0 | 11264 | GsmPDUHelper.writeHexOctet(2); |
michael@0 | 11265 | |
michael@0 | 11266 | // ISO 639-1, Alpha-2 code |
michael@0 | 11267 | // TS 123.038, clause 6.2.1, GSM 7 bit Default Alphabet |
michael@0 | 11268 | GsmPDUHelper.writeHexOctet( |
michael@0 | 11269 | PDU_NL_LOCKING_SHIFT_TABLES[PDU_NL_IDENTIFIER_DEFAULT].indexOf(language[0])); |
michael@0 | 11270 | GsmPDUHelper.writeHexOctet( |
michael@0 | 11271 | PDU_NL_LOCKING_SHIFT_TABLES[PDU_NL_IDENTIFIER_DEFAULT].indexOf(language[1])); |
michael@0 | 11272 | }, |
michael@0 | 11273 | |
michael@0 | 11274 | /** |
michael@0 | 11275 | * Write Timer Value Comprehension TLV. |
michael@0 | 11276 | * |
michael@0 | 11277 | * @param seconds length of time during of the timer. |
michael@0 | 11278 | * @param cr Comprehension Required or not |
michael@0 | 11279 | */ |
michael@0 | 11280 | writeTimerValueTlv: function(seconds, cr) { |
michael@0 | 11281 | let GsmPDUHelper = this.context.GsmPDUHelper; |
michael@0 | 11282 | |
michael@0 | 11283 | GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_TIMER_VALUE | |
michael@0 | 11284 | (cr ? COMPREHENSIONTLV_FLAG_CR : 0)); |
michael@0 | 11285 | GsmPDUHelper.writeHexOctet(3); |
michael@0 | 11286 | |
michael@0 | 11287 | // TS 102.223, clause 8.38 |
michael@0 | 11288 | // +----------------+------------------+-------------------+ |
michael@0 | 11289 | // | hours (1 byte) | minutes (1 btye) | secounds (1 byte) | |
michael@0 | 11290 | // +----------------+------------------+-------------------+ |
michael@0 | 11291 | GsmPDUHelper.writeSwappedNibbleBCDNum(Math.floor(seconds / 60 / 60)); |
michael@0 | 11292 | GsmPDUHelper.writeSwappedNibbleBCDNum(Math.floor(seconds / 60) % 60); |
michael@0 | 11293 | GsmPDUHelper.writeSwappedNibbleBCDNum(seconds % 60); |
michael@0 | 11294 | }, |
michael@0 | 11295 | |
michael@0 | 11296 | getSizeOfLengthOctets: function(length) { |
michael@0 | 11297 | if (length >= 0x10000) { |
michael@0 | 11298 | return 4; // 0x83, len_1, len_2, len_3 |
michael@0 | 11299 | } else if (length >= 0x100) { |
michael@0 | 11300 | return 3; // 0x82, len_1, len_2 |
michael@0 | 11301 | } else if (length >= 0x80) { |
michael@0 | 11302 | return 2; // 0x81, len |
michael@0 | 11303 | } else { |
michael@0 | 11304 | return 1; // len |
michael@0 | 11305 | } |
michael@0 | 11306 | }, |
michael@0 | 11307 | |
michael@0 | 11308 | writeLength: function(length) { |
michael@0 | 11309 | let GsmPDUHelper = this.context.GsmPDUHelper; |
michael@0 | 11310 | |
michael@0 | 11311 | // TS 101.220 clause 7.1.2, Length Encoding. |
michael@0 | 11312 | // Length | Byte 1 | Byte 2 | Byte 3 | Byte 4 | |
michael@0 | 11313 | // 0 - 127 | 00 - 7f | N/A | N/A | N/A | |
michael@0 | 11314 | // 128-255 | 81 | 80 - ff| N/A | N/A | |
michael@0 | 11315 | // 256-65535| 82 | 0100 - ffff | N/A | |
michael@0 | 11316 | // 65536- | 83 | 010000 - ffffff | |
michael@0 | 11317 | // 16777215 |
michael@0 | 11318 | if (length < 0x80) { |
michael@0 | 11319 | GsmPDUHelper.writeHexOctet(length); |
michael@0 | 11320 | } else if (0x80 <= length && length < 0x100) { |
michael@0 | 11321 | GsmPDUHelper.writeHexOctet(0x81); |
michael@0 | 11322 | GsmPDUHelper.writeHexOctet(length); |
michael@0 | 11323 | } else if (0x100 <= length && length < 0x10000) { |
michael@0 | 11324 | GsmPDUHelper.writeHexOctet(0x82); |
michael@0 | 11325 | GsmPDUHelper.writeHexOctet((length >> 8) & 0xff); |
michael@0 | 11326 | GsmPDUHelper.writeHexOctet(length & 0xff); |
michael@0 | 11327 | } else if (0x10000 <= length && length < 0x1000000) { |
michael@0 | 11328 | GsmPDUHelper.writeHexOctet(0x83); |
michael@0 | 11329 | GsmPDUHelper.writeHexOctet((length >> 16) & 0xff); |
michael@0 | 11330 | GsmPDUHelper.writeHexOctet((length >> 8) & 0xff); |
michael@0 | 11331 | GsmPDUHelper.writeHexOctet(length & 0xff); |
michael@0 | 11332 | } else { |
michael@0 | 11333 | throw new Error("Invalid length value :" + length); |
michael@0 | 11334 | } |
michael@0 | 11335 | }, |
michael@0 | 11336 | }; |
michael@0 | 11337 | |
michael@0 | 11338 | function BerTlvHelperObject(aContext) { |
michael@0 | 11339 | this.context = aContext; |
michael@0 | 11340 | } |
michael@0 | 11341 | BerTlvHelperObject.prototype = { |
michael@0 | 11342 | context: null, |
michael@0 | 11343 | |
michael@0 | 11344 | /** |
michael@0 | 11345 | * Decode Ber TLV. |
michael@0 | 11346 | * |
michael@0 | 11347 | * @param dataLen |
michael@0 | 11348 | * The length of data in bytes. |
michael@0 | 11349 | */ |
michael@0 | 11350 | decode: function(dataLen) { |
michael@0 | 11351 | let GsmPDUHelper = this.context.GsmPDUHelper; |
michael@0 | 11352 | |
michael@0 | 11353 | let hlen = 0; |
michael@0 | 11354 | let tag = GsmPDUHelper.readHexOctet(); |
michael@0 | 11355 | hlen++; |
michael@0 | 11356 | |
michael@0 | 11357 | // The length is coded onto 1 or 2 bytes. |
michael@0 | 11358 | // Length | Byte 1 | Byte 2 |
michael@0 | 11359 | // 0 - 127 | length ('00' to '7f') | N/A |
michael@0 | 11360 | // 128 - 255 | '81' | length ('80' to 'ff') |
michael@0 | 11361 | let length; |
michael@0 | 11362 | let temp = GsmPDUHelper.readHexOctet(); |
michael@0 | 11363 | hlen++; |
michael@0 | 11364 | if (temp < 0x80) { |
michael@0 | 11365 | length = temp; |
michael@0 | 11366 | } else if (temp === 0x81) { |
michael@0 | 11367 | length = GsmPDUHelper.readHexOctet(); |
michael@0 | 11368 | hlen++; |
michael@0 | 11369 | if (length < 0x80) { |
michael@0 | 11370 | throw new Error("Invalid length " + length); |
michael@0 | 11371 | } |
michael@0 | 11372 | } else { |
michael@0 | 11373 | throw new Error("Invalid length octet " + temp); |
michael@0 | 11374 | } |
michael@0 | 11375 | |
michael@0 | 11376 | // Header + body length check. |
michael@0 | 11377 | if (dataLen - hlen !== length) { |
michael@0 | 11378 | throw new Error("Unexpected BerTlvHelper value length!!"); |
michael@0 | 11379 | } |
michael@0 | 11380 | |
michael@0 | 11381 | let method = this[tag]; |
michael@0 | 11382 | if (typeof method != "function") { |
michael@0 | 11383 | throw new Error("Unknown Ber tag 0x" + tag.toString(16)); |
michael@0 | 11384 | } |
michael@0 | 11385 | |
michael@0 | 11386 | let value = method.call(this, length); |
michael@0 | 11387 | |
michael@0 | 11388 | return { |
michael@0 | 11389 | tag: tag, |
michael@0 | 11390 | length: length, |
michael@0 | 11391 | value: value |
michael@0 | 11392 | }; |
michael@0 | 11393 | }, |
michael@0 | 11394 | |
michael@0 | 11395 | /** |
michael@0 | 11396 | * Process the value part for FCP template TLV. |
michael@0 | 11397 | * |
michael@0 | 11398 | * @param length |
michael@0 | 11399 | * The length of data in bytes. |
michael@0 | 11400 | */ |
michael@0 | 11401 | processFcpTemplate: function(length) { |
michael@0 | 11402 | let tlvs = this.decodeChunks(length); |
michael@0 | 11403 | return tlvs; |
michael@0 | 11404 | }, |
michael@0 | 11405 | |
michael@0 | 11406 | /** |
michael@0 | 11407 | * Process the value part for proactive command TLV. |
michael@0 | 11408 | * |
michael@0 | 11409 | * @param length |
michael@0 | 11410 | * The length of data in bytes. |
michael@0 | 11411 | */ |
michael@0 | 11412 | processProactiveCommand: function(length) { |
michael@0 | 11413 | let ctlvs = this.context.ComprehensionTlvHelper.decodeChunks(length); |
michael@0 | 11414 | return ctlvs; |
michael@0 | 11415 | }, |
michael@0 | 11416 | |
michael@0 | 11417 | /** |
michael@0 | 11418 | * Decode raw data to a Ber-TLV. |
michael@0 | 11419 | */ |
michael@0 | 11420 | decodeInnerTlv: function() { |
michael@0 | 11421 | let GsmPDUHelper = this.context.GsmPDUHelper; |
michael@0 | 11422 | let tag = GsmPDUHelper.readHexOctet(); |
michael@0 | 11423 | let length = GsmPDUHelper.readHexOctet(); |
michael@0 | 11424 | return { |
michael@0 | 11425 | tag: tag, |
michael@0 | 11426 | length: length, |
michael@0 | 11427 | value: this.retrieve(tag, length) |
michael@0 | 11428 | }; |
michael@0 | 11429 | }, |
michael@0 | 11430 | |
michael@0 | 11431 | decodeChunks: function(length) { |
michael@0 | 11432 | let chunks = []; |
michael@0 | 11433 | let index = 0; |
michael@0 | 11434 | while (index < length) { |
michael@0 | 11435 | let tlv = this.decodeInnerTlv(); |
michael@0 | 11436 | if (tlv.value) { |
michael@0 | 11437 | chunks.push(tlv); |
michael@0 | 11438 | } |
michael@0 | 11439 | index += tlv.length; |
michael@0 | 11440 | // tag + length fields consume 2 bytes. |
michael@0 | 11441 | index += 2; |
michael@0 | 11442 | } |
michael@0 | 11443 | return chunks; |
michael@0 | 11444 | }, |
michael@0 | 11445 | |
michael@0 | 11446 | retrieve: function(tag, length) { |
michael@0 | 11447 | let method = this[tag]; |
michael@0 | 11448 | if (typeof method != "function") { |
michael@0 | 11449 | if (DEBUG) { |
michael@0 | 11450 | this.context.debug("Unknown Ber tag : 0x" + tag.toString(16)); |
michael@0 | 11451 | } |
michael@0 | 11452 | let Buf = this.context.Buf; |
michael@0 | 11453 | Buf.seekIncoming(length * Buf.PDU_HEX_OCTET_SIZE); |
michael@0 | 11454 | return null; |
michael@0 | 11455 | } |
michael@0 | 11456 | return method.call(this, length); |
michael@0 | 11457 | }, |
michael@0 | 11458 | |
michael@0 | 11459 | /** |
michael@0 | 11460 | * File Size Data. |
michael@0 | 11461 | * |
michael@0 | 11462 | * | Byte | Description | Length | |
michael@0 | 11463 | * | 1 | Tag | 1 | |
michael@0 | 11464 | * | 2 | Length | 1 | |
michael@0 | 11465 | * | 3 to X+24 | Number of allocated data bytes in the file | X | |
michael@0 | 11466 | * | | , excluding structural information | | |
michael@0 | 11467 | */ |
michael@0 | 11468 | retrieveFileSizeData: function(length) { |
michael@0 | 11469 | let GsmPDUHelper = this.context.GsmPDUHelper; |
michael@0 | 11470 | let fileSizeData = 0; |
michael@0 | 11471 | for (let i = 0; i < length; i++) { |
michael@0 | 11472 | fileSizeData = fileSizeData << 8; |
michael@0 | 11473 | fileSizeData += GsmPDUHelper.readHexOctet(); |
michael@0 | 11474 | } |
michael@0 | 11475 | |
michael@0 | 11476 | return {fileSizeData: fileSizeData}; |
michael@0 | 11477 | }, |
michael@0 | 11478 | |
michael@0 | 11479 | /** |
michael@0 | 11480 | * File Descriptor. |
michael@0 | 11481 | * |
michael@0 | 11482 | * | Byte | Description | Length | |
michael@0 | 11483 | * | 1 | Tag | 1 | |
michael@0 | 11484 | * | 2 | Length | 1 | |
michael@0 | 11485 | * | 3 | File descriptor byte | 1 | |
michael@0 | 11486 | * | 4 | Data coding byte | 1 | |
michael@0 | 11487 | * | 5 ~ 6 | Record length | 2 | |
michael@0 | 11488 | * | 7 | Number of records | 1 | |
michael@0 | 11489 | */ |
michael@0 | 11490 | retrieveFileDescriptor: function(length) { |
michael@0 | 11491 | let GsmPDUHelper = this.context.GsmPDUHelper; |
michael@0 | 11492 | let fileDescriptorByte = GsmPDUHelper.readHexOctet(); |
michael@0 | 11493 | let dataCodingByte = GsmPDUHelper.readHexOctet(); |
michael@0 | 11494 | // See TS 102 221 Table 11.5, we only care the least 3 bits for the |
michael@0 | 11495 | // structure of file. |
michael@0 | 11496 | let fileStructure = fileDescriptorByte & 0x07; |
michael@0 | 11497 | |
michael@0 | 11498 | let fileDescriptor = { |
michael@0 | 11499 | fileStructure: fileStructure |
michael@0 | 11500 | }; |
michael@0 | 11501 | // byte 5 ~ 7 are mandatory for linear fixed and cyclic files, otherwise |
michael@0 | 11502 | // they are not applicable. |
michael@0 | 11503 | if (fileStructure === UICC_EF_STRUCTURE[EF_TYPE_LINEAR_FIXED] || |
michael@0 | 11504 | fileStructure === UICC_EF_STRUCTURE[EF_TYPE_CYCLIC]) { |
michael@0 | 11505 | fileDescriptor.recordLength = (GsmPDUHelper.readHexOctet() << 8) + |
michael@0 | 11506 | GsmPDUHelper.readHexOctet(); |
michael@0 | 11507 | fileDescriptor.numOfRecords = GsmPDUHelper.readHexOctet(); |
michael@0 | 11508 | } |
michael@0 | 11509 | |
michael@0 | 11510 | return fileDescriptor; |
michael@0 | 11511 | }, |
michael@0 | 11512 | |
michael@0 | 11513 | /** |
michael@0 | 11514 | * File identifier. |
michael@0 | 11515 | * |
michael@0 | 11516 | * | Byte | Description | Length | |
michael@0 | 11517 | * | 1 | Tag | 1 | |
michael@0 | 11518 | * | 2 | Length | 1 | |
michael@0 | 11519 | * | 3 ~ 4 | File identifier | 2 | |
michael@0 | 11520 | */ |
michael@0 | 11521 | retrieveFileIdentifier: function(length) { |
michael@0 | 11522 | let GsmPDUHelper = this.context.GsmPDUHelper; |
michael@0 | 11523 | return {fileId : (GsmPDUHelper.readHexOctet() << 8) + |
michael@0 | 11524 | GsmPDUHelper.readHexOctet()}; |
michael@0 | 11525 | }, |
michael@0 | 11526 | |
michael@0 | 11527 | searchForNextTag: function(tag, iter) { |
michael@0 | 11528 | for (let [index, tlv] in iter) { |
michael@0 | 11529 | if (tlv.tag === tag) { |
michael@0 | 11530 | return tlv; |
michael@0 | 11531 | } |
michael@0 | 11532 | } |
michael@0 | 11533 | return null; |
michael@0 | 11534 | } |
michael@0 | 11535 | }; |
michael@0 | 11536 | BerTlvHelperObject.prototype[BER_FCP_TEMPLATE_TAG] = function BER_FCP_TEMPLATE_TAG(length) { |
michael@0 | 11537 | return this.processFcpTemplate(length); |
michael@0 | 11538 | }; |
michael@0 | 11539 | BerTlvHelperObject.prototype[BER_PROACTIVE_COMMAND_TAG] = function BER_PROACTIVE_COMMAND_TAG(length) { |
michael@0 | 11540 | return this.processProactiveCommand(length); |
michael@0 | 11541 | }; |
michael@0 | 11542 | BerTlvHelperObject.prototype[BER_FCP_FILE_SIZE_DATA_TAG] = function BER_FCP_FILE_SIZE_DATA_TAG(length) { |
michael@0 | 11543 | return this.retrieveFileSizeData(length); |
michael@0 | 11544 | }; |
michael@0 | 11545 | BerTlvHelperObject.prototype[BER_FCP_FILE_DESCRIPTOR_TAG] = function BER_FCP_FILE_DESCRIPTOR_TAG(length) { |
michael@0 | 11546 | return this.retrieveFileDescriptor(length); |
michael@0 | 11547 | }; |
michael@0 | 11548 | BerTlvHelperObject.prototype[BER_FCP_FILE_IDENTIFIER_TAG] = function BER_FCP_FILE_IDENTIFIER_TAG(length) { |
michael@0 | 11549 | return this.retrieveFileIdentifier(length); |
michael@0 | 11550 | }; |
michael@0 | 11551 | |
michael@0 | 11552 | /** |
michael@0 | 11553 | * ICC Helper for getting EF path. |
michael@0 | 11554 | */ |
michael@0 | 11555 | function ICCFileHelperObject(aContext) { |
michael@0 | 11556 | this.context = aContext; |
michael@0 | 11557 | } |
michael@0 | 11558 | ICCFileHelperObject.prototype = { |
michael@0 | 11559 | context: null, |
michael@0 | 11560 | |
michael@0 | 11561 | /** |
michael@0 | 11562 | * This function handles only EFs that are common to RUIM, SIM, USIM |
michael@0 | 11563 | * and other types of ICC cards. |
michael@0 | 11564 | */ |
michael@0 | 11565 | getCommonEFPath: function(fileId) { |
michael@0 | 11566 | switch (fileId) { |
michael@0 | 11567 | case ICC_EF_ICCID: |
michael@0 | 11568 | return EF_PATH_MF_SIM; |
michael@0 | 11569 | case ICC_EF_ADN: |
michael@0 | 11570 | return EF_PATH_MF_SIM + EF_PATH_DF_TELECOM; |
michael@0 | 11571 | case ICC_EF_PBR: |
michael@0 | 11572 | return EF_PATH_MF_SIM + EF_PATH_DF_TELECOM + EF_PATH_DF_PHONEBOOK; |
michael@0 | 11573 | } |
michael@0 | 11574 | return null; |
michael@0 | 11575 | }, |
michael@0 | 11576 | |
michael@0 | 11577 | /** |
michael@0 | 11578 | * This function handles EFs for SIM. |
michael@0 | 11579 | */ |
michael@0 | 11580 | getSimEFPath: function(fileId) { |
michael@0 | 11581 | switch (fileId) { |
michael@0 | 11582 | case ICC_EF_FDN: |
michael@0 | 11583 | case ICC_EF_MSISDN: |
michael@0 | 11584 | case ICC_EF_SMS: |
michael@0 | 11585 | return EF_PATH_MF_SIM + EF_PATH_DF_TELECOM; |
michael@0 | 11586 | case ICC_EF_AD: |
michael@0 | 11587 | case ICC_EF_MBDN: |
michael@0 | 11588 | case ICC_EF_MWIS: |
michael@0 | 11589 | case ICC_EF_PLMNsel: |
michael@0 | 11590 | case ICC_EF_SPN: |
michael@0 | 11591 | case ICC_EF_SPDI: |
michael@0 | 11592 | case ICC_EF_SST: |
michael@0 | 11593 | case ICC_EF_PHASE: |
michael@0 | 11594 | case ICC_EF_CBMI: |
michael@0 | 11595 | case ICC_EF_CBMID: |
michael@0 | 11596 | case ICC_EF_CBMIR: |
michael@0 | 11597 | case ICC_EF_OPL: |
michael@0 | 11598 | case ICC_EF_PNN: |
michael@0 | 11599 | return EF_PATH_MF_SIM + EF_PATH_DF_GSM; |
michael@0 | 11600 | default: |
michael@0 | 11601 | return null; |
michael@0 | 11602 | } |
michael@0 | 11603 | }, |
michael@0 | 11604 | |
michael@0 | 11605 | /** |
michael@0 | 11606 | * This function handles EFs for USIM. |
michael@0 | 11607 | */ |
michael@0 | 11608 | getUSimEFPath: function(fileId) { |
michael@0 | 11609 | switch (fileId) { |
michael@0 | 11610 | case ICC_EF_AD: |
michael@0 | 11611 | case ICC_EF_FDN: |
michael@0 | 11612 | case ICC_EF_MBDN: |
michael@0 | 11613 | case ICC_EF_MWIS: |
michael@0 | 11614 | case ICC_EF_UST: |
michael@0 | 11615 | case ICC_EF_MSISDN: |
michael@0 | 11616 | case ICC_EF_SPN: |
michael@0 | 11617 | case ICC_EF_SPDI: |
michael@0 | 11618 | case ICC_EF_CBMI: |
michael@0 | 11619 | case ICC_EF_CBMID: |
michael@0 | 11620 | case ICC_EF_CBMIR: |
michael@0 | 11621 | case ICC_EF_OPL: |
michael@0 | 11622 | case ICC_EF_PNN: |
michael@0 | 11623 | case ICC_EF_SMS: |
michael@0 | 11624 | return EF_PATH_MF_SIM + EF_PATH_ADF_USIM; |
michael@0 | 11625 | default: |
michael@0 | 11626 | // The file ids in USIM phone book entries are decided by the |
michael@0 | 11627 | // card manufacturer. So if we don't match any of the cases |
michael@0 | 11628 | // above and if its a USIM return the phone book path. |
michael@0 | 11629 | return EF_PATH_MF_SIM + EF_PATH_DF_TELECOM + EF_PATH_DF_PHONEBOOK; |
michael@0 | 11630 | } |
michael@0 | 11631 | }, |
michael@0 | 11632 | |
michael@0 | 11633 | /** |
michael@0 | 11634 | * This function handles EFs for RUIM |
michael@0 | 11635 | */ |
michael@0 | 11636 | getRuimEFPath: function(fileId) { |
michael@0 | 11637 | switch(fileId) { |
michael@0 | 11638 | case ICC_EF_CSIM_IMSI_M: |
michael@0 | 11639 | case ICC_EF_CSIM_CDMAHOME: |
michael@0 | 11640 | case ICC_EF_CSIM_CST: |
michael@0 | 11641 | case ICC_EF_CSIM_SPN: |
michael@0 | 11642 | return EF_PATH_MF_SIM + EF_PATH_DF_CDMA; |
michael@0 | 11643 | case ICC_EF_FDN: |
michael@0 | 11644 | return EF_PATH_MF_SIM + EF_PATH_DF_TELECOM; |
michael@0 | 11645 | default: |
michael@0 | 11646 | return null; |
michael@0 | 11647 | } |
michael@0 | 11648 | }, |
michael@0 | 11649 | |
michael@0 | 11650 | /** |
michael@0 | 11651 | * Helper function for getting the pathId for the specific ICC record |
michael@0 | 11652 | * depeding on which type of ICC card we are using. |
michael@0 | 11653 | * |
michael@0 | 11654 | * @param fileId |
michael@0 | 11655 | * File id. |
michael@0 | 11656 | * @return The pathId or null in case of an error or invalid input. |
michael@0 | 11657 | */ |
michael@0 | 11658 | getEFPath: function(fileId) { |
michael@0 | 11659 | let appType = this.context.RIL.appType; |
michael@0 | 11660 | if (appType == null) { |
michael@0 | 11661 | return null; |
michael@0 | 11662 | } |
michael@0 | 11663 | |
michael@0 | 11664 | let path = this.getCommonEFPath(fileId); |
michael@0 | 11665 | if (path) { |
michael@0 | 11666 | return path; |
michael@0 | 11667 | } |
michael@0 | 11668 | |
michael@0 | 11669 | switch (appType) { |
michael@0 | 11670 | case CARD_APPTYPE_SIM: |
michael@0 | 11671 | return this.getSimEFPath(fileId); |
michael@0 | 11672 | case CARD_APPTYPE_USIM: |
michael@0 | 11673 | return this.getUSimEFPath(fileId); |
michael@0 | 11674 | case CARD_APPTYPE_RUIM: |
michael@0 | 11675 | return this.getRuimEFPath(fileId); |
michael@0 | 11676 | default: |
michael@0 | 11677 | return null; |
michael@0 | 11678 | } |
michael@0 | 11679 | } |
michael@0 | 11680 | }; |
michael@0 | 11681 | |
michael@0 | 11682 | /** |
michael@0 | 11683 | * Helper for ICC IO functionalities. |
michael@0 | 11684 | */ |
michael@0 | 11685 | function ICCIOHelperObject(aContext) { |
michael@0 | 11686 | this.context = aContext; |
michael@0 | 11687 | } |
michael@0 | 11688 | ICCIOHelperObject.prototype = { |
michael@0 | 11689 | context: null, |
michael@0 | 11690 | |
michael@0 | 11691 | /** |
michael@0 | 11692 | * Load EF with type 'Linear Fixed'. |
michael@0 | 11693 | * |
michael@0 | 11694 | * @param fileId |
michael@0 | 11695 | * The file to operate on, one of the ICC_EF_* constants. |
michael@0 | 11696 | * @param recordNumber [optional] |
michael@0 | 11697 | * The number of the record shall be loaded. |
michael@0 | 11698 | * @param recordSize [optional] |
michael@0 | 11699 | * The size of the record. |
michael@0 | 11700 | * @param callback [optional] |
michael@0 | 11701 | * The callback function shall be called when the record(s) is read. |
michael@0 | 11702 | * @param onerror [optional] |
michael@0 | 11703 | * The callback function shall be called when failure. |
michael@0 | 11704 | */ |
michael@0 | 11705 | loadLinearFixedEF: function(options) { |
michael@0 | 11706 | let cb; |
michael@0 | 11707 | let readRecord = (function(options) { |
michael@0 | 11708 | options.command = ICC_COMMAND_READ_RECORD; |
michael@0 | 11709 | options.p1 = options.recordNumber || 1; // Record number |
michael@0 | 11710 | options.p2 = READ_RECORD_ABSOLUTE_MODE; |
michael@0 | 11711 | options.p3 = options.recordSize; |
michael@0 | 11712 | options.callback = cb || options.callback; |
michael@0 | 11713 | this.context.RIL.iccIO(options); |
michael@0 | 11714 | }).bind(this); |
michael@0 | 11715 | |
michael@0 | 11716 | options.type = EF_TYPE_LINEAR_FIXED; |
michael@0 | 11717 | options.pathId = this.context.ICCFileHelper.getEFPath(options.fileId); |
michael@0 | 11718 | if (options.recordSize) { |
michael@0 | 11719 | readRecord(options); |
michael@0 | 11720 | return; |
michael@0 | 11721 | } |
michael@0 | 11722 | |
michael@0 | 11723 | cb = options.callback; |
michael@0 | 11724 | options.callback = readRecord; |
michael@0 | 11725 | this.getResponse(options); |
michael@0 | 11726 | }, |
michael@0 | 11727 | |
michael@0 | 11728 | /** |
michael@0 | 11729 | * Load next record from current record Id. |
michael@0 | 11730 | */ |
michael@0 | 11731 | loadNextRecord: function(options) { |
michael@0 | 11732 | options.p1++; |
michael@0 | 11733 | this.context.RIL.iccIO(options); |
michael@0 | 11734 | }, |
michael@0 | 11735 | |
michael@0 | 11736 | /** |
michael@0 | 11737 | * Update EF with type 'Linear Fixed'. |
michael@0 | 11738 | * |
michael@0 | 11739 | * @param fileId |
michael@0 | 11740 | * The file to operate on, one of the ICC_EF_* constants. |
michael@0 | 11741 | * @param recordNumber |
michael@0 | 11742 | * The number of the record shall be updated. |
michael@0 | 11743 | * @param dataWriter [optional] |
michael@0 | 11744 | * The function for writing string parameter for the ICC_COMMAND_UPDATE_RECORD. |
michael@0 | 11745 | * @param pin2 [optional] |
michael@0 | 11746 | * PIN2 is required when updating ICC_EF_FDN. |
michael@0 | 11747 | * @param callback [optional] |
michael@0 | 11748 | * The callback function shall be called when the record is updated. |
michael@0 | 11749 | * @param onerror [optional] |
michael@0 | 11750 | * The callback function shall be called when failure. |
michael@0 | 11751 | */ |
michael@0 | 11752 | updateLinearFixedEF: function(options) { |
michael@0 | 11753 | if (!options.fileId || !options.recordNumber) { |
michael@0 | 11754 | throw new Error("Unexpected fileId " + options.fileId + |
michael@0 | 11755 | " or recordNumber " + options.recordNumber); |
michael@0 | 11756 | } |
michael@0 | 11757 | |
michael@0 | 11758 | options.type = EF_TYPE_LINEAR_FIXED; |
michael@0 | 11759 | options.pathId = this.context.ICCFileHelper.getEFPath(options.fileId); |
michael@0 | 11760 | let cb = options.callback; |
michael@0 | 11761 | options.callback = function callback(options) { |
michael@0 | 11762 | options.callback = cb; |
michael@0 | 11763 | options.command = ICC_COMMAND_UPDATE_RECORD; |
michael@0 | 11764 | options.p1 = options.recordNumber; |
michael@0 | 11765 | options.p2 = READ_RECORD_ABSOLUTE_MODE; |
michael@0 | 11766 | options.p3 = options.recordSize; |
michael@0 | 11767 | this.context.RIL.iccIO(options); |
michael@0 | 11768 | }.bind(this); |
michael@0 | 11769 | this.getResponse(options); |
michael@0 | 11770 | }, |
michael@0 | 11771 | |
michael@0 | 11772 | /** |
michael@0 | 11773 | * Load EF with type 'Transparent'. |
michael@0 | 11774 | * |
michael@0 | 11775 | * @param fileId |
michael@0 | 11776 | * The file to operate on, one of the ICC_EF_* constants. |
michael@0 | 11777 | * @param callback [optional] |
michael@0 | 11778 | * The callback function shall be called when the record(s) is read. |
michael@0 | 11779 | * @param onerror [optional] |
michael@0 | 11780 | * The callback function shall be called when failure. |
michael@0 | 11781 | */ |
michael@0 | 11782 | loadTransparentEF: function(options) { |
michael@0 | 11783 | options.type = EF_TYPE_TRANSPARENT; |
michael@0 | 11784 | let cb = options.callback; |
michael@0 | 11785 | options.callback = function callback(options) { |
michael@0 | 11786 | options.callback = cb; |
michael@0 | 11787 | options.command = ICC_COMMAND_READ_BINARY; |
michael@0 | 11788 | options.p3 = options.fileSize; |
michael@0 | 11789 | this.context.RIL.iccIO(options); |
michael@0 | 11790 | }.bind(this); |
michael@0 | 11791 | this.getResponse(options); |
michael@0 | 11792 | }, |
michael@0 | 11793 | |
michael@0 | 11794 | /** |
michael@0 | 11795 | * Use ICC_COMMAND_GET_RESPONSE to query the EF. |
michael@0 | 11796 | * |
michael@0 | 11797 | * @param fileId |
michael@0 | 11798 | * The file to operate on, one of the ICC_EF_* constants. |
michael@0 | 11799 | */ |
michael@0 | 11800 | getResponse: function(options) { |
michael@0 | 11801 | options.command = ICC_COMMAND_GET_RESPONSE; |
michael@0 | 11802 | options.pathId = options.pathId || |
michael@0 | 11803 | this.context.ICCFileHelper.getEFPath(options.fileId); |
michael@0 | 11804 | if (!options.pathId) { |
michael@0 | 11805 | throw new Error("Unknown pathId for " + options.fileId.toString(16)); |
michael@0 | 11806 | } |
michael@0 | 11807 | options.p1 = 0; // For GET_RESPONSE, p1 = 0 |
michael@0 | 11808 | options.p2 = 0; // For GET_RESPONSE, p2 = 0 |
michael@0 | 11809 | options.p3 = GET_RESPONSE_EF_SIZE_BYTES; |
michael@0 | 11810 | this.context.RIL.iccIO(options); |
michael@0 | 11811 | }, |
michael@0 | 11812 | |
michael@0 | 11813 | /** |
michael@0 | 11814 | * Process ICC I/O response. |
michael@0 | 11815 | */ |
michael@0 | 11816 | processICCIO: function(options) { |
michael@0 | 11817 | let func = this[options.command]; |
michael@0 | 11818 | func.call(this, options); |
michael@0 | 11819 | }, |
michael@0 | 11820 | |
michael@0 | 11821 | /** |
michael@0 | 11822 | * Process a ICC_COMMAND_GET_RESPONSE type command for REQUEST_SIM_IO. |
michael@0 | 11823 | */ |
michael@0 | 11824 | processICCIOGetResponse: function(options) { |
michael@0 | 11825 | let Buf = this.context.Buf; |
michael@0 | 11826 | let strLen = Buf.readInt32(); |
michael@0 | 11827 | |
michael@0 | 11828 | let peek = this.context.GsmPDUHelper.readHexOctet(); |
michael@0 | 11829 | Buf.seekIncoming(-1 * Buf.PDU_HEX_OCTET_SIZE); |
michael@0 | 11830 | if (peek === BER_FCP_TEMPLATE_TAG) { |
michael@0 | 11831 | this.processUSimGetResponse(options, strLen / 2); |
michael@0 | 11832 | } else { |
michael@0 | 11833 | this.processSimGetResponse(options); |
michael@0 | 11834 | } |
michael@0 | 11835 | Buf.readStringDelimiter(strLen); |
michael@0 | 11836 | |
michael@0 | 11837 | if (options.callback) { |
michael@0 | 11838 | options.callback(options); |
michael@0 | 11839 | } |
michael@0 | 11840 | }, |
michael@0 | 11841 | |
michael@0 | 11842 | /** |
michael@0 | 11843 | * Helper function for processing USIM get response. |
michael@0 | 11844 | */ |
michael@0 | 11845 | processUSimGetResponse: function(options, octetLen) { |
michael@0 | 11846 | let BerTlvHelper = this.context.BerTlvHelper; |
michael@0 | 11847 | |
michael@0 | 11848 | let berTlv = BerTlvHelper.decode(octetLen); |
michael@0 | 11849 | // See TS 102 221 Table 11.4 for the content order of getResponse. |
michael@0 | 11850 | let iter = Iterator(berTlv.value); |
michael@0 | 11851 | let tlv = BerTlvHelper.searchForNextTag(BER_FCP_FILE_DESCRIPTOR_TAG, |
michael@0 | 11852 | iter); |
michael@0 | 11853 | if (!tlv || (tlv.value.fileStructure !== UICC_EF_STRUCTURE[options.type])) { |
michael@0 | 11854 | throw new Error("Expected EF type " + UICC_EF_STRUCTURE[options.type] + |
michael@0 | 11855 | " but read " + tlv.value.fileStructure); |
michael@0 | 11856 | } |
michael@0 | 11857 | |
michael@0 | 11858 | if (tlv.value.fileStructure === UICC_EF_STRUCTURE[EF_TYPE_LINEAR_FIXED] || |
michael@0 | 11859 | tlv.value.fileStructure === UICC_EF_STRUCTURE[EF_TYPE_CYCLIC]) { |
michael@0 | 11860 | options.recordSize = tlv.value.recordLength; |
michael@0 | 11861 | options.totalRecords = tlv.value.numOfRecords; |
michael@0 | 11862 | } |
michael@0 | 11863 | |
michael@0 | 11864 | tlv = BerTlvHelper.searchForNextTag(BER_FCP_FILE_IDENTIFIER_TAG, iter); |
michael@0 | 11865 | if (!tlv || (tlv.value.fileId !== options.fileId)) { |
michael@0 | 11866 | throw new Error("Expected file ID " + options.fileId.toString(16) + |
michael@0 | 11867 | " but read " + fileId.toString(16)); |
michael@0 | 11868 | } |
michael@0 | 11869 | |
michael@0 | 11870 | tlv = BerTlvHelper.searchForNextTag(BER_FCP_FILE_SIZE_DATA_TAG, iter); |
michael@0 | 11871 | if (!tlv) { |
michael@0 | 11872 | throw new Error("Unexpected file size data"); |
michael@0 | 11873 | } |
michael@0 | 11874 | options.fileSize = tlv.value.fileSizeData; |
michael@0 | 11875 | }, |
michael@0 | 11876 | |
michael@0 | 11877 | /** |
michael@0 | 11878 | * Helper function for processing SIM get response. |
michael@0 | 11879 | */ |
michael@0 | 11880 | processSimGetResponse: function(options) { |
michael@0 | 11881 | let Buf = this.context.Buf; |
michael@0 | 11882 | let GsmPDUHelper = this.context.GsmPDUHelper; |
michael@0 | 11883 | |
michael@0 | 11884 | // The format is from TS 51.011, clause 9.2.1 |
michael@0 | 11885 | |
michael@0 | 11886 | // Skip RFU, data[0] data[1]. |
michael@0 | 11887 | Buf.seekIncoming(2 * Buf.PDU_HEX_OCTET_SIZE); |
michael@0 | 11888 | |
michael@0 | 11889 | // File size, data[2], data[3] |
michael@0 | 11890 | options.fileSize = (GsmPDUHelper.readHexOctet() << 8) | |
michael@0 | 11891 | GsmPDUHelper.readHexOctet(); |
michael@0 | 11892 | |
michael@0 | 11893 | // 2 bytes File id. data[4], data[5] |
michael@0 | 11894 | let fileId = (GsmPDUHelper.readHexOctet() << 8) | |
michael@0 | 11895 | GsmPDUHelper.readHexOctet(); |
michael@0 | 11896 | if (fileId != options.fileId) { |
michael@0 | 11897 | throw new Error("Expected file ID " + options.fileId.toString(16) + |
michael@0 | 11898 | " but read " + fileId.toString(16)); |
michael@0 | 11899 | } |
michael@0 | 11900 | |
michael@0 | 11901 | // Type of file, data[6] |
michael@0 | 11902 | let fileType = GsmPDUHelper.readHexOctet(); |
michael@0 | 11903 | if (fileType != TYPE_EF) { |
michael@0 | 11904 | throw new Error("Unexpected file type " + fileType); |
michael@0 | 11905 | } |
michael@0 | 11906 | |
michael@0 | 11907 | // Skip 1 byte RFU, data[7], |
michael@0 | 11908 | // 3 bytes Access conditions, data[8] data[9] data[10], |
michael@0 | 11909 | // 1 byte File status, data[11], |
michael@0 | 11910 | // 1 byte Length of the following data, data[12]. |
michael@0 | 11911 | Buf.seekIncoming(((RESPONSE_DATA_STRUCTURE - RESPONSE_DATA_FILE_TYPE - 1) * |
michael@0 | 11912 | Buf.PDU_HEX_OCTET_SIZE)); |
michael@0 | 11913 | |
michael@0 | 11914 | // Read Structure of EF, data[13] |
michael@0 | 11915 | let efType = GsmPDUHelper.readHexOctet(); |
michael@0 | 11916 | if (efType != options.type) { |
michael@0 | 11917 | throw new Error("Expected EF type " + options.type + " but read " + efType); |
michael@0 | 11918 | } |
michael@0 | 11919 | |
michael@0 | 11920 | // TODO: Bug 952025. |
michael@0 | 11921 | // Length of a record, data[14]. |
michael@0 | 11922 | // Only available for LINEAR_FIXED and CYCLIC. |
michael@0 | 11923 | if (efType == EF_TYPE_LINEAR_FIXED || efType == EF_TYPE_CYCLIC) { |
michael@0 | 11924 | options.recordSize = GsmPDUHelper.readHexOctet(); |
michael@0 | 11925 | options.totalRecords = options.fileSize / options.recordSize; |
michael@0 | 11926 | } else { |
michael@0 | 11927 | Buf.seekIncoming(1 * Buf.PDU_HEX_OCTET_SIZE); |
michael@0 | 11928 | } |
michael@0 | 11929 | }, |
michael@0 | 11930 | |
michael@0 | 11931 | /** |
michael@0 | 11932 | * Process a ICC_COMMAND_READ_RECORD type command for REQUEST_SIM_IO. |
michael@0 | 11933 | */ |
michael@0 | 11934 | processICCIOReadRecord: function(options) { |
michael@0 | 11935 | if (options.callback) { |
michael@0 | 11936 | options.callback(options); |
michael@0 | 11937 | } |
michael@0 | 11938 | }, |
michael@0 | 11939 | |
michael@0 | 11940 | /** |
michael@0 | 11941 | * Process a ICC_COMMAND_READ_BINARY type command for REQUEST_SIM_IO. |
michael@0 | 11942 | */ |
michael@0 | 11943 | processICCIOReadBinary: function(options) { |
michael@0 | 11944 | if (options.callback) { |
michael@0 | 11945 | options.callback(options); |
michael@0 | 11946 | } |
michael@0 | 11947 | }, |
michael@0 | 11948 | |
michael@0 | 11949 | /** |
michael@0 | 11950 | * Process a ICC_COMMAND_UPDATE_RECORD type command for REQUEST_SIM_IO. |
michael@0 | 11951 | */ |
michael@0 | 11952 | processICCIOUpdateRecord: function(options) { |
michael@0 | 11953 | if (options.callback) { |
michael@0 | 11954 | options.callback(options); |
michael@0 | 11955 | } |
michael@0 | 11956 | }, |
michael@0 | 11957 | |
michael@0 | 11958 | /** |
michael@0 | 11959 | * Process ICC IO error. |
michael@0 | 11960 | */ |
michael@0 | 11961 | processICCIOError: function(options) { |
michael@0 | 11962 | let requestError = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError]; |
michael@0 | 11963 | if (DEBUG) { |
michael@0 | 11964 | // See GSM11.11, TS 51.011 clause 9.4, and ISO 7816-4 for the error |
michael@0 | 11965 | // description. |
michael@0 | 11966 | let errorMsg = "ICC I/O Error code " + requestError + |
michael@0 | 11967 | " EF id = " + options.fileId.toString(16) + |
michael@0 | 11968 | " command = " + options.command.toString(16); |
michael@0 | 11969 | if (options.sw1 && options.sw2) { |
michael@0 | 11970 | errorMsg += "(" + options.sw1.toString(16) + |
michael@0 | 11971 | "/" + options.sw2.toString(16) + ")"; |
michael@0 | 11972 | } |
michael@0 | 11973 | this.context.debug(errorMsg); |
michael@0 | 11974 | } |
michael@0 | 11975 | if (options.onerror) { |
michael@0 | 11976 | options.onerror(requestError); |
michael@0 | 11977 | } |
michael@0 | 11978 | }, |
michael@0 | 11979 | }; |
michael@0 | 11980 | ICCIOHelperObject.prototype[ICC_COMMAND_SEEK] = null; |
michael@0 | 11981 | ICCIOHelperObject.prototype[ICC_COMMAND_READ_BINARY] = function ICC_COMMAND_READ_BINARY(options) { |
michael@0 | 11982 | this.processICCIOReadBinary(options); |
michael@0 | 11983 | }; |
michael@0 | 11984 | ICCIOHelperObject.prototype[ICC_COMMAND_READ_RECORD] = function ICC_COMMAND_READ_RECORD(options) { |
michael@0 | 11985 | this.processICCIOReadRecord(options); |
michael@0 | 11986 | }; |
michael@0 | 11987 | ICCIOHelperObject.prototype[ICC_COMMAND_GET_RESPONSE] = function ICC_COMMAND_GET_RESPONSE(options) { |
michael@0 | 11988 | this.processICCIOGetResponse(options); |
michael@0 | 11989 | }; |
michael@0 | 11990 | ICCIOHelperObject.prototype[ICC_COMMAND_UPDATE_BINARY] = null; |
michael@0 | 11991 | ICCIOHelperObject.prototype[ICC_COMMAND_UPDATE_RECORD] = function ICC_COMMAND_UPDATE_RECORD(options) { |
michael@0 | 11992 | this.processICCIOUpdateRecord(options); |
michael@0 | 11993 | }; |
michael@0 | 11994 | |
michael@0 | 11995 | /** |
michael@0 | 11996 | * Helper for ICC records. |
michael@0 | 11997 | */ |
michael@0 | 11998 | function ICCRecordHelperObject(aContext) { |
michael@0 | 11999 | this.context = aContext; |
michael@0 | 12000 | } |
michael@0 | 12001 | ICCRecordHelperObject.prototype = { |
michael@0 | 12002 | context: null, |
michael@0 | 12003 | |
michael@0 | 12004 | /** |
michael@0 | 12005 | * Fetch ICC records. |
michael@0 | 12006 | */ |
michael@0 | 12007 | fetchICCRecords: function() { |
michael@0 | 12008 | switch (this.context.RIL.appType) { |
michael@0 | 12009 | case CARD_APPTYPE_SIM: |
michael@0 | 12010 | case CARD_APPTYPE_USIM: |
michael@0 | 12011 | this.context.SimRecordHelper.fetchSimRecords(); |
michael@0 | 12012 | break; |
michael@0 | 12013 | case CARD_APPTYPE_RUIM: |
michael@0 | 12014 | this.context.RuimRecordHelper.fetchRuimRecords(); |
michael@0 | 12015 | break; |
michael@0 | 12016 | } |
michael@0 | 12017 | }, |
michael@0 | 12018 | |
michael@0 | 12019 | /** |
michael@0 | 12020 | * Read the ICCID. |
michael@0 | 12021 | */ |
michael@0 | 12022 | readICCID: function() { |
michael@0 | 12023 | function callback() { |
michael@0 | 12024 | let Buf = this.context.Buf; |
michael@0 | 12025 | let RIL = this.context.RIL; |
michael@0 | 12026 | |
michael@0 | 12027 | let strLen = Buf.readInt32(); |
michael@0 | 12028 | let octetLen = strLen / 2; |
michael@0 | 12029 | RIL.iccInfo.iccid = |
michael@0 | 12030 | this.context.GsmPDUHelper.readSwappedNibbleBcdString(octetLen, true); |
michael@0 | 12031 | // Consumes the remaining buffer if any. |
michael@0 | 12032 | let unReadBuffer = this.context.Buf.getReadAvailable() - |
michael@0 | 12033 | this.context.Buf.PDU_HEX_OCTET_SIZE; |
michael@0 | 12034 | if (unReadBuffer > 0) { |
michael@0 | 12035 | this.context.Buf.seekIncoming(unReadBuffer); |
michael@0 | 12036 | } |
michael@0 | 12037 | Buf.readStringDelimiter(strLen); |
michael@0 | 12038 | |
michael@0 | 12039 | if (DEBUG) this.context.debug("ICCID: " + RIL.iccInfo.iccid); |
michael@0 | 12040 | if (RIL.iccInfo.iccid) { |
michael@0 | 12041 | this.context.ICCUtilsHelper.handleICCInfoChange(); |
michael@0 | 12042 | RIL.reportStkServiceIsRunning(); |
michael@0 | 12043 | } |
michael@0 | 12044 | } |
michael@0 | 12045 | |
michael@0 | 12046 | this.context.ICCIOHelper.loadTransparentEF({ |
michael@0 | 12047 | fileId: ICC_EF_ICCID, |
michael@0 | 12048 | callback: callback.bind(this) |
michael@0 | 12049 | }); |
michael@0 | 12050 | }, |
michael@0 | 12051 | |
michael@0 | 12052 | /** |
michael@0 | 12053 | * Read ICC ADN like EF, i.e. EF_ADN, EF_FDN. |
michael@0 | 12054 | * |
michael@0 | 12055 | * @param fileId EF id of the ADN or FDN. |
michael@0 | 12056 | * @param onsuccess Callback to be called when success. |
michael@0 | 12057 | * @param onerror Callback to be called when error. |
michael@0 | 12058 | */ |
michael@0 | 12059 | readADNLike: function(fileId, onsuccess, onerror) { |
michael@0 | 12060 | let ICCIOHelper = this.context.ICCIOHelper; |
michael@0 | 12061 | |
michael@0 | 12062 | function callback(options) { |
michael@0 | 12063 | let contact = |
michael@0 | 12064 | this.context.ICCPDUHelper.readAlphaIdDiallingNumber(options.recordSize); |
michael@0 | 12065 | if (contact) { |
michael@0 | 12066 | contact.recordId = options.p1; |
michael@0 | 12067 | contacts.push(contact); |
michael@0 | 12068 | } |
michael@0 | 12069 | |
michael@0 | 12070 | if (options.p1 < options.totalRecords) { |
michael@0 | 12071 | ICCIOHelper.loadNextRecord(options); |
michael@0 | 12072 | } else { |
michael@0 | 12073 | if (DEBUG) { |
michael@0 | 12074 | for (let i = 0; i < contacts.length; i++) { |
michael@0 | 12075 | this.context.debug("contact [" + i + "] " + |
michael@0 | 12076 | JSON.stringify(contacts[i])); |
michael@0 | 12077 | } |
michael@0 | 12078 | } |
michael@0 | 12079 | if (onsuccess) { |
michael@0 | 12080 | onsuccess(contacts); |
michael@0 | 12081 | } |
michael@0 | 12082 | } |
michael@0 | 12083 | } |
michael@0 | 12084 | |
michael@0 | 12085 | let contacts = []; |
michael@0 | 12086 | ICCIOHelper.loadLinearFixedEF({fileId: fileId, |
michael@0 | 12087 | callback: callback.bind(this), |
michael@0 | 12088 | onerror: onerror}); |
michael@0 | 12089 | }, |
michael@0 | 12090 | |
michael@0 | 12091 | /** |
michael@0 | 12092 | * Update ICC ADN like EFs, like EF_ADN, EF_FDN. |
michael@0 | 12093 | * |
michael@0 | 12094 | * @param fileId EF id of the ADN or FDN. |
michael@0 | 12095 | * @param contact The contact will be updated. (Shall have recordId property) |
michael@0 | 12096 | * @param pin2 PIN2 is required when updating ICC_EF_FDN. |
michael@0 | 12097 | * @param onsuccess Callback to be called when success. |
michael@0 | 12098 | * @param onerror Callback to be called when error. |
michael@0 | 12099 | */ |
michael@0 | 12100 | updateADNLike: function(fileId, contact, pin2, onsuccess, onerror) { |
michael@0 | 12101 | function dataWriter(recordSize) { |
michael@0 | 12102 | this.context.ICCPDUHelper.writeAlphaIdDiallingNumber(recordSize, |
michael@0 | 12103 | contact.alphaId, |
michael@0 | 12104 | contact.number); |
michael@0 | 12105 | } |
michael@0 | 12106 | |
michael@0 | 12107 | function callback(options) { |
michael@0 | 12108 | if (onsuccess) { |
michael@0 | 12109 | onsuccess(); |
michael@0 | 12110 | } |
michael@0 | 12111 | } |
michael@0 | 12112 | |
michael@0 | 12113 | if (!contact || !contact.recordId) { |
michael@0 | 12114 | if (onerror) onerror(GECKO_ERROR_INVALID_PARAMETER); |
michael@0 | 12115 | return; |
michael@0 | 12116 | } |
michael@0 | 12117 | |
michael@0 | 12118 | this.context.ICCIOHelper.updateLinearFixedEF({ |
michael@0 | 12119 | fileId: fileId, |
michael@0 | 12120 | recordNumber: contact.recordId, |
michael@0 | 12121 | dataWriter: dataWriter.bind(this), |
michael@0 | 12122 | pin2: pin2, |
michael@0 | 12123 | callback: callback.bind(this), |
michael@0 | 12124 | onerror: onerror |
michael@0 | 12125 | }); |
michael@0 | 12126 | }, |
michael@0 | 12127 | |
michael@0 | 12128 | /** |
michael@0 | 12129 | * Read USIM/RUIM Phonebook. |
michael@0 | 12130 | * |
michael@0 | 12131 | * @param onsuccess Callback to be called when success. |
michael@0 | 12132 | * @param onerror Callback to be called when error. |
michael@0 | 12133 | */ |
michael@0 | 12134 | readPBR: function(onsuccess, onerror) { |
michael@0 | 12135 | let Buf = this.context.Buf; |
michael@0 | 12136 | let GsmPDUHelper = this.context.GsmPDUHelper; |
michael@0 | 12137 | let ICCIOHelper = this.context.ICCIOHelper; |
michael@0 | 12138 | let ICCUtilsHelper = this.context.ICCUtilsHelper; |
michael@0 | 12139 | let RIL = this.context.RIL; |
michael@0 | 12140 | |
michael@0 | 12141 | function callback(options) { |
michael@0 | 12142 | let strLen = Buf.readInt32(); |
michael@0 | 12143 | let octetLen = strLen / 2, readLen = 0; |
michael@0 | 12144 | |
michael@0 | 12145 | let pbrTlvs = []; |
michael@0 | 12146 | while (readLen < octetLen) { |
michael@0 | 12147 | let tag = GsmPDUHelper.readHexOctet(); |
michael@0 | 12148 | if (tag == 0xff) { |
michael@0 | 12149 | readLen++; |
michael@0 | 12150 | Buf.seekIncoming((octetLen - readLen) * Buf.PDU_HEX_OCTET_SIZE); |
michael@0 | 12151 | break; |
michael@0 | 12152 | } |
michael@0 | 12153 | |
michael@0 | 12154 | let tlvLen = GsmPDUHelper.readHexOctet(); |
michael@0 | 12155 | let tlvs = ICCUtilsHelper.decodeSimTlvs(tlvLen); |
michael@0 | 12156 | pbrTlvs.push({tag: tag, |
michael@0 | 12157 | length: tlvLen, |
michael@0 | 12158 | value: tlvs}); |
michael@0 | 12159 | |
michael@0 | 12160 | readLen += tlvLen + 2; // +2 for tag and tlvLen |
michael@0 | 12161 | } |
michael@0 | 12162 | Buf.readStringDelimiter(strLen); |
michael@0 | 12163 | |
michael@0 | 12164 | if (pbrTlvs.length > 0) { |
michael@0 | 12165 | let pbr = ICCUtilsHelper.parsePbrTlvs(pbrTlvs); |
michael@0 | 12166 | // EF_ADN is mandatory if and only if DF_PHONEBOOK is present. |
michael@0 | 12167 | if (!pbr.adn) { |
michael@0 | 12168 | if (onerror) onerror("Cannot access ADN."); |
michael@0 | 12169 | return; |
michael@0 | 12170 | } |
michael@0 | 12171 | pbrs.push(pbr); |
michael@0 | 12172 | } |
michael@0 | 12173 | |
michael@0 | 12174 | if (options.p1 < options.totalRecords) { |
michael@0 | 12175 | ICCIOHelper.loadNextRecord(options); |
michael@0 | 12176 | } else { |
michael@0 | 12177 | if (onsuccess) { |
michael@0 | 12178 | RIL.iccInfoPrivate.pbrs = pbrs; |
michael@0 | 12179 | onsuccess(pbrs); |
michael@0 | 12180 | } |
michael@0 | 12181 | } |
michael@0 | 12182 | } |
michael@0 | 12183 | |
michael@0 | 12184 | if (RIL.iccInfoPrivate.pbrs) { |
michael@0 | 12185 | onsuccess(RIL.iccInfoPrivate.pbrs); |
michael@0 | 12186 | return; |
michael@0 | 12187 | } |
michael@0 | 12188 | |
michael@0 | 12189 | let pbrs = []; |
michael@0 | 12190 | ICCIOHelper.loadLinearFixedEF({fileId : ICC_EF_PBR, |
michael@0 | 12191 | callback: callback.bind(this), |
michael@0 | 12192 | onerror: onerror}); |
michael@0 | 12193 | }, |
michael@0 | 12194 | |
michael@0 | 12195 | /** |
michael@0 | 12196 | * Cache EF_IAP record size. |
michael@0 | 12197 | */ |
michael@0 | 12198 | _iapRecordSize: null, |
michael@0 | 12199 | |
michael@0 | 12200 | /** |
michael@0 | 12201 | * Read ICC EF_IAP. (Index Administration Phonebook) |
michael@0 | 12202 | * |
michael@0 | 12203 | * @see TS 131.102, clause 4.4.2.2 |
michael@0 | 12204 | * |
michael@0 | 12205 | * @param fileId EF id of the IAP. |
michael@0 | 12206 | * @param recordNumber The number of the record shall be loaded. |
michael@0 | 12207 | * @param onsuccess Callback to be called when success. |
michael@0 | 12208 | * @param onerror Callback to be called when error. |
michael@0 | 12209 | */ |
michael@0 | 12210 | readIAP: function(fileId, recordNumber, onsuccess, onerror) { |
michael@0 | 12211 | function callback(options) { |
michael@0 | 12212 | let Buf = this.context.Buf; |
michael@0 | 12213 | let strLen = Buf.readInt32(); |
michael@0 | 12214 | let octetLen = strLen / 2; |
michael@0 | 12215 | this._iapRecordSize = options.recordSize; |
michael@0 | 12216 | |
michael@0 | 12217 | let iap = this.context.GsmPDUHelper.readHexOctetArray(octetLen); |
michael@0 | 12218 | Buf.readStringDelimiter(strLen); |
michael@0 | 12219 | |
michael@0 | 12220 | if (onsuccess) { |
michael@0 | 12221 | onsuccess(iap); |
michael@0 | 12222 | } |
michael@0 | 12223 | } |
michael@0 | 12224 | |
michael@0 | 12225 | this.context.ICCIOHelper.loadLinearFixedEF({ |
michael@0 | 12226 | fileId: fileId, |
michael@0 | 12227 | recordNumber: recordNumber, |
michael@0 | 12228 | recordSize: this._iapRecordSize, |
michael@0 | 12229 | callback: callback.bind(this), |
michael@0 | 12230 | onerror: onerror |
michael@0 | 12231 | }); |
michael@0 | 12232 | }, |
michael@0 | 12233 | |
michael@0 | 12234 | /** |
michael@0 | 12235 | * Update USIM/RUIM Phonebook EF_IAP. |
michael@0 | 12236 | * |
michael@0 | 12237 | * @see TS 131.102, clause 4.4.2.13 |
michael@0 | 12238 | * |
michael@0 | 12239 | * @param fileId EF id of the IAP. |
michael@0 | 12240 | * @param recordNumber The identifier of the record shall be updated. |
michael@0 | 12241 | * @param iap The IAP value to be written. |
michael@0 | 12242 | * @param onsuccess Callback to be called when success. |
michael@0 | 12243 | * @param onerror Callback to be called when error. |
michael@0 | 12244 | */ |
michael@0 | 12245 | updateIAP: function(fileId, recordNumber, iap, onsuccess, onerror) { |
michael@0 | 12246 | let dataWriter = function dataWriter(recordSize) { |
michael@0 | 12247 | let Buf = this.context.Buf; |
michael@0 | 12248 | let GsmPDUHelper = this.context.GsmPDUHelper; |
michael@0 | 12249 | |
michael@0 | 12250 | // Write String length |
michael@0 | 12251 | let strLen = recordSize * 2; |
michael@0 | 12252 | Buf.writeInt32(strLen); |
michael@0 | 12253 | |
michael@0 | 12254 | for (let i = 0; i < iap.length; i++) { |
michael@0 | 12255 | GsmPDUHelper.writeHexOctet(iap[i]); |
michael@0 | 12256 | } |
michael@0 | 12257 | |
michael@0 | 12258 | Buf.writeStringDelimiter(strLen); |
michael@0 | 12259 | }.bind(this); |
michael@0 | 12260 | |
michael@0 | 12261 | this.context.ICCIOHelper.updateLinearFixedEF({ |
michael@0 | 12262 | fileId: fileId, |
michael@0 | 12263 | recordNumber: recordNumber, |
michael@0 | 12264 | dataWriter: dataWriter, |
michael@0 | 12265 | callback: onsuccess, |
michael@0 | 12266 | onerror: onerror |
michael@0 | 12267 | }); |
michael@0 | 12268 | }, |
michael@0 | 12269 | |
michael@0 | 12270 | /** |
michael@0 | 12271 | * Cache EF_Email record size. |
michael@0 | 12272 | */ |
michael@0 | 12273 | _emailRecordSize: null, |
michael@0 | 12274 | |
michael@0 | 12275 | /** |
michael@0 | 12276 | * Read USIM/RUIM Phonebook EF_EMAIL. |
michael@0 | 12277 | * |
michael@0 | 12278 | * @see TS 131.102, clause 4.4.2.13 |
michael@0 | 12279 | * |
michael@0 | 12280 | * @param fileId EF id of the EMAIL. |
michael@0 | 12281 | * @param fileType The type of the EMAIL, one of the ICC_USIM_TYPE* constants. |
michael@0 | 12282 | * @param recordNumber The number of the record shall be loaded. |
michael@0 | 12283 | * @param onsuccess Callback to be called when success. |
michael@0 | 12284 | * @param onerror Callback to be called when error. |
michael@0 | 12285 | */ |
michael@0 | 12286 | readEmail: function(fileId, fileType, recordNumber, onsuccess, onerror) { |
michael@0 | 12287 | function callback(options) { |
michael@0 | 12288 | let Buf = this.context.Buf; |
michael@0 | 12289 | let ICCPDUHelper = this.context.ICCPDUHelper; |
michael@0 | 12290 | |
michael@0 | 12291 | let strLen = Buf.readInt32(); |
michael@0 | 12292 | let octetLen = strLen / 2; |
michael@0 | 12293 | let email = null; |
michael@0 | 12294 | this._emailRecordSize = options.recordSize; |
michael@0 | 12295 | |
michael@0 | 12296 | // Read contact's email |
michael@0 | 12297 | // |
michael@0 | 12298 | // | Byte | Description | Length | M/O |
michael@0 | 12299 | // | 1 ~ X | E-mail Address | X | M |
michael@0 | 12300 | // | X+1 | ADN file SFI | 1 | C |
michael@0 | 12301 | // | X+2 | ADN file Record Identifier | 1 | C |
michael@0 | 12302 | // Note: The fields marked as C above are mandatort if the file |
michael@0 | 12303 | // is not type 1 (as specified in EF_PBR) |
michael@0 | 12304 | if (fileType == ICC_USIM_TYPE1_TAG) { |
michael@0 | 12305 | email = ICCPDUHelper.read8BitUnpackedToString(octetLen); |
michael@0 | 12306 | } else { |
michael@0 | 12307 | email = ICCPDUHelper.read8BitUnpackedToString(octetLen - 2); |
michael@0 | 12308 | |
michael@0 | 12309 | // Consumes the remaining buffer |
michael@0 | 12310 | Buf.seekIncoming(2 * Buf.PDU_HEX_OCTET_SIZE); // For ADN SFI and Record Identifier |
michael@0 | 12311 | } |
michael@0 | 12312 | |
michael@0 | 12313 | Buf.readStringDelimiter(strLen); |
michael@0 | 12314 | |
michael@0 | 12315 | if (onsuccess) { |
michael@0 | 12316 | onsuccess(email); |
michael@0 | 12317 | } |
michael@0 | 12318 | } |
michael@0 | 12319 | |
michael@0 | 12320 | this.context.ICCIOHelper.loadLinearFixedEF({ |
michael@0 | 12321 | fileId: fileId, |
michael@0 | 12322 | recordNumber: recordNumber, |
michael@0 | 12323 | recordSize: this._emailRecordSize, |
michael@0 | 12324 | callback: callback.bind(this), |
michael@0 | 12325 | onerror: onerror |
michael@0 | 12326 | }); |
michael@0 | 12327 | }, |
michael@0 | 12328 | |
michael@0 | 12329 | /** |
michael@0 | 12330 | * Update USIM/RUIM Phonebook EF_EMAIL. |
michael@0 | 12331 | * |
michael@0 | 12332 | * @see TS 131.102, clause 4.4.2.13 |
michael@0 | 12333 | * |
michael@0 | 12334 | * @param pbr Phonebook Reference File. |
michael@0 | 12335 | * @param recordNumber The identifier of the record shall be updated. |
michael@0 | 12336 | * @param email The value to be written. |
michael@0 | 12337 | * @param adnRecordId The record Id of ADN, only needed if the fileType of Email is TYPE2. |
michael@0 | 12338 | * @param onsuccess Callback to be called when success. |
michael@0 | 12339 | * @param onerror Callback to be called when error. |
michael@0 | 12340 | */ |
michael@0 | 12341 | updateEmail: function(pbr, recordNumber, email, adnRecordId, onsuccess, onerror) { |
michael@0 | 12342 | let fileId = pbr[USIM_PBR_EMAIL].fileId; |
michael@0 | 12343 | let fileType = pbr[USIM_PBR_EMAIL].fileType; |
michael@0 | 12344 | let dataWriter = function dataWriter(recordSize) { |
michael@0 | 12345 | let Buf = this.context.Buf; |
michael@0 | 12346 | let GsmPDUHelper = this.context.GsmPDUHelper; |
michael@0 | 12347 | let ICCPDUHelper = this.context.ICCPDUHelper; |
michael@0 | 12348 | |
michael@0 | 12349 | // Write String length |
michael@0 | 12350 | let strLen = recordSize * 2; |
michael@0 | 12351 | Buf.writeInt32(strLen); |
michael@0 | 12352 | |
michael@0 | 12353 | if (fileType == ICC_USIM_TYPE1_TAG) { |
michael@0 | 12354 | ICCPDUHelper.writeStringTo8BitUnpacked(recordSize, email); |
michael@0 | 12355 | } else { |
michael@0 | 12356 | ICCPDUHelper.writeStringTo8BitUnpacked(recordSize - 2, email); |
michael@0 | 12357 | GsmPDUHelper.writeHexOctet(pbr.adn.sfi || 0xff); |
michael@0 | 12358 | GsmPDUHelper.writeHexOctet(adnRecordId); |
michael@0 | 12359 | } |
michael@0 | 12360 | |
michael@0 | 12361 | Buf.writeStringDelimiter(strLen); |
michael@0 | 12362 | }.bind(this); |
michael@0 | 12363 | |
michael@0 | 12364 | this.context.ICCIOHelper.updateLinearFixedEF({ |
michael@0 | 12365 | fileId: fileId, |
michael@0 | 12366 | recordNumber: recordNumber, |
michael@0 | 12367 | dataWriter: dataWriter, |
michael@0 | 12368 | callback: onsuccess, |
michael@0 | 12369 | onerror: onerror |
michael@0 | 12370 | }); |
michael@0 | 12371 | }, |
michael@0 | 12372 | |
michael@0 | 12373 | /** |
michael@0 | 12374 | * Cache EF_ANR record size. |
michael@0 | 12375 | */ |
michael@0 | 12376 | _anrRecordSize: null, |
michael@0 | 12377 | |
michael@0 | 12378 | /** |
michael@0 | 12379 | * Read USIM/RUIM Phonebook EF_ANR. |
michael@0 | 12380 | * |
michael@0 | 12381 | * @see TS 131.102, clause 4.4.2.9 |
michael@0 | 12382 | * |
michael@0 | 12383 | * @param fileId EF id of the ANR. |
michael@0 | 12384 | * @param fileType One of the ICC_USIM_TYPE* constants. |
michael@0 | 12385 | * @param recordNumber The number of the record shall be loaded. |
michael@0 | 12386 | * @param onsuccess Callback to be called when success. |
michael@0 | 12387 | * @param onerror Callback to be called when error. |
michael@0 | 12388 | */ |
michael@0 | 12389 | readANR: function(fileId, fileType, recordNumber, onsuccess, onerror) { |
michael@0 | 12390 | function callback(options) { |
michael@0 | 12391 | let Buf = this.context.Buf; |
michael@0 | 12392 | let strLen = Buf.readInt32(); |
michael@0 | 12393 | let number = null; |
michael@0 | 12394 | this._anrRecordSize = options.recordSize; |
michael@0 | 12395 | |
michael@0 | 12396 | // Skip EF_AAS Record ID. |
michael@0 | 12397 | Buf.seekIncoming(1 * Buf.PDU_HEX_OCTET_SIZE); |
michael@0 | 12398 | |
michael@0 | 12399 | number = this.context.ICCPDUHelper.readNumberWithLength(); |
michael@0 | 12400 | |
michael@0 | 12401 | // Skip 2 unused octets, CCP and EXT1. |
michael@0 | 12402 | Buf.seekIncoming(2 * Buf.PDU_HEX_OCTET_SIZE); |
michael@0 | 12403 | |
michael@0 | 12404 | // For Type 2 there are two extra octets. |
michael@0 | 12405 | if (fileType == ICC_USIM_TYPE2_TAG) { |
michael@0 | 12406 | // Skip 2 unused octets, ADN SFI and Record Identifier. |
michael@0 | 12407 | Buf.seekIncoming(2 * Buf.PDU_HEX_OCTET_SIZE); |
michael@0 | 12408 | } |
michael@0 | 12409 | |
michael@0 | 12410 | Buf.readStringDelimiter(strLen); |
michael@0 | 12411 | |
michael@0 | 12412 | if (onsuccess) { |
michael@0 | 12413 | onsuccess(number); |
michael@0 | 12414 | } |
michael@0 | 12415 | } |
michael@0 | 12416 | |
michael@0 | 12417 | this.context.ICCIOHelper.loadLinearFixedEF({ |
michael@0 | 12418 | fileId: fileId, |
michael@0 | 12419 | recordNumber: recordNumber, |
michael@0 | 12420 | recordSize: this._anrRecordSize, |
michael@0 | 12421 | callback: callback.bind(this), |
michael@0 | 12422 | onerror: onerror |
michael@0 | 12423 | }); |
michael@0 | 12424 | }, |
michael@0 | 12425 | |
michael@0 | 12426 | /** |
michael@0 | 12427 | * Update USIM/RUIM Phonebook EF_ANR. |
michael@0 | 12428 | * |
michael@0 | 12429 | * @see TS 131.102, clause 4.4.2.9 |
michael@0 | 12430 | * |
michael@0 | 12431 | * @param pbr Phonebook Reference File. |
michael@0 | 12432 | * @param recordNumber The identifier of the record shall be updated. |
michael@0 | 12433 | * @param number The value to be written. |
michael@0 | 12434 | * @param adnRecordId The record Id of ADN, only needed if the fileType of Email is TYPE2. |
michael@0 | 12435 | * @param onsuccess Callback to be called when success. |
michael@0 | 12436 | * @param onerror Callback to be called when error. |
michael@0 | 12437 | */ |
michael@0 | 12438 | updateANR: function(pbr, recordNumber, number, adnRecordId, onsuccess, onerror) { |
michael@0 | 12439 | let fileId = pbr[USIM_PBR_ANR0].fileId; |
michael@0 | 12440 | let fileType = pbr[USIM_PBR_ANR0].fileType; |
michael@0 | 12441 | let dataWriter = function dataWriter(recordSize) { |
michael@0 | 12442 | let Buf = this.context.Buf; |
michael@0 | 12443 | let GsmPDUHelper = this.context.GsmPDUHelper; |
michael@0 | 12444 | |
michael@0 | 12445 | // Write String length |
michael@0 | 12446 | let strLen = recordSize * 2; |
michael@0 | 12447 | Buf.writeInt32(strLen); |
michael@0 | 12448 | |
michael@0 | 12449 | // EF_AAS record Id. Unused for now. |
michael@0 | 12450 | GsmPDUHelper.writeHexOctet(0xff); |
michael@0 | 12451 | |
michael@0 | 12452 | this.context.ICCPDUHelper.writeNumberWithLength(number); |
michael@0 | 12453 | |
michael@0 | 12454 | // Write unused octets 0xff, CCP and EXT1. |
michael@0 | 12455 | GsmPDUHelper.writeHexOctet(0xff); |
michael@0 | 12456 | GsmPDUHelper.writeHexOctet(0xff); |
michael@0 | 12457 | |
michael@0 | 12458 | // For Type 2 there are two extra octets. |
michael@0 | 12459 | if (fileType == ICC_USIM_TYPE2_TAG) { |
michael@0 | 12460 | GsmPDUHelper.writeHexOctet(pbr.adn.sfi || 0xff); |
michael@0 | 12461 | GsmPDUHelper.writeHexOctet(adnRecordId); |
michael@0 | 12462 | } |
michael@0 | 12463 | |
michael@0 | 12464 | Buf.writeStringDelimiter(strLen); |
michael@0 | 12465 | }.bind(this); |
michael@0 | 12466 | |
michael@0 | 12467 | this.context.ICCIOHelper.updateLinearFixedEF({ |
michael@0 | 12468 | fileId: fileId, |
michael@0 | 12469 | recordNumber: recordNumber, |
michael@0 | 12470 | dataWriter: dataWriter, |
michael@0 | 12471 | callback: onsuccess, |
michael@0 | 12472 | onerror: onerror |
michael@0 | 12473 | }); |
michael@0 | 12474 | }, |
michael@0 | 12475 | |
michael@0 | 12476 | /** |
michael@0 | 12477 | * Find free record id. |
michael@0 | 12478 | * |
michael@0 | 12479 | * @param fileId EF id. |
michael@0 | 12480 | * @param onsuccess Callback to be called when success. |
michael@0 | 12481 | * @param onerror Callback to be called when error. |
michael@0 | 12482 | */ |
michael@0 | 12483 | findFreeRecordId: function(fileId, onsuccess, onerror) { |
michael@0 | 12484 | let ICCIOHelper = this.context.ICCIOHelper; |
michael@0 | 12485 | |
michael@0 | 12486 | function callback(options) { |
michael@0 | 12487 | let Buf = this.context.Buf; |
michael@0 | 12488 | let GsmPDUHelper = this.context.GsmPDUHelper; |
michael@0 | 12489 | |
michael@0 | 12490 | let strLen = Buf.readInt32(); |
michael@0 | 12491 | let octetLen = strLen / 2; |
michael@0 | 12492 | let readLen = 0; |
michael@0 | 12493 | |
michael@0 | 12494 | while (readLen < octetLen) { |
michael@0 | 12495 | let octet = GsmPDUHelper.readHexOctet(); |
michael@0 | 12496 | readLen++; |
michael@0 | 12497 | if (octet != 0xff) { |
michael@0 | 12498 | break; |
michael@0 | 12499 | } |
michael@0 | 12500 | } |
michael@0 | 12501 | |
michael@0 | 12502 | if (readLen == octetLen) { |
michael@0 | 12503 | // Find free record. |
michael@0 | 12504 | if (onsuccess) { |
michael@0 | 12505 | onsuccess(options.p1); |
michael@0 | 12506 | } |
michael@0 | 12507 | return; |
michael@0 | 12508 | } else { |
michael@0 | 12509 | Buf.seekIncoming((octetLen - readLen) * Buf.PDU_HEX_OCTET_SIZE); |
michael@0 | 12510 | } |
michael@0 | 12511 | |
michael@0 | 12512 | Buf.readStringDelimiter(strLen); |
michael@0 | 12513 | |
michael@0 | 12514 | if (options.p1 < options.totalRecords) { |
michael@0 | 12515 | ICCIOHelper.loadNextRecord(options); |
michael@0 | 12516 | } else { |
michael@0 | 12517 | // No free record found. |
michael@0 | 12518 | if (DEBUG) { |
michael@0 | 12519 | this.context.debug(CONTACT_ERR_NO_FREE_RECORD_FOUND); |
michael@0 | 12520 | } |
michael@0 | 12521 | onerror(CONTACT_ERR_NO_FREE_RECORD_FOUND); |
michael@0 | 12522 | } |
michael@0 | 12523 | } |
michael@0 | 12524 | |
michael@0 | 12525 | ICCIOHelper.loadLinearFixedEF({fileId: fileId, |
michael@0 | 12526 | callback: callback.bind(this), |
michael@0 | 12527 | onerror: onerror}); |
michael@0 | 12528 | }, |
michael@0 | 12529 | }; |
michael@0 | 12530 | |
michael@0 | 12531 | /** |
michael@0 | 12532 | * Helper for (U)SIM Records. |
michael@0 | 12533 | */ |
michael@0 | 12534 | function SimRecordHelperObject(aContext) { |
michael@0 | 12535 | this.context = aContext; |
michael@0 | 12536 | } |
michael@0 | 12537 | SimRecordHelperObject.prototype = { |
michael@0 | 12538 | context: null, |
michael@0 | 12539 | |
michael@0 | 12540 | /** |
michael@0 | 12541 | * Fetch (U)SIM records. |
michael@0 | 12542 | */ |
michael@0 | 12543 | fetchSimRecords: function() { |
michael@0 | 12544 | this.context.RIL.getIMSI(); |
michael@0 | 12545 | this.readAD(); |
michael@0 | 12546 | this.readSST(); |
michael@0 | 12547 | }, |
michael@0 | 12548 | |
michael@0 | 12549 | /** |
michael@0 | 12550 | * Read EF_phase. |
michael@0 | 12551 | * This EF is only available in SIM. |
michael@0 | 12552 | */ |
michael@0 | 12553 | readSimPhase: function() { |
michael@0 | 12554 | function callback() { |
michael@0 | 12555 | let Buf = this.context.Buf; |
michael@0 | 12556 | let strLen = Buf.readInt32(); |
michael@0 | 12557 | |
michael@0 | 12558 | let GsmPDUHelper = this.context.GsmPDUHelper; |
michael@0 | 12559 | let phase = GsmPDUHelper.readHexOctet(); |
michael@0 | 12560 | // If EF_phase is coded '03' or greater, an ME supporting STK shall |
michael@0 | 12561 | // perform the PROFILE DOWNLOAD procedure. |
michael@0 | 12562 | if (RILQUIRKS_SEND_STK_PROFILE_DOWNLOAD && |
michael@0 | 12563 | phase >= ICC_PHASE_2_PROFILE_DOWNLOAD_REQUIRED) { |
michael@0 | 12564 | this.context.RIL.sendStkTerminalProfile(STK_SUPPORTED_TERMINAL_PROFILE); |
michael@0 | 12565 | } |
michael@0 | 12566 | |
michael@0 | 12567 | Buf.readStringDelimiter(strLen); |
michael@0 | 12568 | } |
michael@0 | 12569 | |
michael@0 | 12570 | this.context.ICCIOHelper.loadTransparentEF({ |
michael@0 | 12571 | fileId: ICC_EF_PHASE, |
michael@0 | 12572 | callback: callback.bind(this) |
michael@0 | 12573 | }); |
michael@0 | 12574 | }, |
michael@0 | 12575 | |
michael@0 | 12576 | /** |
michael@0 | 12577 | * Read the MSISDN from the (U)SIM. |
michael@0 | 12578 | */ |
michael@0 | 12579 | readMSISDN: function() { |
michael@0 | 12580 | function callback(options) { |
michael@0 | 12581 | let RIL = this.context.RIL; |
michael@0 | 12582 | |
michael@0 | 12583 | let contact = |
michael@0 | 12584 | this.context.ICCPDUHelper.readAlphaIdDiallingNumber(options.recordSize); |
michael@0 | 12585 | if (!contact || |
michael@0 | 12586 | (RIL.iccInfo.msisdn !== undefined && |
michael@0 | 12587 | RIL.iccInfo.msisdn === contact.number)) { |
michael@0 | 12588 | return; |
michael@0 | 12589 | } |
michael@0 | 12590 | RIL.iccInfo.msisdn = contact.number; |
michael@0 | 12591 | if (DEBUG) this.context.debug("MSISDN: " + RIL.iccInfo.msisdn); |
michael@0 | 12592 | this.context.ICCUtilsHelper.handleICCInfoChange(); |
michael@0 | 12593 | } |
michael@0 | 12594 | |
michael@0 | 12595 | this.context.ICCIOHelper.loadLinearFixedEF({ |
michael@0 | 12596 | fileId: ICC_EF_MSISDN, |
michael@0 | 12597 | callback: callback.bind(this) |
michael@0 | 12598 | }); |
michael@0 | 12599 | }, |
michael@0 | 12600 | |
michael@0 | 12601 | /** |
michael@0 | 12602 | * Read the AD (Administrative Data) from the (U)SIM. |
michael@0 | 12603 | */ |
michael@0 | 12604 | readAD: function() { |
michael@0 | 12605 | function callback() { |
michael@0 | 12606 | let Buf = this.context.Buf; |
michael@0 | 12607 | let strLen = Buf.readInt32(); |
michael@0 | 12608 | // Each octet is encoded into two chars. |
michael@0 | 12609 | let octetLen = strLen / 2; |
michael@0 | 12610 | let ad = this.context.GsmPDUHelper.readHexOctetArray(octetLen); |
michael@0 | 12611 | Buf.readStringDelimiter(strLen); |
michael@0 | 12612 | |
michael@0 | 12613 | if (DEBUG) { |
michael@0 | 12614 | let str = ""; |
michael@0 | 12615 | for (let i = 0; i < ad.length; i++) { |
michael@0 | 12616 | str += ad[i] + ", "; |
michael@0 | 12617 | } |
michael@0 | 12618 | this.context.debug("AD: " + str); |
michael@0 | 12619 | } |
michael@0 | 12620 | |
michael@0 | 12621 | let ICCUtilsHelper = this.context.ICCUtilsHelper; |
michael@0 | 12622 | let RIL = this.context.RIL; |
michael@0 | 12623 | // The 4th byte of the response is the length of MNC. |
michael@0 | 12624 | let mccMnc = ICCUtilsHelper.parseMccMncFromImsi(RIL.iccInfoPrivate.imsi, |
michael@0 | 12625 | ad && ad[3]); |
michael@0 | 12626 | if (mccMnc) { |
michael@0 | 12627 | RIL.iccInfo.mcc = mccMnc.mcc; |
michael@0 | 12628 | RIL.iccInfo.mnc = mccMnc.mnc; |
michael@0 | 12629 | ICCUtilsHelper.handleICCInfoChange(); |
michael@0 | 12630 | } |
michael@0 | 12631 | } |
michael@0 | 12632 | |
michael@0 | 12633 | this.context.ICCIOHelper.loadTransparentEF({ |
michael@0 | 12634 | fileId: ICC_EF_AD, |
michael@0 | 12635 | callback: callback.bind(this) |
michael@0 | 12636 | }); |
michael@0 | 12637 | }, |
michael@0 | 12638 | |
michael@0 | 12639 | /** |
michael@0 | 12640 | * Read the SPN (Service Provider Name) from the (U)SIM. |
michael@0 | 12641 | */ |
michael@0 | 12642 | readSPN: function() { |
michael@0 | 12643 | function callback() { |
michael@0 | 12644 | let Buf = this.context.Buf; |
michael@0 | 12645 | let strLen = Buf.readInt32(); |
michael@0 | 12646 | // Each octet is encoded into two chars. |
michael@0 | 12647 | let octetLen = strLen / 2; |
michael@0 | 12648 | let spnDisplayCondition = this.context.GsmPDUHelper.readHexOctet(); |
michael@0 | 12649 | // Minus 1 because the first octet is used to store display condition. |
michael@0 | 12650 | let spn = this.context.ICCPDUHelper.readAlphaIdentifier(octetLen - 1); |
michael@0 | 12651 | Buf.readStringDelimiter(strLen); |
michael@0 | 12652 | |
michael@0 | 12653 | if (DEBUG) { |
michael@0 | 12654 | this.context.debug("SPN: spn = " + spn + |
michael@0 | 12655 | ", spnDisplayCondition = " + spnDisplayCondition); |
michael@0 | 12656 | } |
michael@0 | 12657 | |
michael@0 | 12658 | let RIL = this.context.RIL; |
michael@0 | 12659 | RIL.iccInfoPrivate.spnDisplayCondition = spnDisplayCondition; |
michael@0 | 12660 | RIL.iccInfo.spn = spn; |
michael@0 | 12661 | let ICCUtilsHelper = this.context.ICCUtilsHelper; |
michael@0 | 12662 | ICCUtilsHelper.updateDisplayCondition(); |
michael@0 | 12663 | ICCUtilsHelper.handleICCInfoChange(); |
michael@0 | 12664 | } |
michael@0 | 12665 | |
michael@0 | 12666 | this.context.ICCIOHelper.loadTransparentEF({ |
michael@0 | 12667 | fileId: ICC_EF_SPN, |
michael@0 | 12668 | callback: callback.bind(this) |
michael@0 | 12669 | }); |
michael@0 | 12670 | }, |
michael@0 | 12671 | |
michael@0 | 12672 | /** |
michael@0 | 12673 | * Read the (U)SIM Service Table from the (U)SIM. |
michael@0 | 12674 | */ |
michael@0 | 12675 | readSST: function() { |
michael@0 | 12676 | function callback() { |
michael@0 | 12677 | let Buf = this.context.Buf; |
michael@0 | 12678 | let RIL = this.context.RIL; |
michael@0 | 12679 | |
michael@0 | 12680 | let strLen = Buf.readInt32(); |
michael@0 | 12681 | // Each octet is encoded into two chars. |
michael@0 | 12682 | let octetLen = strLen / 2; |
michael@0 | 12683 | let sst = this.context.GsmPDUHelper.readHexOctetArray(octetLen); |
michael@0 | 12684 | Buf.readStringDelimiter(strLen); |
michael@0 | 12685 | RIL.iccInfoPrivate.sst = sst; |
michael@0 | 12686 | if (DEBUG) { |
michael@0 | 12687 | let str = ""; |
michael@0 | 12688 | for (let i = 0; i < sst.length; i++) { |
michael@0 | 12689 | str += sst[i] + ", "; |
michael@0 | 12690 | } |
michael@0 | 12691 | this.context.debug("SST: " + str); |
michael@0 | 12692 | } |
michael@0 | 12693 | |
michael@0 | 12694 | let ICCUtilsHelper = this.context.ICCUtilsHelper; |
michael@0 | 12695 | if (ICCUtilsHelper.isICCServiceAvailable("MSISDN")) { |
michael@0 | 12696 | if (DEBUG) this.context.debug("MSISDN: MSISDN is available"); |
michael@0 | 12697 | this.readMSISDN(); |
michael@0 | 12698 | } else { |
michael@0 | 12699 | if (DEBUG) this.context.debug("MSISDN: MSISDN service is not available"); |
michael@0 | 12700 | } |
michael@0 | 12701 | |
michael@0 | 12702 | // Fetch SPN and PLMN list, if some of them are available. |
michael@0 | 12703 | if (ICCUtilsHelper.isICCServiceAvailable("SPN")) { |
michael@0 | 12704 | if (DEBUG) this.context.debug("SPN: SPN is available"); |
michael@0 | 12705 | this.readSPN(); |
michael@0 | 12706 | } else { |
michael@0 | 12707 | if (DEBUG) this.context.debug("SPN: SPN service is not available"); |
michael@0 | 12708 | } |
michael@0 | 12709 | |
michael@0 | 12710 | if (ICCUtilsHelper.isICCServiceAvailable("MDN")) { |
michael@0 | 12711 | if (DEBUG) this.context.debug("MDN: MDN available."); |
michael@0 | 12712 | this.readMBDN(); |
michael@0 | 12713 | } else { |
michael@0 | 12714 | if (DEBUG) this.context.debug("MDN: MDN service is not available"); |
michael@0 | 12715 | } |
michael@0 | 12716 | |
michael@0 | 12717 | if (ICCUtilsHelper.isICCServiceAvailable("MWIS")) { |
michael@0 | 12718 | if (DEBUG) this.context.debug("MWIS: MWIS is available"); |
michael@0 | 12719 | this.readMWIS(); |
michael@0 | 12720 | } else { |
michael@0 | 12721 | if (DEBUG) this.context.debug("MWIS: MWIS is not available"); |
michael@0 | 12722 | } |
michael@0 | 12723 | |
michael@0 | 12724 | if (ICCUtilsHelper.isICCServiceAvailable("SPDI")) { |
michael@0 | 12725 | if (DEBUG) this.context.debug("SPDI: SPDI available."); |
michael@0 | 12726 | this.readSPDI(); |
michael@0 | 12727 | } else { |
michael@0 | 12728 | if (DEBUG) this.context.debug("SPDI: SPDI service is not available"); |
michael@0 | 12729 | } |
michael@0 | 12730 | |
michael@0 | 12731 | if (ICCUtilsHelper.isICCServiceAvailable("PNN")) { |
michael@0 | 12732 | if (DEBUG) this.context.debug("PNN: PNN is available"); |
michael@0 | 12733 | this.readPNN(); |
michael@0 | 12734 | } else { |
michael@0 | 12735 | if (DEBUG) this.context.debug("PNN: PNN is not available"); |
michael@0 | 12736 | } |
michael@0 | 12737 | |
michael@0 | 12738 | if (ICCUtilsHelper.isICCServiceAvailable("OPL")) { |
michael@0 | 12739 | if (DEBUG) this.context.debug("OPL: OPL is available"); |
michael@0 | 12740 | this.readOPL(); |
michael@0 | 12741 | } else { |
michael@0 | 12742 | if (DEBUG) this.context.debug("OPL: OPL is not available"); |
michael@0 | 12743 | } |
michael@0 | 12744 | |
michael@0 | 12745 | if (ICCUtilsHelper.isICCServiceAvailable("CBMI")) { |
michael@0 | 12746 | this.readCBMI(); |
michael@0 | 12747 | } else { |
michael@0 | 12748 | RIL.cellBroadcastConfigs.CBMI = null; |
michael@0 | 12749 | } |
michael@0 | 12750 | if (ICCUtilsHelper.isICCServiceAvailable("DATA_DOWNLOAD_SMS_CB")) { |
michael@0 | 12751 | this.readCBMID(); |
michael@0 | 12752 | } else { |
michael@0 | 12753 | RIL.cellBroadcastConfigs.CBMID = null; |
michael@0 | 12754 | } |
michael@0 | 12755 | if (ICCUtilsHelper.isICCServiceAvailable("CBMIR")) { |
michael@0 | 12756 | this.readCBMIR(); |
michael@0 | 12757 | } else { |
michael@0 | 12758 | RIL.cellBroadcastConfigs.CBMIR = null; |
michael@0 | 12759 | } |
michael@0 | 12760 | RIL._mergeAllCellBroadcastConfigs(); |
michael@0 | 12761 | } |
michael@0 | 12762 | |
michael@0 | 12763 | // ICC_EF_UST has the same value with ICC_EF_SST. |
michael@0 | 12764 | this.context.ICCIOHelper.loadTransparentEF({ |
michael@0 | 12765 | fileId: ICC_EF_SST, |
michael@0 | 12766 | callback: callback.bind(this) |
michael@0 | 12767 | }); |
michael@0 | 12768 | }, |
michael@0 | 12769 | |
michael@0 | 12770 | /** |
michael@0 | 12771 | * Read (U)SIM MBDN. (Mailbox Dialling Number) |
michael@0 | 12772 | * |
michael@0 | 12773 | * @see TS 131.102, clause 4.2.60 |
michael@0 | 12774 | */ |
michael@0 | 12775 | readMBDN: function() { |
michael@0 | 12776 | function callback(options) { |
michael@0 | 12777 | let RIL = this.context.RIL; |
michael@0 | 12778 | let contact = |
michael@0 | 12779 | this.context.ICCPDUHelper.readAlphaIdDiallingNumber(options.recordSize); |
michael@0 | 12780 | if (!contact || |
michael@0 | 12781 | (RIL.iccInfoPrivate.mbdn !== undefined && |
michael@0 | 12782 | RIL.iccInfoPrivate.mbdn === contact.number)) { |
michael@0 | 12783 | return; |
michael@0 | 12784 | } |
michael@0 | 12785 | RIL.iccInfoPrivate.mbdn = contact.number; |
michael@0 | 12786 | if (DEBUG) { |
michael@0 | 12787 | this.context.debug("MBDN, alphaId=" + contact.alphaId + |
michael@0 | 12788 | " number=" + contact.number); |
michael@0 | 12789 | } |
michael@0 | 12790 | contact.rilMessageType = "iccmbdn"; |
michael@0 | 12791 | RIL.sendChromeMessage(contact); |
michael@0 | 12792 | } |
michael@0 | 12793 | |
michael@0 | 12794 | this.context.ICCIOHelper.loadLinearFixedEF({ |
michael@0 | 12795 | fileId: ICC_EF_MBDN, |
michael@0 | 12796 | callback: callback.bind(this) |
michael@0 | 12797 | }); |
michael@0 | 12798 | }, |
michael@0 | 12799 | |
michael@0 | 12800 | /** |
michael@0 | 12801 | * Read ICC MWIS. (Message Waiting Indication Status) |
michael@0 | 12802 | * |
michael@0 | 12803 | * @see TS 31.102, clause 4.2.63 for USIM and TS 51.011, clause 10.3.45 for SIM. |
michael@0 | 12804 | */ |
michael@0 | 12805 | readMWIS: function() { |
michael@0 | 12806 | function callback(options) { |
michael@0 | 12807 | let Buf = this.context.Buf; |
michael@0 | 12808 | let RIL = this.context.RIL; |
michael@0 | 12809 | |
michael@0 | 12810 | let strLen = Buf.readInt32(); |
michael@0 | 12811 | // Each octet is encoded into two chars. |
michael@0 | 12812 | let octetLen = strLen / 2; |
michael@0 | 12813 | let mwis = this.context.GsmPDUHelper.readHexOctetArray(octetLen); |
michael@0 | 12814 | Buf.readStringDelimiter(strLen); |
michael@0 | 12815 | if (!mwis) { |
michael@0 | 12816 | return; |
michael@0 | 12817 | } |
michael@0 | 12818 | RIL.iccInfoPrivate.mwis = mwis; //Keep raw MWIS for updateMWIS() |
michael@0 | 12819 | |
michael@0 | 12820 | let mwi = {}; |
michael@0 | 12821 | // b8 b7 B6 b5 b4 b3 b2 b1 4.2.63, TS 31.102 version 11.6.0 |
michael@0 | 12822 | // | | | | | | | |__ Voicemail |
michael@0 | 12823 | // | | | | | | |_____ Fax |
michael@0 | 12824 | // | | | | | |________ Electronic Mail |
michael@0 | 12825 | // | | | | |___________ Other |
michael@0 | 12826 | // | | | |______________ Videomail |
michael@0 | 12827 | // |__|__|_________________ RFU |
michael@0 | 12828 | mwi.active = ((mwis[0] & 0x01) != 0); |
michael@0 | 12829 | |
michael@0 | 12830 | if (mwi.active) { |
michael@0 | 12831 | // In TS 23.040 msgCount is in the range from 0 to 255. |
michael@0 | 12832 | // The value 255 shall be taken to mean 255 or greater. |
michael@0 | 12833 | // |
michael@0 | 12834 | // However, There is no definition about 0 when MWI is active. |
michael@0 | 12835 | // |
michael@0 | 12836 | // Normally, when mwi is active, the msgCount must be larger than 0. |
michael@0 | 12837 | // Refer to other reference phone, |
michael@0 | 12838 | // 0 is usually treated as UNKNOWN for storing 2nd level MWI status (DCS). |
michael@0 | 12839 | mwi.msgCount = (mwis[1] === 0) ? GECKO_VOICEMAIL_MESSAGE_COUNT_UNKNOWN |
michael@0 | 12840 | : mwis[1]; |
michael@0 | 12841 | } else { |
michael@0 | 12842 | mwi.msgCount = 0; |
michael@0 | 12843 | } |
michael@0 | 12844 | |
michael@0 | 12845 | RIL.sendChromeMessage({ rilMessageType: "iccmwis", |
michael@0 | 12846 | mwi: mwi }); |
michael@0 | 12847 | } |
michael@0 | 12848 | |
michael@0 | 12849 | this.context.ICCIOHelper.loadLinearFixedEF({ |
michael@0 | 12850 | fileId: ICC_EF_MWIS, |
michael@0 | 12851 | recordNumber: 1, // Get 1st Subscriber Profile. |
michael@0 | 12852 | callback: callback.bind(this) |
michael@0 | 12853 | }); |
michael@0 | 12854 | }, |
michael@0 | 12855 | |
michael@0 | 12856 | /** |
michael@0 | 12857 | * Update ICC MWIS. (Message Waiting Indication Status) |
michael@0 | 12858 | * |
michael@0 | 12859 | * @see TS 31.102, clause 4.2.63 for USIM and TS 51.011, clause 10.3.45 for SIM. |
michael@0 | 12860 | */ |
michael@0 | 12861 | updateMWIS: function(mwi) { |
michael@0 | 12862 | let RIL = this.context.RIL; |
michael@0 | 12863 | if (!RIL.iccInfoPrivate.mwis) { |
michael@0 | 12864 | return; |
michael@0 | 12865 | } |
michael@0 | 12866 | |
michael@0 | 12867 | function dataWriter(recordSize) { |
michael@0 | 12868 | let mwis = RIL.iccInfoPrivate.mwis; |
michael@0 | 12869 | |
michael@0 | 12870 | let msgCount = |
michael@0 | 12871 | (mwi.msgCount === GECKO_VOICEMAIL_MESSAGE_COUNT_UNKNOWN) ? 0 : mwi.msgCount; |
michael@0 | 12872 | |
michael@0 | 12873 | [mwis[0], mwis[1]] = (mwi.active) ? [(mwis[0] | 0x01), msgCount] |
michael@0 | 12874 | : [(mwis[0] & 0xFE), 0]; |
michael@0 | 12875 | |
michael@0 | 12876 | let strLen = recordSize * 2; |
michael@0 | 12877 | let Buf = this.context.Buf; |
michael@0 | 12878 | Buf.writeInt32(strLen); |
michael@0 | 12879 | |
michael@0 | 12880 | let GsmPDUHelper = this.context.GsmPDUHelper; |
michael@0 | 12881 | for (let i = 0; i < mwis.length; i++) { |
michael@0 | 12882 | GsmPDUHelper.writeHexOctet(mwis[i]); |
michael@0 | 12883 | } |
michael@0 | 12884 | |
michael@0 | 12885 | Buf.writeStringDelimiter(strLen); |
michael@0 | 12886 | } |
michael@0 | 12887 | |
michael@0 | 12888 | this.context.ICCIOHelper.updateLinearFixedEF({ |
michael@0 | 12889 | fileId: ICC_EF_MWIS, |
michael@0 | 12890 | recordNumber: 1, // Update 1st Subscriber Profile. |
michael@0 | 12891 | dataWriter: dataWriter.bind(this) |
michael@0 | 12892 | }); |
michael@0 | 12893 | }, |
michael@0 | 12894 | |
michael@0 | 12895 | /** |
michael@0 | 12896 | * Read the SPDI (Service Provider Display Information) from the (U)SIM. |
michael@0 | 12897 | * |
michael@0 | 12898 | * See TS 131.102 section 4.2.66 for USIM and TS 51.011 section 10.3.50 |
michael@0 | 12899 | * for SIM. |
michael@0 | 12900 | */ |
michael@0 | 12901 | readSPDI: function() { |
michael@0 | 12902 | function callback() { |
michael@0 | 12903 | let Buf = this.context.Buf; |
michael@0 | 12904 | let strLen = Buf.readInt32(); |
michael@0 | 12905 | let octetLen = strLen / 2; |
michael@0 | 12906 | let readLen = 0; |
michael@0 | 12907 | let endLoop = false; |
michael@0 | 12908 | |
michael@0 | 12909 | let RIL = this.context.RIL; |
michael@0 | 12910 | RIL.iccInfoPrivate.SPDI = null; |
michael@0 | 12911 | |
michael@0 | 12912 | let GsmPDUHelper = this.context.GsmPDUHelper; |
michael@0 | 12913 | while ((readLen < octetLen) && !endLoop) { |
michael@0 | 12914 | let tlvTag = GsmPDUHelper.readHexOctet(); |
michael@0 | 12915 | let tlvLen = GsmPDUHelper.readHexOctet(); |
michael@0 | 12916 | readLen += 2; // For tag and length fields. |
michael@0 | 12917 | switch (tlvTag) { |
michael@0 | 12918 | case SPDI_TAG_SPDI: |
michael@0 | 12919 | // The value part itself is a TLV. |
michael@0 | 12920 | continue; |
michael@0 | 12921 | case SPDI_TAG_PLMN_LIST: |
michael@0 | 12922 | // This PLMN list is what we want. |
michael@0 | 12923 | RIL.iccInfoPrivate.SPDI = this.readPLMNEntries(tlvLen / 3); |
michael@0 | 12924 | readLen += tlvLen; |
michael@0 | 12925 | endLoop = true; |
michael@0 | 12926 | break; |
michael@0 | 12927 | default: |
michael@0 | 12928 | // We don't care about its content if its tag is not SPDI nor |
michael@0 | 12929 | // PLMN_LIST. |
michael@0 | 12930 | endLoop = true; |
michael@0 | 12931 | break; |
michael@0 | 12932 | } |
michael@0 | 12933 | } |
michael@0 | 12934 | |
michael@0 | 12935 | // Consume unread octets. |
michael@0 | 12936 | Buf.seekIncoming((octetLen - readLen) * Buf.PDU_HEX_OCTET_SIZE); |
michael@0 | 12937 | Buf.readStringDelimiter(strLen); |
michael@0 | 12938 | |
michael@0 | 12939 | if (DEBUG) { |
michael@0 | 12940 | this.context.debug("SPDI: " + JSON.stringify(RIL.iccInfoPrivate.SPDI)); |
michael@0 | 12941 | } |
michael@0 | 12942 | let ICCUtilsHelper = this.context.ICCUtilsHelper; |
michael@0 | 12943 | if (ICCUtilsHelper.updateDisplayCondition()) { |
michael@0 | 12944 | ICCUtilsHelper.handleICCInfoChange(); |
michael@0 | 12945 | } |
michael@0 | 12946 | } |
michael@0 | 12947 | |
michael@0 | 12948 | // PLMN List is Servive 51 in USIM, EF_SPDI |
michael@0 | 12949 | this.context.ICCIOHelper.loadTransparentEF({ |
michael@0 | 12950 | fileId: ICC_EF_SPDI, |
michael@0 | 12951 | callback: callback.bind(this) |
michael@0 | 12952 | }); |
michael@0 | 12953 | }, |
michael@0 | 12954 | |
michael@0 | 12955 | _readCbmiHelper: function(which) { |
michael@0 | 12956 | let RIL = this.context.RIL; |
michael@0 | 12957 | |
michael@0 | 12958 | function callback() { |
michael@0 | 12959 | let Buf = this.context.Buf; |
michael@0 | 12960 | let strLength = Buf.readInt32(); |
michael@0 | 12961 | |
michael@0 | 12962 | // Each Message Identifier takes two octets and each octet is encoded |
michael@0 | 12963 | // into two chars. |
michael@0 | 12964 | let numIds = strLength / 4, list = null; |
michael@0 | 12965 | if (numIds) { |
michael@0 | 12966 | list = []; |
michael@0 | 12967 | let GsmPDUHelper = this.context.GsmPDUHelper; |
michael@0 | 12968 | for (let i = 0, id; i < numIds; i++) { |
michael@0 | 12969 | id = GsmPDUHelper.readHexOctet() << 8 | GsmPDUHelper.readHexOctet(); |
michael@0 | 12970 | // `Unused entries shall be set to 'FF FF'.` |
michael@0 | 12971 | if (id != 0xFFFF) { |
michael@0 | 12972 | list.push(id); |
michael@0 | 12973 | list.push(id + 1); |
michael@0 | 12974 | } |
michael@0 | 12975 | } |
michael@0 | 12976 | } |
michael@0 | 12977 | if (DEBUG) { |
michael@0 | 12978 | this.context.debug(which + ": " + JSON.stringify(list)); |
michael@0 | 12979 | } |
michael@0 | 12980 | |
michael@0 | 12981 | Buf.readStringDelimiter(strLength); |
michael@0 | 12982 | |
michael@0 | 12983 | RIL.cellBroadcastConfigs[which] = list; |
michael@0 | 12984 | RIL._mergeAllCellBroadcastConfigs(); |
michael@0 | 12985 | } |
michael@0 | 12986 | |
michael@0 | 12987 | function onerror() { |
michael@0 | 12988 | RIL.cellBroadcastConfigs[which] = null; |
michael@0 | 12989 | RIL._mergeAllCellBroadcastConfigs(); |
michael@0 | 12990 | } |
michael@0 | 12991 | |
michael@0 | 12992 | let fileId = GLOBAL["ICC_EF_" + which]; |
michael@0 | 12993 | this.context.ICCIOHelper.loadTransparentEF({ |
michael@0 | 12994 | fileId: fileId, |
michael@0 | 12995 | callback: callback.bind(this), |
michael@0 | 12996 | onerror: onerror.bind(this) |
michael@0 | 12997 | }); |
michael@0 | 12998 | }, |
michael@0 | 12999 | |
michael@0 | 13000 | /** |
michael@0 | 13001 | * Read EFcbmi (Cell Broadcast Message Identifier selection) |
michael@0 | 13002 | * |
michael@0 | 13003 | * @see 3GPP TS 31.102 v110.02.0 section 4.2.14 EFcbmi |
michael@0 | 13004 | * @see 3GPP TS 51.011 v5.0.0 section 10.3.13 EFcbmi |
michael@0 | 13005 | */ |
michael@0 | 13006 | readCBMI: function() { |
michael@0 | 13007 | this._readCbmiHelper("CBMI"); |
michael@0 | 13008 | }, |
michael@0 | 13009 | |
michael@0 | 13010 | /** |
michael@0 | 13011 | * Read EFcbmid (Cell Broadcast Message Identifier for Data Download) |
michael@0 | 13012 | * |
michael@0 | 13013 | * @see 3GPP TS 31.102 v110.02.0 section 4.2.20 EFcbmid |
michael@0 | 13014 | * @see 3GPP TS 51.011 v5.0.0 section 10.3.26 EFcbmid |
michael@0 | 13015 | */ |
michael@0 | 13016 | readCBMID: function() { |
michael@0 | 13017 | this._readCbmiHelper("CBMID"); |
michael@0 | 13018 | }, |
michael@0 | 13019 | |
michael@0 | 13020 | /** |
michael@0 | 13021 | * Read EFcbmir (Cell Broadcast Message Identifier Range selection) |
michael@0 | 13022 | * |
michael@0 | 13023 | * @see 3GPP TS 31.102 v110.02.0 section 4.2.22 EFcbmir |
michael@0 | 13024 | * @see 3GPP TS 51.011 v5.0.0 section 10.3.28 EFcbmir |
michael@0 | 13025 | */ |
michael@0 | 13026 | readCBMIR: function() { |
michael@0 | 13027 | let RIL = this.context.RIL; |
michael@0 | 13028 | |
michael@0 | 13029 | function callback() { |
michael@0 | 13030 | let Buf = this.context.Buf; |
michael@0 | 13031 | let strLength = Buf.readInt32(); |
michael@0 | 13032 | |
michael@0 | 13033 | // Each Message Identifier range takes four octets and each octet is |
michael@0 | 13034 | // encoded into two chars. |
michael@0 | 13035 | let numIds = strLength / 8, list = null; |
michael@0 | 13036 | if (numIds) { |
michael@0 | 13037 | list = []; |
michael@0 | 13038 | let GsmPDUHelper = this.context.GsmPDUHelper; |
michael@0 | 13039 | for (let i = 0, from, to; i < numIds; i++) { |
michael@0 | 13040 | // `Bytes one and two of each range identifier equal the lower value |
michael@0 | 13041 | // of a cell broadcast range, bytes three and four equal the upper |
michael@0 | 13042 | // value of a cell broadcast range.` |
michael@0 | 13043 | from = GsmPDUHelper.readHexOctet() << 8 | GsmPDUHelper.readHexOctet(); |
michael@0 | 13044 | to = GsmPDUHelper.readHexOctet() << 8 | GsmPDUHelper.readHexOctet(); |
michael@0 | 13045 | // `Unused entries shall be set to 'FF FF'.` |
michael@0 | 13046 | if ((from != 0xFFFF) && (to != 0xFFFF)) { |
michael@0 | 13047 | list.push(from); |
michael@0 | 13048 | list.push(to + 1); |
michael@0 | 13049 | } |
michael@0 | 13050 | } |
michael@0 | 13051 | } |
michael@0 | 13052 | if (DEBUG) { |
michael@0 | 13053 | this.context.debug("CBMIR: " + JSON.stringify(list)); |
michael@0 | 13054 | } |
michael@0 | 13055 | |
michael@0 | 13056 | Buf.readStringDelimiter(strLength); |
michael@0 | 13057 | |
michael@0 | 13058 | RIL.cellBroadcastConfigs.CBMIR = list; |
michael@0 | 13059 | RIL._mergeAllCellBroadcastConfigs(); |
michael@0 | 13060 | } |
michael@0 | 13061 | |
michael@0 | 13062 | function onerror() { |
michael@0 | 13063 | RIL.cellBroadcastConfigs.CBMIR = null; |
michael@0 | 13064 | RIL._mergeAllCellBroadcastConfigs(); |
michael@0 | 13065 | } |
michael@0 | 13066 | |
michael@0 | 13067 | this.context.ICCIOHelper.loadTransparentEF({ |
michael@0 | 13068 | fileId: ICC_EF_CBMIR, |
michael@0 | 13069 | callback: callback.bind(this), |
michael@0 | 13070 | onerror: onerror.bind(this) |
michael@0 | 13071 | }); |
michael@0 | 13072 | }, |
michael@0 | 13073 | |
michael@0 | 13074 | /** |
michael@0 | 13075 | * Read OPL (Operator PLMN List) from (U)SIM. |
michael@0 | 13076 | * |
michael@0 | 13077 | * See 3GPP TS 31.102 Sec. 4.2.59 for USIM |
michael@0 | 13078 | * 3GPP TS 51.011 Sec. 10.3.42 for SIM. |
michael@0 | 13079 | */ |
michael@0 | 13080 | readOPL: function() { |
michael@0 | 13081 | let ICCIOHelper = this.context.ICCIOHelper; |
michael@0 | 13082 | let opl = []; |
michael@0 | 13083 | function callback(options) { |
michael@0 | 13084 | let Buf = this.context.Buf; |
michael@0 | 13085 | let GsmPDUHelper = this.context.GsmPDUHelper; |
michael@0 | 13086 | |
michael@0 | 13087 | let strLen = Buf.readInt32(); |
michael@0 | 13088 | // The first 7 bytes are LAI (for UMTS) and the format of LAI is defined |
michael@0 | 13089 | // in 3GPP TS 23.003, Sec 4.1 |
michael@0 | 13090 | // +-------------+---------+ |
michael@0 | 13091 | // | Octet 1 - 3 | MCC/MNC | |
michael@0 | 13092 | // +-------------+---------+ |
michael@0 | 13093 | // | Octet 4 - 7 | LAC | |
michael@0 | 13094 | // +-------------+---------+ |
michael@0 | 13095 | let mccMnc = [GsmPDUHelper.readHexOctet(), |
michael@0 | 13096 | GsmPDUHelper.readHexOctet(), |
michael@0 | 13097 | GsmPDUHelper.readHexOctet()]; |
michael@0 | 13098 | if (mccMnc[0] != 0xFF || mccMnc[1] != 0xFF || mccMnc[2] != 0xFF) { |
michael@0 | 13099 | let oplElement = {}; |
michael@0 | 13100 | let semiOctets = []; |
michael@0 | 13101 | for (let i = 0; i < mccMnc.length; i++) { |
michael@0 | 13102 | semiOctets.push((mccMnc[i] & 0xf0) >> 4); |
michael@0 | 13103 | semiOctets.push(mccMnc[i] & 0x0f); |
michael@0 | 13104 | } |
michael@0 | 13105 | let reformat = [semiOctets[1], semiOctets[0], semiOctets[3], |
michael@0 | 13106 | semiOctets[5], semiOctets[4], semiOctets[2]]; |
michael@0 | 13107 | let buf = ""; |
michael@0 | 13108 | for (let i = 0; i < reformat.length; i++) { |
michael@0 | 13109 | if (reformat[i] != 0xF) { |
michael@0 | 13110 | buf += GsmPDUHelper.semiOctetToBcdChar(reformat[i]); |
michael@0 | 13111 | } |
michael@0 | 13112 | if (i === 2) { |
michael@0 | 13113 | // 0-2: MCC |
michael@0 | 13114 | oplElement.mcc = buf; |
michael@0 | 13115 | buf = ""; |
michael@0 | 13116 | } else if (i === 5) { |
michael@0 | 13117 | // 3-5: MNC |
michael@0 | 13118 | oplElement.mnc = buf; |
michael@0 | 13119 | } |
michael@0 | 13120 | } |
michael@0 | 13121 | // LAC/TAC |
michael@0 | 13122 | oplElement.lacTacStart = |
michael@0 | 13123 | (GsmPDUHelper.readHexOctet() << 8) | GsmPDUHelper.readHexOctet(); |
michael@0 | 13124 | oplElement.lacTacEnd = |
michael@0 | 13125 | (GsmPDUHelper.readHexOctet() << 8) | GsmPDUHelper.readHexOctet(); |
michael@0 | 13126 | // PLMN Network Name Record Identifier |
michael@0 | 13127 | oplElement.pnnRecordId = GsmPDUHelper.readHexOctet(); |
michael@0 | 13128 | if (DEBUG) { |
michael@0 | 13129 | this.context.debug("OPL: [" + (opl.length + 1) + "]: " + |
michael@0 | 13130 | JSON.stringify(oplElement)); |
michael@0 | 13131 | } |
michael@0 | 13132 | opl.push(oplElement); |
michael@0 | 13133 | } else { |
michael@0 | 13134 | Buf.seekIncoming(5 * Buf.PDU_HEX_OCTET_SIZE); |
michael@0 | 13135 | } |
michael@0 | 13136 | Buf.readStringDelimiter(strLen); |
michael@0 | 13137 | |
michael@0 | 13138 | if (options.p1 < options.totalRecords) { |
michael@0 | 13139 | ICCIOHelper.loadNextRecord(options); |
michael@0 | 13140 | } else { |
michael@0 | 13141 | this.context.RIL.iccInfoPrivate.OPL = opl; |
michael@0 | 13142 | } |
michael@0 | 13143 | } |
michael@0 | 13144 | |
michael@0 | 13145 | ICCIOHelper.loadLinearFixedEF({fileId: ICC_EF_OPL, |
michael@0 | 13146 | callback: callback.bind(this)}); |
michael@0 | 13147 | }, |
michael@0 | 13148 | |
michael@0 | 13149 | /** |
michael@0 | 13150 | * Read PNN (PLMN Network Name) from (U)SIM. |
michael@0 | 13151 | * |
michael@0 | 13152 | * See 3GPP TS 31.102 Sec. 4.2.58 for USIM |
michael@0 | 13153 | * 3GPP TS 51.011 Sec. 10.3.41 for SIM. |
michael@0 | 13154 | */ |
michael@0 | 13155 | readPNN: function() { |
michael@0 | 13156 | let ICCIOHelper = this.context.ICCIOHelper; |
michael@0 | 13157 | function callback(options) { |
michael@0 | 13158 | let pnnElement; |
michael@0 | 13159 | let Buf = this.context.Buf; |
michael@0 | 13160 | let strLen = Buf.readInt32(); |
michael@0 | 13161 | let octetLen = strLen / 2; |
michael@0 | 13162 | let readLen = 0; |
michael@0 | 13163 | |
michael@0 | 13164 | let GsmPDUHelper = this.context.GsmPDUHelper; |
michael@0 | 13165 | while (readLen < octetLen) { |
michael@0 | 13166 | let tlvTag = GsmPDUHelper.readHexOctet(); |
michael@0 | 13167 | |
michael@0 | 13168 | if (tlvTag == 0xFF) { |
michael@0 | 13169 | // Unused byte |
michael@0 | 13170 | readLen++; |
michael@0 | 13171 | Buf.seekIncoming((octetLen - readLen) * Buf.PDU_HEX_OCTET_SIZE); |
michael@0 | 13172 | break; |
michael@0 | 13173 | } |
michael@0 | 13174 | |
michael@0 | 13175 | // Needs this check to avoid initializing twice. |
michael@0 | 13176 | pnnElement = pnnElement || {}; |
michael@0 | 13177 | |
michael@0 | 13178 | let tlvLen = GsmPDUHelper.readHexOctet(); |
michael@0 | 13179 | |
michael@0 | 13180 | switch (tlvTag) { |
michael@0 | 13181 | case PNN_IEI_FULL_NETWORK_NAME: |
michael@0 | 13182 | pnnElement.fullName = GsmPDUHelper.readNetworkName(tlvLen); |
michael@0 | 13183 | break; |
michael@0 | 13184 | case PNN_IEI_SHORT_NETWORK_NAME: |
michael@0 | 13185 | pnnElement.shortName = GsmPDUHelper.readNetworkName(tlvLen); |
michael@0 | 13186 | break; |
michael@0 | 13187 | default: |
michael@0 | 13188 | Buf.seekIncoming(tlvLen * Buf.PDU_HEX_OCTET_SIZE); |
michael@0 | 13189 | break; |
michael@0 | 13190 | } |
michael@0 | 13191 | |
michael@0 | 13192 | readLen += (tlvLen + 2); // +2 for tlvTag and tlvLen |
michael@0 | 13193 | } |
michael@0 | 13194 | Buf.readStringDelimiter(strLen); |
michael@0 | 13195 | |
michael@0 | 13196 | if (pnnElement) { |
michael@0 | 13197 | pnn.push(pnnElement); |
michael@0 | 13198 | } |
michael@0 | 13199 | |
michael@0 | 13200 | // Will ignore remaining records when got the contents of a record are all 0xff. |
michael@0 | 13201 | if (pnnElement && options.p1 < options.totalRecords) { |
michael@0 | 13202 | ICCIOHelper.loadNextRecord(options); |
michael@0 | 13203 | } else { |
michael@0 | 13204 | if (DEBUG) { |
michael@0 | 13205 | for (let i = 0; i < pnn.length; i++) { |
michael@0 | 13206 | this.context.debug("PNN: [" + i + "]: " + JSON.stringify(pnn[i])); |
michael@0 | 13207 | } |
michael@0 | 13208 | } |
michael@0 | 13209 | this.context.RIL.iccInfoPrivate.PNN = pnn; |
michael@0 | 13210 | } |
michael@0 | 13211 | } |
michael@0 | 13212 | |
michael@0 | 13213 | let pnn = []; |
michael@0 | 13214 | ICCIOHelper.loadLinearFixedEF({fileId: ICC_EF_PNN, |
michael@0 | 13215 | callback: callback.bind(this)}); |
michael@0 | 13216 | }, |
michael@0 | 13217 | |
michael@0 | 13218 | /** |
michael@0 | 13219 | * Read the list of PLMN (Public Land Mobile Network) entries |
michael@0 | 13220 | * We cannot directly rely on readSwappedNibbleBcdToString(), |
michael@0 | 13221 | * since it will no correctly handle some corner-cases that are |
michael@0 | 13222 | * not a problem in our case (0xFF 0xFF 0xFF). |
michael@0 | 13223 | * |
michael@0 | 13224 | * @param length The number of PLMN records. |
michael@0 | 13225 | * @return An array of string corresponding to the PLMNs. |
michael@0 | 13226 | */ |
michael@0 | 13227 | readPLMNEntries: function(length) { |
michael@0 | 13228 | let plmnList = []; |
michael@0 | 13229 | // Each PLMN entry has 3 bytes. |
michael@0 | 13230 | if (DEBUG) { |
michael@0 | 13231 | this.context.debug("PLMN entries length = " + length); |
michael@0 | 13232 | } |
michael@0 | 13233 | let GsmPDUHelper = this.context.GsmPDUHelper; |
michael@0 | 13234 | let index = 0; |
michael@0 | 13235 | while (index < length) { |
michael@0 | 13236 | // Unused entries will be 0xFFFFFF, according to EF_SPDI |
michael@0 | 13237 | // specs (TS 131 102, section 4.2.66) |
michael@0 | 13238 | try { |
michael@0 | 13239 | let plmn = [GsmPDUHelper.readHexOctet(), |
michael@0 | 13240 | GsmPDUHelper.readHexOctet(), |
michael@0 | 13241 | GsmPDUHelper.readHexOctet()]; |
michael@0 | 13242 | if (DEBUG) { |
michael@0 | 13243 | this.context.debug("Reading PLMN entry: [" + index + "]: '" + plmn + "'"); |
michael@0 | 13244 | } |
michael@0 | 13245 | if (plmn[0] != 0xFF && |
michael@0 | 13246 | plmn[1] != 0xFF && |
michael@0 | 13247 | plmn[2] != 0xFF) { |
michael@0 | 13248 | let semiOctets = []; |
michael@0 | 13249 | for (let idx = 0; idx < plmn.length; idx++) { |
michael@0 | 13250 | semiOctets.push((plmn[idx] & 0xF0) >> 4); |
michael@0 | 13251 | semiOctets.push(plmn[idx] & 0x0F); |
michael@0 | 13252 | } |
michael@0 | 13253 | |
michael@0 | 13254 | // According to TS 24.301, 9.9.3.12, the semi octets is arranged |
michael@0 | 13255 | // in format: |
michael@0 | 13256 | // Byte 1: MCC[2] | MCC[1] |
michael@0 | 13257 | // Byte 2: MNC[3] | MCC[3] |
michael@0 | 13258 | // Byte 3: MNC[2] | MNC[1] |
michael@0 | 13259 | // Therefore, we need to rearrange them. |
michael@0 | 13260 | let reformat = [semiOctets[1], semiOctets[0], semiOctets[3], |
michael@0 | 13261 | semiOctets[5], semiOctets[4], semiOctets[2]]; |
michael@0 | 13262 | let buf = ""; |
michael@0 | 13263 | let plmnEntry = {}; |
michael@0 | 13264 | for (let i = 0; i < reformat.length; i++) { |
michael@0 | 13265 | if (reformat[i] != 0xF) { |
michael@0 | 13266 | buf += GsmPDUHelper.semiOctetToBcdChar(reformat[i]); |
michael@0 | 13267 | } |
michael@0 | 13268 | if (i === 2) { |
michael@0 | 13269 | // 0-2: MCC |
michael@0 | 13270 | plmnEntry.mcc = buf; |
michael@0 | 13271 | buf = ""; |
michael@0 | 13272 | } else if (i === 5) { |
michael@0 | 13273 | // 3-5: MNC |
michael@0 | 13274 | plmnEntry.mnc = buf; |
michael@0 | 13275 | } |
michael@0 | 13276 | } |
michael@0 | 13277 | if (DEBUG) { |
michael@0 | 13278 | this.context.debug("PLMN = " + plmnEntry.mcc + ", " + plmnEntry.mnc); |
michael@0 | 13279 | } |
michael@0 | 13280 | plmnList.push(plmnEntry); |
michael@0 | 13281 | } |
michael@0 | 13282 | } catch (e) { |
michael@0 | 13283 | if (DEBUG) { |
michael@0 | 13284 | this.context.debug("PLMN entry " + index + " is invalid."); |
michael@0 | 13285 | } |
michael@0 | 13286 | break; |
michael@0 | 13287 | } |
michael@0 | 13288 | index ++; |
michael@0 | 13289 | } |
michael@0 | 13290 | return plmnList; |
michael@0 | 13291 | }, |
michael@0 | 13292 | |
michael@0 | 13293 | /** |
michael@0 | 13294 | * Read the SMS from the ICC. |
michael@0 | 13295 | * |
michael@0 | 13296 | * @param recordNumber The number of the record shall be loaded. |
michael@0 | 13297 | * @param onsuccess Callback to be called when success. |
michael@0 | 13298 | * @param onerror Callback to be called when error. |
michael@0 | 13299 | */ |
michael@0 | 13300 | readSMS: function(recordNumber, onsuccess, onerror) { |
michael@0 | 13301 | function callback(options) { |
michael@0 | 13302 | let Buf = this.context.Buf; |
michael@0 | 13303 | let strLen = Buf.readInt32(); |
michael@0 | 13304 | |
michael@0 | 13305 | // TS 51.011, 10.5.3 EF_SMS |
michael@0 | 13306 | // b3 b2 b1 |
michael@0 | 13307 | // 0 0 1 message received by MS from network; message read |
michael@0 | 13308 | // 0 1 1 message received by MS from network; message to be read |
michael@0 | 13309 | // 1 1 1 MS originating message; message to be sent |
michael@0 | 13310 | // 1 0 1 MS originating message; message sent to the network: |
michael@0 | 13311 | let GsmPDUHelper = this.context.GsmPDUHelper; |
michael@0 | 13312 | let status = GsmPDUHelper.readHexOctet(); |
michael@0 | 13313 | |
michael@0 | 13314 | let message = GsmPDUHelper.readMessage(); |
michael@0 | 13315 | message.simStatus = status; |
michael@0 | 13316 | |
michael@0 | 13317 | // Consumes the remaining buffer |
michael@0 | 13318 | Buf.seekIncoming(Buf.getReadAvailable() - Buf.PDU_HEX_OCTET_SIZE); |
michael@0 | 13319 | |
michael@0 | 13320 | Buf.readStringDelimiter(strLen); |
michael@0 | 13321 | |
michael@0 | 13322 | if (message) { |
michael@0 | 13323 | onsuccess(message); |
michael@0 | 13324 | } else { |
michael@0 | 13325 | onerror("Failed to decode SMS on SIM #" + recordNumber); |
michael@0 | 13326 | } |
michael@0 | 13327 | } |
michael@0 | 13328 | |
michael@0 | 13329 | this.context.ICCIOHelper.loadLinearFixedEF({ |
michael@0 | 13330 | fileId: ICC_EF_SMS, |
michael@0 | 13331 | recordNumber: recordNumber, |
michael@0 | 13332 | callback: callback.bind(this), |
michael@0 | 13333 | onerror: onerror |
michael@0 | 13334 | }); |
michael@0 | 13335 | }, |
michael@0 | 13336 | }; |
michael@0 | 13337 | |
michael@0 | 13338 | function RuimRecordHelperObject(aContext) { |
michael@0 | 13339 | this.context = aContext; |
michael@0 | 13340 | } |
michael@0 | 13341 | RuimRecordHelperObject.prototype = { |
michael@0 | 13342 | context: null, |
michael@0 | 13343 | |
michael@0 | 13344 | fetchRuimRecords: function() { |
michael@0 | 13345 | this.getIMSI_M(); |
michael@0 | 13346 | this.readCST(); |
michael@0 | 13347 | this.readCDMAHome(); |
michael@0 | 13348 | this.context.RIL.getCdmaSubscription(); |
michael@0 | 13349 | }, |
michael@0 | 13350 | |
michael@0 | 13351 | /** |
michael@0 | 13352 | * Get IMSI_M from CSIM/RUIM. |
michael@0 | 13353 | * See 3GPP2 C.S0065 Sec. 5.2.2 |
michael@0 | 13354 | */ |
michael@0 | 13355 | getIMSI_M: function() { |
michael@0 | 13356 | function callback() { |
michael@0 | 13357 | let Buf = this.context.Buf; |
michael@0 | 13358 | let strLen = Buf.readInt32(); |
michael@0 | 13359 | let encodedImsi = this.context.GsmPDUHelper.readHexOctetArray(strLen / 2); |
michael@0 | 13360 | Buf.readStringDelimiter(strLen); |
michael@0 | 13361 | |
michael@0 | 13362 | if ((encodedImsi[CSIM_IMSI_M_PROGRAMMED_BYTE] & 0x80)) { // IMSI_M programmed |
michael@0 | 13363 | let RIL = this.context.RIL; |
michael@0 | 13364 | RIL.iccInfoPrivate.imsi = this.decodeIMSI(encodedImsi); |
michael@0 | 13365 | RIL.sendChromeMessage({rilMessageType: "iccimsi", |
michael@0 | 13366 | imsi: RIL.iccInfoPrivate.imsi}); |
michael@0 | 13367 | |
michael@0 | 13368 | let ICCUtilsHelper = this.context.ICCUtilsHelper; |
michael@0 | 13369 | let mccMnc = ICCUtilsHelper.parseMccMncFromImsi(RIL.iccInfoPrivate.imsi); |
michael@0 | 13370 | if (mccMnc) { |
michael@0 | 13371 | RIL.iccInfo.mcc = mccMnc.mcc; |
michael@0 | 13372 | RIL.iccInfo.mnc = mccMnc.mnc; |
michael@0 | 13373 | ICCUtilsHelper.handleICCInfoChange(); |
michael@0 | 13374 | } |
michael@0 | 13375 | } |
michael@0 | 13376 | } |
michael@0 | 13377 | |
michael@0 | 13378 | this.context.ICCIOHelper.loadTransparentEF({ |
michael@0 | 13379 | fileId: ICC_EF_CSIM_IMSI_M, |
michael@0 | 13380 | callback: callback.bind(this) |
michael@0 | 13381 | }); |
michael@0 | 13382 | }, |
michael@0 | 13383 | |
michael@0 | 13384 | /** |
michael@0 | 13385 | * Decode IMSI from IMSI_M |
michael@0 | 13386 | * See 3GPP2 C.S0005 Sec. 2.3.1 |
michael@0 | 13387 | * +---+---------+------------+---+--------+---------+---+---------+--------+ |
michael@0 | 13388 | * |RFU| MCC | programmed |RFU| MNC | MIN1 |RFU| MIN2 | CLASS | |
michael@0 | 13389 | * +---+---------+------------+---+--------+---------+---+---------+--------+ |
michael@0 | 13390 | * | 6 | 10 bits | 8 bits | 1 | 7 bits | 24 bits | 6 | 10 bits | 8 bits | |
michael@0 | 13391 | * +---+---------+------------+---+--------+---------+---+---------+--------+ |
michael@0 | 13392 | */ |
michael@0 | 13393 | decodeIMSI: function(encodedImsi) { |
michael@0 | 13394 | // MCC: 10 bits, 3 digits |
michael@0 | 13395 | let encodedMCC = ((encodedImsi[CSIM_IMSI_M_MCC_BYTE + 1] & 0x03) << 8) + |
michael@0 | 13396 | (encodedImsi[CSIM_IMSI_M_MCC_BYTE] & 0xff); |
michael@0 | 13397 | let mcc = this.decodeIMSIValue(encodedMCC, 3); |
michael@0 | 13398 | |
michael@0 | 13399 | // MNC: 7 bits, 2 digits |
michael@0 | 13400 | let encodedMNC = encodedImsi[CSIM_IMSI_M_MNC_BYTE] & 0x7f; |
michael@0 | 13401 | let mnc = this.decodeIMSIValue(encodedMNC, 2); |
michael@0 | 13402 | |
michael@0 | 13403 | // MIN2: 10 bits, 3 digits |
michael@0 | 13404 | let encodedMIN2 = ((encodedImsi[CSIM_IMSI_M_MIN2_BYTE + 1] & 0x03) << 8) + |
michael@0 | 13405 | (encodedImsi[CSIM_IMSI_M_MIN2_BYTE] & 0xff); |
michael@0 | 13406 | let min2 = this.decodeIMSIValue(encodedMIN2, 3); |
michael@0 | 13407 | |
michael@0 | 13408 | // MIN1: 10+4+10 bits, 3+1+3 digits |
michael@0 | 13409 | let encodedMIN1First3 = ((encodedImsi[CSIM_IMSI_M_MIN1_BYTE + 2] & 0xff) << 2) + |
michael@0 | 13410 | ((encodedImsi[CSIM_IMSI_M_MIN1_BYTE + 1] & 0xc0) >> 6); |
michael@0 | 13411 | let min1First3 = this.decodeIMSIValue(encodedMIN1First3, 3); |
michael@0 | 13412 | |
michael@0 | 13413 | let encodedFourthDigit = (encodedImsi[CSIM_IMSI_M_MIN1_BYTE + 1] & 0x3c) >> 2; |
michael@0 | 13414 | if (encodedFourthDigit > 9) { |
michael@0 | 13415 | encodedFourthDigit = 0; |
michael@0 | 13416 | } |
michael@0 | 13417 | let fourthDigit = encodedFourthDigit.toString(); |
michael@0 | 13418 | |
michael@0 | 13419 | let encodedMIN1Last3 = ((encodedImsi[CSIM_IMSI_M_MIN1_BYTE + 1] & 0x03) << 8) + |
michael@0 | 13420 | (encodedImsi[CSIM_IMSI_M_MIN1_BYTE] & 0xff); |
michael@0 | 13421 | let min1Last3 = this.decodeIMSIValue(encodedMIN1Last3, 3); |
michael@0 | 13422 | |
michael@0 | 13423 | return mcc + mnc + min2 + min1First3 + fourthDigit + min1Last3; |
michael@0 | 13424 | }, |
michael@0 | 13425 | |
michael@0 | 13426 | /** |
michael@0 | 13427 | * Decode IMSI Helper function |
michael@0 | 13428 | * See 3GPP2 C.S0005 section 2.3.1.1 |
michael@0 | 13429 | */ |
michael@0 | 13430 | decodeIMSIValue: function(encoded, length) { |
michael@0 | 13431 | let offset = length === 3 ? 111 : 11; |
michael@0 | 13432 | let value = encoded + offset; |
michael@0 | 13433 | |
michael@0 | 13434 | for (let base = 10, temp = value, i = 0; i < length; i++) { |
michael@0 | 13435 | if (temp % 10 === 0) { |
michael@0 | 13436 | value -= base; |
michael@0 | 13437 | } |
michael@0 | 13438 | temp = Math.floor(value / base); |
michael@0 | 13439 | base = base * 10; |
michael@0 | 13440 | } |
michael@0 | 13441 | |
michael@0 | 13442 | let s = value.toString(); |
michael@0 | 13443 | while (s.length < length) { |
michael@0 | 13444 | s = "0" + s; |
michael@0 | 13445 | } |
michael@0 | 13446 | |
michael@0 | 13447 | return s; |
michael@0 | 13448 | }, |
michael@0 | 13449 | |
michael@0 | 13450 | /** |
michael@0 | 13451 | * Read CDMAHOME for CSIM. |
michael@0 | 13452 | * See 3GPP2 C.S0023 Sec. 3.4.8. |
michael@0 | 13453 | */ |
michael@0 | 13454 | readCDMAHome: function() { |
michael@0 | 13455 | let ICCIOHelper = this.context.ICCIOHelper; |
michael@0 | 13456 | |
michael@0 | 13457 | function callback(options) { |
michael@0 | 13458 | let Buf = this.context.Buf; |
michael@0 | 13459 | let GsmPDUHelper = this.context.GsmPDUHelper; |
michael@0 | 13460 | |
michael@0 | 13461 | let strLen = Buf.readInt32(); |
michael@0 | 13462 | let tempOctet = GsmPDUHelper.readHexOctet(); |
michael@0 | 13463 | cdmaHomeSystemId.push(((GsmPDUHelper.readHexOctet() & 0x7f) << 8) | tempOctet); |
michael@0 | 13464 | tempOctet = GsmPDUHelper.readHexOctet(); |
michael@0 | 13465 | cdmaHomeNetworkId.push(((GsmPDUHelper.readHexOctet() & 0xff) << 8) | tempOctet); |
michael@0 | 13466 | |
michael@0 | 13467 | // Consuming the last octet: band class. |
michael@0 | 13468 | Buf.seekIncoming(Buf.PDU_HEX_OCTET_SIZE); |
michael@0 | 13469 | |
michael@0 | 13470 | Buf.readStringDelimiter(strLen); |
michael@0 | 13471 | if (options.p1 < options.totalRecords) { |
michael@0 | 13472 | ICCIOHelper.loadNextRecord(options); |
michael@0 | 13473 | } else { |
michael@0 | 13474 | if (DEBUG) { |
michael@0 | 13475 | this.context.debug("CDMAHome system id: " + |
michael@0 | 13476 | JSON.stringify(cdmaHomeSystemId)); |
michael@0 | 13477 | this.context.debug("CDMAHome network id: " + |
michael@0 | 13478 | JSON.stringify(cdmaHomeNetworkId)); |
michael@0 | 13479 | } |
michael@0 | 13480 | this.context.RIL.cdmaHome = { |
michael@0 | 13481 | systemId: cdmaHomeSystemId, |
michael@0 | 13482 | networkId: cdmaHomeNetworkId |
michael@0 | 13483 | }; |
michael@0 | 13484 | } |
michael@0 | 13485 | } |
michael@0 | 13486 | |
michael@0 | 13487 | let cdmaHomeSystemId = [], cdmaHomeNetworkId = []; |
michael@0 | 13488 | ICCIOHelper.loadLinearFixedEF({fileId: ICC_EF_CSIM_CDMAHOME, |
michael@0 | 13489 | callback: callback.bind(this)}); |
michael@0 | 13490 | }, |
michael@0 | 13491 | |
michael@0 | 13492 | /** |
michael@0 | 13493 | * Read CDMA Service Table. |
michael@0 | 13494 | * See 3GPP2 C.S0023 Sec. 3.4.18 |
michael@0 | 13495 | */ |
michael@0 | 13496 | readCST: function() { |
michael@0 | 13497 | function callback() { |
michael@0 | 13498 | let Buf = this.context.Buf; |
michael@0 | 13499 | let RIL = this.context.RIL; |
michael@0 | 13500 | |
michael@0 | 13501 | let strLen = Buf.readInt32(); |
michael@0 | 13502 | // Each octet is encoded into two chars. |
michael@0 | 13503 | RIL.iccInfoPrivate.cst = |
michael@0 | 13504 | this.context.GsmPDUHelper.readHexOctetArray(strLen / 2); |
michael@0 | 13505 | Buf.readStringDelimiter(strLen); |
michael@0 | 13506 | |
michael@0 | 13507 | if (DEBUG) { |
michael@0 | 13508 | let str = ""; |
michael@0 | 13509 | for (let i = 0; i < RIL.iccInfoPrivate.cst.length; i++) { |
michael@0 | 13510 | str += RIL.iccInfoPrivate.cst[i] + ", "; |
michael@0 | 13511 | } |
michael@0 | 13512 | this.context.debug("CST: " + str); |
michael@0 | 13513 | } |
michael@0 | 13514 | |
michael@0 | 13515 | if (this.context.ICCUtilsHelper.isICCServiceAvailable("SPN")) { |
michael@0 | 13516 | if (DEBUG) this.context.debug("SPN: SPN is available"); |
michael@0 | 13517 | this.readSPN(); |
michael@0 | 13518 | } |
michael@0 | 13519 | } |
michael@0 | 13520 | this.context.ICCIOHelper.loadTransparentEF({ |
michael@0 | 13521 | fileId: ICC_EF_CSIM_CST, |
michael@0 | 13522 | callback: callback.bind(this) |
michael@0 | 13523 | }); |
michael@0 | 13524 | }, |
michael@0 | 13525 | |
michael@0 | 13526 | readSPN: function() { |
michael@0 | 13527 | function callback() { |
michael@0 | 13528 | let Buf = this.context.Buf; |
michael@0 | 13529 | let strLen = Buf.readInt32(); |
michael@0 | 13530 | let octetLen = strLen / 2; |
michael@0 | 13531 | |
michael@0 | 13532 | let GsmPDUHelper = this.context.GsmPDUHelper; |
michael@0 | 13533 | let displayCondition = GsmPDUHelper.readHexOctet(); |
michael@0 | 13534 | let codingScheme = GsmPDUHelper.readHexOctet(); |
michael@0 | 13535 | // Skip one octet: language indicator. |
michael@0 | 13536 | Buf.seekIncoming(Buf.PDU_HEX_OCTET_SIZE); |
michael@0 | 13537 | let readLen = 3; |
michael@0 | 13538 | |
michael@0 | 13539 | // SPN String ends up with 0xff. |
michael@0 | 13540 | let userDataBuffer = []; |
michael@0 | 13541 | |
michael@0 | 13542 | while (readLen < octetLen) { |
michael@0 | 13543 | let octet = GsmPDUHelper.readHexOctet(); |
michael@0 | 13544 | readLen++; |
michael@0 | 13545 | if (octet == 0xff) { |
michael@0 | 13546 | break; |
michael@0 | 13547 | } |
michael@0 | 13548 | userDataBuffer.push(octet); |
michael@0 | 13549 | } |
michael@0 | 13550 | |
michael@0 | 13551 | this.context.BitBufferHelper.startRead(userDataBuffer); |
michael@0 | 13552 | |
michael@0 | 13553 | let CdmaPDUHelper = this.context.CdmaPDUHelper; |
michael@0 | 13554 | let msgLen; |
michael@0 | 13555 | switch (CdmaPDUHelper.getCdmaMsgEncoding(codingScheme)) { |
michael@0 | 13556 | case PDU_DCS_MSG_CODING_7BITS_ALPHABET: |
michael@0 | 13557 | msgLen = Math.floor(userDataBuffer.length * 8 / 7); |
michael@0 | 13558 | break; |
michael@0 | 13559 | case PDU_DCS_MSG_CODING_8BITS_ALPHABET: |
michael@0 | 13560 | msgLen = userDataBuffer.length; |
michael@0 | 13561 | break; |
michael@0 | 13562 | case PDU_DCS_MSG_CODING_16BITS_ALPHABET: |
michael@0 | 13563 | msgLen = Math.floor(userDataBuffer.length / 2); |
michael@0 | 13564 | break; |
michael@0 | 13565 | } |
michael@0 | 13566 | |
michael@0 | 13567 | let RIL = this.context.RIL; |
michael@0 | 13568 | RIL.iccInfo.spn = CdmaPDUHelper.decodeCdmaPDUMsg(codingScheme, null, msgLen); |
michael@0 | 13569 | if (DEBUG) { |
michael@0 | 13570 | this.context.debug("CDMA SPN: " + RIL.iccInfo.spn + |
michael@0 | 13571 | ", Display condition: " + displayCondition); |
michael@0 | 13572 | } |
michael@0 | 13573 | RIL.iccInfoPrivate.spnDisplayCondition = displayCondition; |
michael@0 | 13574 | Buf.seekIncoming((octetLen - readLen) * Buf.PDU_HEX_OCTET_SIZE); |
michael@0 | 13575 | Buf.readStringDelimiter(strLen); |
michael@0 | 13576 | } |
michael@0 | 13577 | |
michael@0 | 13578 | this.context.ICCIOHelper.loadTransparentEF({ |
michael@0 | 13579 | fileId: ICC_EF_CSIM_SPN, |
michael@0 | 13580 | callback: callback.bind(this) |
michael@0 | 13581 | }); |
michael@0 | 13582 | } |
michael@0 | 13583 | }; |
michael@0 | 13584 | |
michael@0 | 13585 | /** |
michael@0 | 13586 | * Helper functions for ICC utilities. |
michael@0 | 13587 | */ |
michael@0 | 13588 | function ICCUtilsHelperObject(aContext) { |
michael@0 | 13589 | this.context = aContext; |
michael@0 | 13590 | } |
michael@0 | 13591 | ICCUtilsHelperObject.prototype = { |
michael@0 | 13592 | context: null, |
michael@0 | 13593 | |
michael@0 | 13594 | /** |
michael@0 | 13595 | * Get network names by using EF_OPL and EF_PNN |
michael@0 | 13596 | * |
michael@0 | 13597 | * @See 3GPP TS 31.102 sec. 4.2.58 and sec. 4.2.59 for USIM, |
michael@0 | 13598 | * 3GPP TS 51.011 sec. 10.3.41 and sec. 10.3.42 for SIM. |
michael@0 | 13599 | * |
michael@0 | 13600 | * @param mcc The mobile country code of the network. |
michael@0 | 13601 | * @param mnc The mobile network code of the network. |
michael@0 | 13602 | * @param lac The location area code of the network. |
michael@0 | 13603 | */ |
michael@0 | 13604 | getNetworkNameFromICC: function(mcc, mnc, lac) { |
michael@0 | 13605 | let RIL = this.context.RIL; |
michael@0 | 13606 | let iccInfoPriv = RIL.iccInfoPrivate; |
michael@0 | 13607 | let iccInfo = RIL.iccInfo; |
michael@0 | 13608 | let pnnEntry; |
michael@0 | 13609 | |
michael@0 | 13610 | if (!mcc || !mnc || !lac) { |
michael@0 | 13611 | return null; |
michael@0 | 13612 | } |
michael@0 | 13613 | |
michael@0 | 13614 | // We won't get network name if there is no PNN file. |
michael@0 | 13615 | if (!iccInfoPriv.PNN) { |
michael@0 | 13616 | return null; |
michael@0 | 13617 | } |
michael@0 | 13618 | |
michael@0 | 13619 | if (!iccInfoPriv.OPL) { |
michael@0 | 13620 | // When OPL is not present: |
michael@0 | 13621 | // According to 3GPP TS 31.102 Sec. 4.2.58 and 3GPP TS 51.011 Sec. 10.3.41, |
michael@0 | 13622 | // If EF_OPL is not present, the first record in this EF is used for the |
michael@0 | 13623 | // default network name when registered to the HPLMN. |
michael@0 | 13624 | // If we haven't get pnnEntry assigned, we should try to assign default |
michael@0 | 13625 | // value to it. |
michael@0 | 13626 | if (mcc == iccInfo.mcc && mnc == iccInfo.mnc) { |
michael@0 | 13627 | pnnEntry = iccInfoPriv.PNN[0]; |
michael@0 | 13628 | } |
michael@0 | 13629 | } else { |
michael@0 | 13630 | // According to 3GPP TS 31.102 Sec. 4.2.59 and 3GPP TS 51.011 Sec. 10.3.42, |
michael@0 | 13631 | // the ME shall use this EF_OPL in association with the EF_PNN in place |
michael@0 | 13632 | // of any network name stored within the ME's internal list and any network |
michael@0 | 13633 | // name received when registered to the PLMN. |
michael@0 | 13634 | let length = iccInfoPriv.OPL ? iccInfoPriv.OPL.length : 0; |
michael@0 | 13635 | for (let i = 0; i < length; i++) { |
michael@0 | 13636 | let opl = iccInfoPriv.OPL[i]; |
michael@0 | 13637 | // Try to match the MCC/MNC. |
michael@0 | 13638 | if (mcc != opl.mcc || mnc != opl.mnc) { |
michael@0 | 13639 | continue; |
michael@0 | 13640 | } |
michael@0 | 13641 | // Try to match the location area code. If current local area code is |
michael@0 | 13642 | // covered by lac range that specified in the OPL entry, use the PNN |
michael@0 | 13643 | // that specified in the OPL entry. |
michael@0 | 13644 | if ((opl.lacTacStart === 0x0 && opl.lacTacEnd == 0xFFFE) || |
michael@0 | 13645 | (opl.lacTacStart <= lac && opl.lacTacEnd >= lac)) { |
michael@0 | 13646 | if (opl.pnnRecordId === 0) { |
michael@0 | 13647 | // See 3GPP TS 31.102 Sec. 4.2.59 and 3GPP TS 51.011 Sec. 10.3.42, |
michael@0 | 13648 | // A value of '00' indicates that the name is to be taken from other |
michael@0 | 13649 | // sources. |
michael@0 | 13650 | return null; |
michael@0 | 13651 | } |
michael@0 | 13652 | pnnEntry = iccInfoPriv.PNN[opl.pnnRecordId - 1]; |
michael@0 | 13653 | break; |
michael@0 | 13654 | } |
michael@0 | 13655 | } |
michael@0 | 13656 | } |
michael@0 | 13657 | |
michael@0 | 13658 | if (!pnnEntry) { |
michael@0 | 13659 | return null; |
michael@0 | 13660 | } |
michael@0 | 13661 | |
michael@0 | 13662 | // Return a new object to avoid global variable, PNN, be modified by accident. |
michael@0 | 13663 | return { fullName: pnnEntry.fullName || "", |
michael@0 | 13664 | shortName: pnnEntry.shortName || "" }; |
michael@0 | 13665 | }, |
michael@0 | 13666 | |
michael@0 | 13667 | /** |
michael@0 | 13668 | * This will compute the spnDisplay field of the network. |
michael@0 | 13669 | * See TS 22.101 Annex A and TS 51.011 10.3.11 for details. |
michael@0 | 13670 | * |
michael@0 | 13671 | * @return True if some of iccInfo is changed in by this function. |
michael@0 | 13672 | */ |
michael@0 | 13673 | updateDisplayCondition: function() { |
michael@0 | 13674 | let RIL = this.context.RIL; |
michael@0 | 13675 | |
michael@0 | 13676 | // If EFspn isn't existed in SIM or it haven't been read yet, we should |
michael@0 | 13677 | // just set isDisplayNetworkNameRequired = true and |
michael@0 | 13678 | // isDisplaySpnRequired = false |
michael@0 | 13679 | let iccInfo = RIL.iccInfo; |
michael@0 | 13680 | let iccInfoPriv = RIL.iccInfoPrivate; |
michael@0 | 13681 | let displayCondition = iccInfoPriv.spnDisplayCondition; |
michael@0 | 13682 | let origIsDisplayNetworkNameRequired = iccInfo.isDisplayNetworkNameRequired; |
michael@0 | 13683 | let origIsDisplaySPNRequired = iccInfo.isDisplaySpnRequired; |
michael@0 | 13684 | |
michael@0 | 13685 | if (displayCondition === undefined) { |
michael@0 | 13686 | iccInfo.isDisplayNetworkNameRequired = true; |
michael@0 | 13687 | iccInfo.isDisplaySpnRequired = false; |
michael@0 | 13688 | } else if (RIL._isCdma) { |
michael@0 | 13689 | // CDMA family display rule. |
michael@0 | 13690 | let cdmaHome = RIL.cdmaHome; |
michael@0 | 13691 | let cell = RIL.voiceRegistrationState.cell; |
michael@0 | 13692 | let sid = cell && cell.cdmaSystemId; |
michael@0 | 13693 | let nid = cell && cell.cdmaNetworkId; |
michael@0 | 13694 | |
michael@0 | 13695 | iccInfo.isDisplayNetworkNameRequired = false; |
michael@0 | 13696 | |
michael@0 | 13697 | // If display condition is 0x0, we don't even need to check network id |
michael@0 | 13698 | // or system id. |
michael@0 | 13699 | if (displayCondition === 0x0) { |
michael@0 | 13700 | iccInfo.isDisplaySpnRequired = false; |
michael@0 | 13701 | } else { |
michael@0 | 13702 | // CDMA SPN Display condition dosen't specify whenever network name is |
michael@0 | 13703 | // reqired. |
michael@0 | 13704 | if (!cdmaHome || |
michael@0 | 13705 | !cdmaHome.systemId || |
michael@0 | 13706 | cdmaHome.systemId.length === 0 || |
michael@0 | 13707 | cdmaHome.systemId.length != cdmaHome.networkId.length || |
michael@0 | 13708 | !sid || !nid) { |
michael@0 | 13709 | // CDMA Home haven't been ready, or we haven't got the system id and |
michael@0 | 13710 | // network id of the network we register to, assuming we are in home |
michael@0 | 13711 | // network. |
michael@0 | 13712 | iccInfo.isDisplaySpnRequired = true; |
michael@0 | 13713 | } else { |
michael@0 | 13714 | // Determine if we are registered in the home service area. |
michael@0 | 13715 | // System ID and Network ID are described in 3GPP2 C.S0005 Sec. 2.6.5.2. |
michael@0 | 13716 | let inHomeArea = false; |
michael@0 | 13717 | for (let i = 0; i < cdmaHome.systemId.length; i++) { |
michael@0 | 13718 | let homeSid = cdmaHome.systemId[i], |
michael@0 | 13719 | homeNid = cdmaHome.networkId[i]; |
michael@0 | 13720 | if (homeSid === 0 || homeNid === 0 // Reserved system id/network id |
michael@0 | 13721 | || homeSid != sid) { |
michael@0 | 13722 | continue; |
michael@0 | 13723 | } |
michael@0 | 13724 | // According to 3GPP2 C.S0005 Sec. 2.6.5.2, NID number 65535 means |
michael@0 | 13725 | // all networks in the system should be considered as home. |
michael@0 | 13726 | if (homeNid == 65535 || homeNid == nid) { |
michael@0 | 13727 | inHomeArea = true; |
michael@0 | 13728 | break; |
michael@0 | 13729 | } |
michael@0 | 13730 | } |
michael@0 | 13731 | iccInfo.isDisplaySpnRequired = inHomeArea; |
michael@0 | 13732 | } |
michael@0 | 13733 | } |
michael@0 | 13734 | } else { |
michael@0 | 13735 | // GSM family display rule. |
michael@0 | 13736 | let operatorMnc = RIL.operator.mnc; |
michael@0 | 13737 | let operatorMcc = RIL.operator.mcc; |
michael@0 | 13738 | |
michael@0 | 13739 | // First detect if we are on HPLMN or one of the PLMN |
michael@0 | 13740 | // specified by the SIM card. |
michael@0 | 13741 | let isOnMatchingPlmn = false; |
michael@0 | 13742 | |
michael@0 | 13743 | // If the current network is the one defined as mcc/mnc |
michael@0 | 13744 | // in SIM card, it's okay. |
michael@0 | 13745 | if (iccInfo.mcc == operatorMcc && iccInfo.mnc == operatorMnc) { |
michael@0 | 13746 | isOnMatchingPlmn = true; |
michael@0 | 13747 | } |
michael@0 | 13748 | |
michael@0 | 13749 | // Test to see if operator's mcc/mnc match mcc/mnc of PLMN. |
michael@0 | 13750 | if (!isOnMatchingPlmn && iccInfoPriv.SPDI) { |
michael@0 | 13751 | let iccSpdi = iccInfoPriv.SPDI; // PLMN list |
michael@0 | 13752 | for (let plmn in iccSpdi) { |
michael@0 | 13753 | let plmnMcc = iccSpdi[plmn].mcc; |
michael@0 | 13754 | let plmnMnc = iccSpdi[plmn].mnc; |
michael@0 | 13755 | isOnMatchingPlmn = (plmnMcc == operatorMcc) && (plmnMnc == operatorMnc); |
michael@0 | 13756 | if (isOnMatchingPlmn) { |
michael@0 | 13757 | break; |
michael@0 | 13758 | } |
michael@0 | 13759 | } |
michael@0 | 13760 | } |
michael@0 | 13761 | |
michael@0 | 13762 | if (isOnMatchingPlmn) { |
michael@0 | 13763 | // The first bit of display condition tells us if we should display |
michael@0 | 13764 | // registered PLMN. |
michael@0 | 13765 | if (DEBUG) { |
michael@0 | 13766 | this.context.debug("PLMN is HPLMN or PLMN " + "is in PLMN list"); |
michael@0 | 13767 | } |
michael@0 | 13768 | |
michael@0 | 13769 | // TS 31.102 Sec. 4.2.66 and TS 51.011 Sec. 10.3.50 |
michael@0 | 13770 | // EF_SPDI contains a list of PLMNs in which the Service Provider Name |
michael@0 | 13771 | // shall be displayed. |
michael@0 | 13772 | iccInfo.isDisplaySpnRequired = true; |
michael@0 | 13773 | iccInfo.isDisplayNetworkNameRequired = (displayCondition & 0x01) !== 0; |
michael@0 | 13774 | } else { |
michael@0 | 13775 | // The second bit of display condition tells us if we should display |
michael@0 | 13776 | // registered PLMN. |
michael@0 | 13777 | if (DEBUG) { |
michael@0 | 13778 | this.context.debug("PLMN isn't HPLMN and PLMN isn't in PLMN list"); |
michael@0 | 13779 | } |
michael@0 | 13780 | |
michael@0 | 13781 | // We didn't found the requirement of displaying network name if |
michael@0 | 13782 | // current PLMN isn't HPLMN nor one of PLMN in SPDI. So we keep |
michael@0 | 13783 | // isDisplayNetworkNameRequired false. |
michael@0 | 13784 | iccInfo.isDisplayNetworkNameRequired = false; |
michael@0 | 13785 | iccInfo.isDisplaySpnRequired = (displayCondition & 0x02) === 0; |
michael@0 | 13786 | } |
michael@0 | 13787 | } |
michael@0 | 13788 | |
michael@0 | 13789 | if (DEBUG) { |
michael@0 | 13790 | this.context.debug("isDisplayNetworkNameRequired = " + |
michael@0 | 13791 | iccInfo.isDisplayNetworkNameRequired); |
michael@0 | 13792 | this.context.debug("isDisplaySpnRequired = " + iccInfo.isDisplaySpnRequired); |
michael@0 | 13793 | } |
michael@0 | 13794 | |
michael@0 | 13795 | return ((origIsDisplayNetworkNameRequired !== iccInfo.isDisplayNetworkNameRequired) || |
michael@0 | 13796 | (origIsDisplaySPNRequired !== iccInfo.isDisplaySpnRequired)); |
michael@0 | 13797 | }, |
michael@0 | 13798 | |
michael@0 | 13799 | decodeSimTlvs: function(tlvsLen) { |
michael@0 | 13800 | let GsmPDUHelper = this.context.GsmPDUHelper; |
michael@0 | 13801 | |
michael@0 | 13802 | let index = 0; |
michael@0 | 13803 | let tlvs = []; |
michael@0 | 13804 | while (index < tlvsLen) { |
michael@0 | 13805 | let simTlv = { |
michael@0 | 13806 | tag : GsmPDUHelper.readHexOctet(), |
michael@0 | 13807 | length : GsmPDUHelper.readHexOctet(), |
michael@0 | 13808 | }; |
michael@0 | 13809 | simTlv.value = GsmPDUHelper.readHexOctetArray(simTlv.length); |
michael@0 | 13810 | tlvs.push(simTlv); |
michael@0 | 13811 | index += simTlv.length + 2; // The length of 'tag' and 'length' field. |
michael@0 | 13812 | } |
michael@0 | 13813 | return tlvs; |
michael@0 | 13814 | }, |
michael@0 | 13815 | |
michael@0 | 13816 | /** |
michael@0 | 13817 | * Parse those TLVs and convert it to an object. |
michael@0 | 13818 | */ |
michael@0 | 13819 | parsePbrTlvs: function(pbrTlvs) { |
michael@0 | 13820 | let pbr = {}; |
michael@0 | 13821 | for (let i = 0; i < pbrTlvs.length; i++) { |
michael@0 | 13822 | let pbrTlv = pbrTlvs[i]; |
michael@0 | 13823 | let anrIndex = 0; |
michael@0 | 13824 | for (let j = 0; j < pbrTlv.value.length; j++) { |
michael@0 | 13825 | let tlv = pbrTlv.value[j]; |
michael@0 | 13826 | let tagName = USIM_TAG_NAME[tlv.tag]; |
michael@0 | 13827 | |
michael@0 | 13828 | // ANR could have multiple files. We save it as anr0, anr1,...etc. |
michael@0 | 13829 | if (tlv.tag == ICC_USIM_EFANR_TAG) { |
michael@0 | 13830 | tagName += anrIndex; |
michael@0 | 13831 | anrIndex++; |
michael@0 | 13832 | } |
michael@0 | 13833 | pbr[tagName] = tlv; |
michael@0 | 13834 | pbr[tagName].fileType = pbrTlv.tag; |
michael@0 | 13835 | pbr[tagName].fileId = (tlv.value[0] << 8) | tlv.value[1]; |
michael@0 | 13836 | pbr[tagName].sfi = tlv.value[2]; |
michael@0 | 13837 | |
michael@0 | 13838 | // For Type 2, the order of files is in the same order in IAP. |
michael@0 | 13839 | if (pbrTlv.tag == ICC_USIM_TYPE2_TAG) { |
michael@0 | 13840 | pbr[tagName].indexInIAP = j; |
michael@0 | 13841 | } |
michael@0 | 13842 | } |
michael@0 | 13843 | } |
michael@0 | 13844 | |
michael@0 | 13845 | return pbr; |
michael@0 | 13846 | }, |
michael@0 | 13847 | |
michael@0 | 13848 | /** |
michael@0 | 13849 | * Update the ICC information to RadioInterfaceLayer. |
michael@0 | 13850 | */ |
michael@0 | 13851 | handleICCInfoChange: function() { |
michael@0 | 13852 | let RIL = this.context.RIL; |
michael@0 | 13853 | RIL.iccInfo.rilMessageType = "iccinfochange"; |
michael@0 | 13854 | RIL.sendChromeMessage(RIL.iccInfo); |
michael@0 | 13855 | }, |
michael@0 | 13856 | |
michael@0 | 13857 | /** |
michael@0 | 13858 | * Get whether specificed (U)SIM service is available. |
michael@0 | 13859 | * |
michael@0 | 13860 | * @param geckoService |
michael@0 | 13861 | * Service name like "ADN", "BDN", etc. |
michael@0 | 13862 | * |
michael@0 | 13863 | * @return true if the service is enabled, false otherwise. |
michael@0 | 13864 | */ |
michael@0 | 13865 | isICCServiceAvailable: function(geckoService) { |
michael@0 | 13866 | let RIL = this.context.RIL; |
michael@0 | 13867 | let serviceTable = RIL._isCdma ? RIL.iccInfoPrivate.cst: |
michael@0 | 13868 | RIL.iccInfoPrivate.sst; |
michael@0 | 13869 | let index, bitmask; |
michael@0 | 13870 | if (RIL.appType == CARD_APPTYPE_SIM || RIL.appType == CARD_APPTYPE_RUIM) { |
michael@0 | 13871 | /** |
michael@0 | 13872 | * Service id is valid in 1..N, and 2 bits are used to code each service. |
michael@0 | 13873 | * |
michael@0 | 13874 | * +----+-- --+----+----+ |
michael@0 | 13875 | * | b8 | ... | b2 | b1 | |
michael@0 | 13876 | * +----+-- --+----+----+ |
michael@0 | 13877 | * |
michael@0 | 13878 | * b1 = 0, service not allocated. |
michael@0 | 13879 | * 1, service allocated. |
michael@0 | 13880 | * b2 = 0, service not activatd. |
michael@0 | 13881 | * 1, service allocated. |
michael@0 | 13882 | * |
michael@0 | 13883 | * @see 3GPP TS 51.011 10.3.7. |
michael@0 | 13884 | */ |
michael@0 | 13885 | let simService; |
michael@0 | 13886 | if (RIL.appType == CARD_APPTYPE_SIM) { |
michael@0 | 13887 | simService = GECKO_ICC_SERVICES.sim[geckoService]; |
michael@0 | 13888 | } else { |
michael@0 | 13889 | simService = GECKO_ICC_SERVICES.ruim[geckoService]; |
michael@0 | 13890 | } |
michael@0 | 13891 | if (!simService) { |
michael@0 | 13892 | return false; |
michael@0 | 13893 | } |
michael@0 | 13894 | simService -= 1; |
michael@0 | 13895 | index = Math.floor(simService / 4); |
michael@0 | 13896 | bitmask = 2 << ((simService % 4) << 1); |
michael@0 | 13897 | } else if (RIL.appType == CARD_APPTYPE_USIM) { |
michael@0 | 13898 | /** |
michael@0 | 13899 | * Service id is valid in 1..N, and 1 bit is used to code each service. |
michael@0 | 13900 | * |
michael@0 | 13901 | * +----+-- --+----+----+ |
michael@0 | 13902 | * | b8 | ... | b2 | b1 | |
michael@0 | 13903 | * +----+-- --+----+----+ |
michael@0 | 13904 | * |
michael@0 | 13905 | * b1 = 0, service not avaiable. |
michael@0 | 13906 | * 1, service available. |
michael@0 | 13907 | * b2 = 0, service not avaiable. |
michael@0 | 13908 | * 1, service available. |
michael@0 | 13909 | * |
michael@0 | 13910 | * @see 3GPP TS 31.102 4.2.8. |
michael@0 | 13911 | */ |
michael@0 | 13912 | let usimService = GECKO_ICC_SERVICES.usim[geckoService]; |
michael@0 | 13913 | if (!usimService) { |
michael@0 | 13914 | return false; |
michael@0 | 13915 | } |
michael@0 | 13916 | usimService -= 1; |
michael@0 | 13917 | index = Math.floor(usimService / 8); |
michael@0 | 13918 | bitmask = 1 << ((usimService % 8) << 0); |
michael@0 | 13919 | } |
michael@0 | 13920 | |
michael@0 | 13921 | return (serviceTable !== null) && |
michael@0 | 13922 | (index < serviceTable.length) && |
michael@0 | 13923 | ((serviceTable[index] & bitmask) !== 0); |
michael@0 | 13924 | }, |
michael@0 | 13925 | |
michael@0 | 13926 | /** |
michael@0 | 13927 | * Check if the string is of GSM default 7-bit coded alphabets with bit 8 |
michael@0 | 13928 | * set to 0. |
michael@0 | 13929 | * |
michael@0 | 13930 | * @param str String to be checked. |
michael@0 | 13931 | */ |
michael@0 | 13932 | isGsm8BitAlphabet: function(str) { |
michael@0 | 13933 | if (!str) { |
michael@0 | 13934 | return false; |
michael@0 | 13935 | } |
michael@0 | 13936 | |
michael@0 | 13937 | const langTable = PDU_NL_LOCKING_SHIFT_TABLES[PDU_NL_IDENTIFIER_DEFAULT]; |
michael@0 | 13938 | const langShiftTable = PDU_NL_SINGLE_SHIFT_TABLES[PDU_NL_IDENTIFIER_DEFAULT]; |
michael@0 | 13939 | |
michael@0 | 13940 | for (let i = 0; i < str.length; i++) { |
michael@0 | 13941 | let c = str.charAt(i); |
michael@0 | 13942 | let octet = langTable.indexOf(c); |
michael@0 | 13943 | if (octet == -1) { |
michael@0 | 13944 | octet = langShiftTable.indexOf(c); |
michael@0 | 13945 | if (octet == -1) { |
michael@0 | 13946 | return false; |
michael@0 | 13947 | } |
michael@0 | 13948 | } |
michael@0 | 13949 | } |
michael@0 | 13950 | |
michael@0 | 13951 | return true; |
michael@0 | 13952 | }, |
michael@0 | 13953 | |
michael@0 | 13954 | /** |
michael@0 | 13955 | * Parse MCC/MNC from IMSI. If there is no available value for the length of |
michael@0 | 13956 | * mnc, it will use the data in MCC table to parse. |
michael@0 | 13957 | * |
michael@0 | 13958 | * @param imsi |
michael@0 | 13959 | * The imsi of icc. |
michael@0 | 13960 | * @param mncLength [optional] |
michael@0 | 13961 | * The length of mnc. |
michael@0 | 13962 | * |
michael@0 | 13963 | * @return An object contains the parsing result of mcc and mnc. |
michael@0 | 13964 | * Or null if any error occurred. |
michael@0 | 13965 | */ |
michael@0 | 13966 | parseMccMncFromImsi: function(imsi, mncLength) { |
michael@0 | 13967 | if (!imsi) { |
michael@0 | 13968 | return null; |
michael@0 | 13969 | } |
michael@0 | 13970 | |
michael@0 | 13971 | // MCC is the first 3 digits of IMSI. |
michael@0 | 13972 | let mcc = imsi.substr(0,3); |
michael@0 | 13973 | if (!mncLength) { |
michael@0 | 13974 | // Check the MCC table to decide the length of MNC. |
michael@0 | 13975 | let index = MCC_TABLE_FOR_MNC_LENGTH_IS_3.indexOf(mcc); |
michael@0 | 13976 | mncLength = (index !== -1) ? 3 : 2; |
michael@0 | 13977 | } |
michael@0 | 13978 | let mnc = imsi.substr(3, mncLength); |
michael@0 | 13979 | if (DEBUG) { |
michael@0 | 13980 | this.context.debug("IMSI: " + imsi + " MCC: " + mcc + " MNC: " + mnc); |
michael@0 | 13981 | } |
michael@0 | 13982 | |
michael@0 | 13983 | return { mcc: mcc, mnc: mnc}; |
michael@0 | 13984 | }, |
michael@0 | 13985 | }; |
michael@0 | 13986 | |
michael@0 | 13987 | /** |
michael@0 | 13988 | * Helper for ICC Contacts. |
michael@0 | 13989 | */ |
michael@0 | 13990 | function ICCContactHelperObject(aContext) { |
michael@0 | 13991 | this.context = aContext; |
michael@0 | 13992 | } |
michael@0 | 13993 | ICCContactHelperObject.prototype = { |
michael@0 | 13994 | context: null, |
michael@0 | 13995 | |
michael@0 | 13996 | /** |
michael@0 | 13997 | * Helper function to check DF_PHONEBOOK. |
michael@0 | 13998 | */ |
michael@0 | 13999 | hasDfPhoneBook: function(appType) { |
michael@0 | 14000 | switch (appType) { |
michael@0 | 14001 | case CARD_APPTYPE_SIM: |
michael@0 | 14002 | return false; |
michael@0 | 14003 | case CARD_APPTYPE_USIM: |
michael@0 | 14004 | return true; |
michael@0 | 14005 | case CARD_APPTYPE_RUIM: |
michael@0 | 14006 | let ICCUtilsHelper = this.context.ICCUtilsHelper; |
michael@0 | 14007 | return ICCUtilsHelper.isICCServiceAvailable("ENHANCED_PHONEBOOK"); |
michael@0 | 14008 | default: |
michael@0 | 14009 | return false; |
michael@0 | 14010 | } |
michael@0 | 14011 | }, |
michael@0 | 14012 | |
michael@0 | 14013 | /** |
michael@0 | 14014 | * Helper function to read ICC contacts. |
michael@0 | 14015 | * |
michael@0 | 14016 | * @param appType One of CARD_APPTYPE_*. |
michael@0 | 14017 | * @param contactType "adn" or "fdn". |
michael@0 | 14018 | * @param onsuccess Callback to be called when success. |
michael@0 | 14019 | * @param onerror Callback to be called when error. |
michael@0 | 14020 | */ |
michael@0 | 14021 | readICCContacts: function(appType, contactType, onsuccess, onerror) { |
michael@0 | 14022 | let ICCRecordHelper = this.context.ICCRecordHelper; |
michael@0 | 14023 | |
michael@0 | 14024 | switch (contactType) { |
michael@0 | 14025 | case "adn": |
michael@0 | 14026 | if (!this.hasDfPhoneBook(appType)) { |
michael@0 | 14027 | ICCRecordHelper.readADNLike(ICC_EF_ADN, onsuccess, onerror); |
michael@0 | 14028 | } else { |
michael@0 | 14029 | this.readUSimContacts(onsuccess, onerror); |
michael@0 | 14030 | } |
michael@0 | 14031 | break; |
michael@0 | 14032 | case "fdn": |
michael@0 | 14033 | ICCRecordHelper.readADNLike(ICC_EF_FDN, onsuccess, onerror); |
michael@0 | 14034 | break; |
michael@0 | 14035 | default: |
michael@0 | 14036 | if (DEBUG) { |
michael@0 | 14037 | this.context.debug("Unsupported contactType :" + contactType); |
michael@0 | 14038 | } |
michael@0 | 14039 | onerror(CONTACT_ERR_CONTACT_TYPE_NOT_SUPPORTED); |
michael@0 | 14040 | break; |
michael@0 | 14041 | } |
michael@0 | 14042 | }, |
michael@0 | 14043 | |
michael@0 | 14044 | /** |
michael@0 | 14045 | * Helper function to find free contact record. |
michael@0 | 14046 | * |
michael@0 | 14047 | * @param appType One of CARD_APPTYPE_*. |
michael@0 | 14048 | * @param contactType "adn" or "fdn". |
michael@0 | 14049 | * @param onsuccess Callback to be called when success. |
michael@0 | 14050 | * @param onerror Callback to be called when error. |
michael@0 | 14051 | */ |
michael@0 | 14052 | findFreeICCContact: function(appType, contactType, onsuccess, onerror) { |
michael@0 | 14053 | let ICCRecordHelper = this.context.ICCRecordHelper; |
michael@0 | 14054 | |
michael@0 | 14055 | switch (contactType) { |
michael@0 | 14056 | case "adn": |
michael@0 | 14057 | if (!this.hasDfPhoneBook(appType)) { |
michael@0 | 14058 | ICCRecordHelper.findFreeRecordId(ICC_EF_ADN, onsuccess.bind(null, 0), onerror); |
michael@0 | 14059 | } else { |
michael@0 | 14060 | let gotPbrCb = function gotPbrCb(pbrs) { |
michael@0 | 14061 | this.findUSimFreeADNRecordId(pbrs, onsuccess, onerror); |
michael@0 | 14062 | }.bind(this); |
michael@0 | 14063 | |
michael@0 | 14064 | ICCRecordHelper.readPBR(gotPbrCb, onerror); |
michael@0 | 14065 | } |
michael@0 | 14066 | break; |
michael@0 | 14067 | case "fdn": |
michael@0 | 14068 | ICCRecordHelper.findFreeRecordId(ICC_EF_FDN, onsuccess.bind(null, 0), onerror); |
michael@0 | 14069 | break; |
michael@0 | 14070 | default: |
michael@0 | 14071 | if (DEBUG) { |
michael@0 | 14072 | this.context.debug("Unsupported contactType :" + contactType); |
michael@0 | 14073 | } |
michael@0 | 14074 | onerror(CONTACT_ERR_CONTACT_TYPE_NOT_SUPPORTED); |
michael@0 | 14075 | break; |
michael@0 | 14076 | } |
michael@0 | 14077 | }, |
michael@0 | 14078 | |
michael@0 | 14079 | /** |
michael@0 | 14080 | * Find free ADN record id in USIM. |
michael@0 | 14081 | * |
michael@0 | 14082 | * @param pbrs All Phonebook Reference Files read. |
michael@0 | 14083 | * @param onsuccess Callback to be called when success. |
michael@0 | 14084 | * @param onerror Callback to be called when error. |
michael@0 | 14085 | */ |
michael@0 | 14086 | findUSimFreeADNRecordId: function(pbrs, onsuccess, onerror) { |
michael@0 | 14087 | let ICCRecordHelper = this.context.ICCRecordHelper; |
michael@0 | 14088 | |
michael@0 | 14089 | (function findFreeRecordId(pbrIndex) { |
michael@0 | 14090 | if (pbrIndex >= pbrs.length) { |
michael@0 | 14091 | if (DEBUG) { |
michael@0 | 14092 | this.context.debug(CONTACT_ERR_NO_FREE_RECORD_FOUND); |
michael@0 | 14093 | } |
michael@0 | 14094 | onerror(CONTACT_ERR_NO_FREE_RECORD_FOUND); |
michael@0 | 14095 | return; |
michael@0 | 14096 | } |
michael@0 | 14097 | |
michael@0 | 14098 | let pbr = pbrs[pbrIndex]; |
michael@0 | 14099 | ICCRecordHelper.findFreeRecordId( |
michael@0 | 14100 | pbr.adn.fileId, |
michael@0 | 14101 | onsuccess.bind(this, pbrIndex), |
michael@0 | 14102 | findFreeRecordId.bind(null, pbrIndex + 1)); |
michael@0 | 14103 | })(0); |
michael@0 | 14104 | }, |
michael@0 | 14105 | |
michael@0 | 14106 | /** |
michael@0 | 14107 | * Helper function to add a new ICC contact. |
michael@0 | 14108 | * |
michael@0 | 14109 | * @param appType One of CARD_APPTYPE_*. |
michael@0 | 14110 | * @param contactType "adn" or "fdn". |
michael@0 | 14111 | * @param contact The contact will be added. |
michael@0 | 14112 | * @param pin2 PIN2 is required for FDN. |
michael@0 | 14113 | * @param onsuccess Callback to be called when success. |
michael@0 | 14114 | * @param onerror Callback to be called when error. |
michael@0 | 14115 | */ |
michael@0 | 14116 | addICCContact: function(appType, contactType, contact, pin2, onsuccess, onerror) { |
michael@0 | 14117 | let foundFreeCb = (function foundFreeCb(pbrIndex, recordId) { |
michael@0 | 14118 | contact.pbrIndex = pbrIndex; |
michael@0 | 14119 | contact.recordId = recordId; |
michael@0 | 14120 | this.updateICCContact(appType, contactType, contact, pin2, onsuccess, onerror); |
michael@0 | 14121 | }).bind(this); |
michael@0 | 14122 | |
michael@0 | 14123 | // Find free record first. |
michael@0 | 14124 | this.findFreeICCContact(appType, contactType, foundFreeCb, onerror); |
michael@0 | 14125 | }, |
michael@0 | 14126 | |
michael@0 | 14127 | /** |
michael@0 | 14128 | * Helper function to update ICC contact. |
michael@0 | 14129 | * |
michael@0 | 14130 | * @param appType One of CARD_APPTYPE_*. |
michael@0 | 14131 | * @param contactType "adn" or "fdn". |
michael@0 | 14132 | * @param contact The contact will be updated. |
michael@0 | 14133 | * @param pin2 PIN2 is required for FDN. |
michael@0 | 14134 | * @param onsuccess Callback to be called when success. |
michael@0 | 14135 | * @param onerror Callback to be called when error. |
michael@0 | 14136 | */ |
michael@0 | 14137 | updateICCContact: function(appType, contactType, contact, pin2, onsuccess, onerror) { |
michael@0 | 14138 | let ICCRecordHelper = this.context.ICCRecordHelper; |
michael@0 | 14139 | |
michael@0 | 14140 | switch (contactType) { |
michael@0 | 14141 | case "adn": |
michael@0 | 14142 | if (!this.hasDfPhoneBook(appType)) { |
michael@0 | 14143 | ICCRecordHelper.updateADNLike(ICC_EF_ADN, contact, null, onsuccess, onerror); |
michael@0 | 14144 | } else { |
michael@0 | 14145 | this.updateUSimContact(contact, onsuccess, onerror); |
michael@0 | 14146 | } |
michael@0 | 14147 | break; |
michael@0 | 14148 | case "fdn": |
michael@0 | 14149 | if (!pin2) { |
michael@0 | 14150 | onerror(GECKO_ERROR_SIM_PIN2); |
michael@0 | 14151 | return; |
michael@0 | 14152 | } |
michael@0 | 14153 | ICCRecordHelper.updateADNLike(ICC_EF_FDN, contact, pin2, onsuccess, onerror); |
michael@0 | 14154 | break; |
michael@0 | 14155 | default: |
michael@0 | 14156 | if (DEBUG) { |
michael@0 | 14157 | this.context.debug("Unsupported contactType :" + contactType); |
michael@0 | 14158 | } |
michael@0 | 14159 | onerror(CONTACT_ERR_CONTACT_TYPE_NOT_SUPPORTED); |
michael@0 | 14160 | break; |
michael@0 | 14161 | } |
michael@0 | 14162 | }, |
michael@0 | 14163 | |
michael@0 | 14164 | /** |
michael@0 | 14165 | * Read contacts from USIM. |
michael@0 | 14166 | * |
michael@0 | 14167 | * @param onsuccess Callback to be called when success. |
michael@0 | 14168 | * @param onerror Callback to be called when error. |
michael@0 | 14169 | */ |
michael@0 | 14170 | readUSimContacts: function(onsuccess, onerror) { |
michael@0 | 14171 | let gotPbrCb = function gotPbrCb(pbrs) { |
michael@0 | 14172 | this.readAllPhonebookSets(pbrs, onsuccess, onerror); |
michael@0 | 14173 | }.bind(this); |
michael@0 | 14174 | |
michael@0 | 14175 | this.context.ICCRecordHelper.readPBR(gotPbrCb, onerror); |
michael@0 | 14176 | }, |
michael@0 | 14177 | |
michael@0 | 14178 | /** |
michael@0 | 14179 | * Read all Phonebook sets. |
michael@0 | 14180 | * |
michael@0 | 14181 | * @param pbrs All Phonebook Reference Files read. |
michael@0 | 14182 | * @param onsuccess Callback to be called when success. |
michael@0 | 14183 | * @param onerror Callback to be called when error. |
michael@0 | 14184 | */ |
michael@0 | 14185 | readAllPhonebookSets: function(pbrs, onsuccess, onerror) { |
michael@0 | 14186 | let allContacts = [], pbrIndex = 0; |
michael@0 | 14187 | let readPhonebook = function readPhonebook(contacts) { |
michael@0 | 14188 | if (contacts) { |
michael@0 | 14189 | allContacts = allContacts.concat(contacts); |
michael@0 | 14190 | } |
michael@0 | 14191 | |
michael@0 | 14192 | let cLen = contacts ? contacts.length : 0; |
michael@0 | 14193 | for (let i = 0; i < cLen; i++) { |
michael@0 | 14194 | contacts[i].pbrIndex = pbrIndex; |
michael@0 | 14195 | } |
michael@0 | 14196 | |
michael@0 | 14197 | pbrIndex++; |
michael@0 | 14198 | if (pbrIndex >= pbrs.length) { |
michael@0 | 14199 | if (onsuccess) { |
michael@0 | 14200 | onsuccess(allContacts); |
michael@0 | 14201 | } |
michael@0 | 14202 | return; |
michael@0 | 14203 | } |
michael@0 | 14204 | |
michael@0 | 14205 | this.readPhonebookSet(pbrs[pbrIndex], readPhonebook, onerror); |
michael@0 | 14206 | }.bind(this); |
michael@0 | 14207 | |
michael@0 | 14208 | this.readPhonebookSet(pbrs[pbrIndex], readPhonebook, onerror); |
michael@0 | 14209 | }, |
michael@0 | 14210 | |
michael@0 | 14211 | /** |
michael@0 | 14212 | * Read from Phonebook Reference File. |
michael@0 | 14213 | * |
michael@0 | 14214 | * @param pbr Phonebook Reference File to be read. |
michael@0 | 14215 | * @param onsuccess Callback to be called when success. |
michael@0 | 14216 | * @param onerror Callback to be called when error. |
michael@0 | 14217 | */ |
michael@0 | 14218 | readPhonebookSet: function(pbr, onsuccess, onerror) { |
michael@0 | 14219 | let gotAdnCb = function gotAdnCb(contacts) { |
michael@0 | 14220 | this.readSupportedPBRFields(pbr, contacts, onsuccess, onerror); |
michael@0 | 14221 | }.bind(this); |
michael@0 | 14222 | |
michael@0 | 14223 | this.context.ICCRecordHelper.readADNLike(pbr.adn.fileId, gotAdnCb, onerror); |
michael@0 | 14224 | }, |
michael@0 | 14225 | |
michael@0 | 14226 | /** |
michael@0 | 14227 | * Read supported Phonebook fields. |
michael@0 | 14228 | * |
michael@0 | 14229 | * @param pbr Phone Book Reference file. |
michael@0 | 14230 | * @param contacts Contacts stored on ICC. |
michael@0 | 14231 | * @param onsuccess Callback to be called when success. |
michael@0 | 14232 | * @param onerror Callback to be called when error. |
michael@0 | 14233 | */ |
michael@0 | 14234 | readSupportedPBRFields: function(pbr, contacts, onsuccess, onerror) { |
michael@0 | 14235 | let fieldIndex = 0; |
michael@0 | 14236 | (function readField() { |
michael@0 | 14237 | let field = USIM_PBR_FIELDS[fieldIndex]; |
michael@0 | 14238 | fieldIndex += 1; |
michael@0 | 14239 | if (!field) { |
michael@0 | 14240 | if (onsuccess) { |
michael@0 | 14241 | onsuccess(contacts); |
michael@0 | 14242 | } |
michael@0 | 14243 | return; |
michael@0 | 14244 | } |
michael@0 | 14245 | |
michael@0 | 14246 | this.readPhonebookField(pbr, contacts, field, readField.bind(this), onerror); |
michael@0 | 14247 | }).call(this); |
michael@0 | 14248 | }, |
michael@0 | 14249 | |
michael@0 | 14250 | /** |
michael@0 | 14251 | * Read Phonebook field. |
michael@0 | 14252 | * |
michael@0 | 14253 | * @param pbr The phonebook reference file. |
michael@0 | 14254 | * @param contacts Contacts stored on ICC. |
michael@0 | 14255 | * @param field Phonebook field to be retrieved. |
michael@0 | 14256 | * @param onsuccess Callback to be called when success. |
michael@0 | 14257 | * @param onerror Callback to be called when error. |
michael@0 | 14258 | */ |
michael@0 | 14259 | readPhonebookField: function(pbr, contacts, field, onsuccess, onerror) { |
michael@0 | 14260 | if (!pbr[field]) { |
michael@0 | 14261 | if (onsuccess) { |
michael@0 | 14262 | onsuccess(contacts); |
michael@0 | 14263 | } |
michael@0 | 14264 | return; |
michael@0 | 14265 | } |
michael@0 | 14266 | |
michael@0 | 14267 | (function doReadContactField(n) { |
michael@0 | 14268 | if (n >= contacts.length) { |
michael@0 | 14269 | // All contact's fields are read. |
michael@0 | 14270 | if (onsuccess) { |
michael@0 | 14271 | onsuccess(contacts); |
michael@0 | 14272 | } |
michael@0 | 14273 | return; |
michael@0 | 14274 | } |
michael@0 | 14275 | |
michael@0 | 14276 | // get n-th contact's field. |
michael@0 | 14277 | this.readContactField(pbr, contacts[n], field, |
michael@0 | 14278 | doReadContactField.bind(this, n + 1), onerror); |
michael@0 | 14279 | }).call(this, 0); |
michael@0 | 14280 | }, |
michael@0 | 14281 | |
michael@0 | 14282 | /** |
michael@0 | 14283 | * Read contact's field from USIM. |
michael@0 | 14284 | * |
michael@0 | 14285 | * @param pbr The phonebook reference file. |
michael@0 | 14286 | * @param contact The contact needs to get field. |
michael@0 | 14287 | * @param field Phonebook field to be retrieved. |
michael@0 | 14288 | * @param onsuccess Callback to be called when success. |
michael@0 | 14289 | * @param onerror Callback to be called when error. |
michael@0 | 14290 | */ |
michael@0 | 14291 | readContactField: function(pbr, contact, field, onsuccess, onerror) { |
michael@0 | 14292 | let gotRecordIdCb = function gotRecordIdCb(recordId) { |
michael@0 | 14293 | if (recordId == 0xff) { |
michael@0 | 14294 | if (onsuccess) { |
michael@0 | 14295 | onsuccess(); |
michael@0 | 14296 | } |
michael@0 | 14297 | return; |
michael@0 | 14298 | } |
michael@0 | 14299 | |
michael@0 | 14300 | let fileId = pbr[field].fileId; |
michael@0 | 14301 | let fileType = pbr[field].fileType; |
michael@0 | 14302 | let gotFieldCb = function gotFieldCb(value) { |
michael@0 | 14303 | if (value) { |
michael@0 | 14304 | // Move anr0 anr1,.. into anr[]. |
michael@0 | 14305 | if (field.startsWith(USIM_PBR_ANR)) { |
michael@0 | 14306 | if (!contact[USIM_PBR_ANR]) { |
michael@0 | 14307 | contact[USIM_PBR_ANR] = []; |
michael@0 | 14308 | } |
michael@0 | 14309 | contact[USIM_PBR_ANR].push(value); |
michael@0 | 14310 | } else { |
michael@0 | 14311 | contact[field] = value; |
michael@0 | 14312 | } |
michael@0 | 14313 | } |
michael@0 | 14314 | |
michael@0 | 14315 | if (onsuccess) { |
michael@0 | 14316 | onsuccess(); |
michael@0 | 14317 | } |
michael@0 | 14318 | }.bind(this); |
michael@0 | 14319 | |
michael@0 | 14320 | let ICCRecordHelper = this.context.ICCRecordHelper; |
michael@0 | 14321 | // Detect EF to be read, for anr, it could have anr0, anr1,... |
michael@0 | 14322 | let ef = field.startsWith(USIM_PBR_ANR) ? USIM_PBR_ANR : field; |
michael@0 | 14323 | switch (ef) { |
michael@0 | 14324 | case USIM_PBR_EMAIL: |
michael@0 | 14325 | ICCRecordHelper.readEmail(fileId, fileType, recordId, gotFieldCb, onerror); |
michael@0 | 14326 | break; |
michael@0 | 14327 | case USIM_PBR_ANR: |
michael@0 | 14328 | ICCRecordHelper.readANR(fileId, fileType, recordId, gotFieldCb, onerror); |
michael@0 | 14329 | break; |
michael@0 | 14330 | default: |
michael@0 | 14331 | if (DEBUG) { |
michael@0 | 14332 | this.context.debug("Unsupported field :" + field); |
michael@0 | 14333 | } |
michael@0 | 14334 | onerror(CONTACT_ERR_FIELD_NOT_SUPPORTED); |
michael@0 | 14335 | break; |
michael@0 | 14336 | } |
michael@0 | 14337 | }.bind(this); |
michael@0 | 14338 | |
michael@0 | 14339 | this.getContactFieldRecordId(pbr, contact, field, gotRecordIdCb, onerror); |
michael@0 | 14340 | }, |
michael@0 | 14341 | |
michael@0 | 14342 | /** |
michael@0 | 14343 | * Get the recordId. |
michael@0 | 14344 | * |
michael@0 | 14345 | * If the fileType of field is ICC_USIM_TYPE1_TAG, use corresponding ADN recordId. |
michael@0 | 14346 | * otherwise get the recordId from IAP. |
michael@0 | 14347 | * |
michael@0 | 14348 | * @see TS 131.102, clause 4.4.2.2 |
michael@0 | 14349 | * |
michael@0 | 14350 | * @param pbr The phonebook reference file. |
michael@0 | 14351 | * @param contact The contact will be updated. |
michael@0 | 14352 | * @param onsuccess Callback to be called when success. |
michael@0 | 14353 | * @param onerror Callback to be called when error. |
michael@0 | 14354 | */ |
michael@0 | 14355 | getContactFieldRecordId: function(pbr, contact, field, onsuccess, onerror) { |
michael@0 | 14356 | if (pbr[field].fileType == ICC_USIM_TYPE1_TAG) { |
michael@0 | 14357 | // If the file type is ICC_USIM_TYPE1_TAG, use corresponding ADN recordId. |
michael@0 | 14358 | if (onsuccess) { |
michael@0 | 14359 | onsuccess(contact.recordId); |
michael@0 | 14360 | } |
michael@0 | 14361 | } else if (pbr[field].fileType == ICC_USIM_TYPE2_TAG) { |
michael@0 | 14362 | // If the file type is ICC_USIM_TYPE2_TAG, the recordId shall be got from IAP. |
michael@0 | 14363 | let gotIapCb = function gotIapCb(iap) { |
michael@0 | 14364 | let indexInIAP = pbr[field].indexInIAP; |
michael@0 | 14365 | let recordId = iap[indexInIAP]; |
michael@0 | 14366 | |
michael@0 | 14367 | if (onsuccess) { |
michael@0 | 14368 | onsuccess(recordId); |
michael@0 | 14369 | } |
michael@0 | 14370 | }.bind(this); |
michael@0 | 14371 | |
michael@0 | 14372 | this.context.ICCRecordHelper.readIAP(pbr.iap.fileId, contact.recordId, |
michael@0 | 14373 | gotIapCb, onerror); |
michael@0 | 14374 | } else { |
michael@0 | 14375 | if (DEBUG) { |
michael@0 | 14376 | this.context.debug("USIM PBR files in Type 3 format are not supported."); |
michael@0 | 14377 | } |
michael@0 | 14378 | onerror(CONTACT_ERR_REQUEST_NOT_SUPPORTED); |
michael@0 | 14379 | } |
michael@0 | 14380 | }, |
michael@0 | 14381 | |
michael@0 | 14382 | /** |
michael@0 | 14383 | * Update USIM contact. |
michael@0 | 14384 | * |
michael@0 | 14385 | * @param contact The contact will be updated. |
michael@0 | 14386 | * @param onsuccess Callback to be called when success. |
michael@0 | 14387 | * @param onerror Callback to be called when error. |
michael@0 | 14388 | */ |
michael@0 | 14389 | updateUSimContact: function(contact, onsuccess, onerror) { |
michael@0 | 14390 | let gotPbrCb = function gotPbrCb(pbrs) { |
michael@0 | 14391 | let pbr = pbrs[contact.pbrIndex]; |
michael@0 | 14392 | if (!pbr) { |
michael@0 | 14393 | if (DEBUG) { |
michael@0 | 14394 | this.context.debug(CONTACT_ERR_CANNOT_ACCESS_PHONEBOOK); |
michael@0 | 14395 | } |
michael@0 | 14396 | onerror(CONTACT_ERR_CANNOT_ACCESS_PHONEBOOK); |
michael@0 | 14397 | return; |
michael@0 | 14398 | } |
michael@0 | 14399 | this.updatePhonebookSet(pbr, contact, onsuccess, onerror); |
michael@0 | 14400 | }.bind(this); |
michael@0 | 14401 | |
michael@0 | 14402 | this.context.ICCRecordHelper.readPBR(gotPbrCb, onerror); |
michael@0 | 14403 | }, |
michael@0 | 14404 | |
michael@0 | 14405 | /** |
michael@0 | 14406 | * Update fields in Phonebook Reference File. |
michael@0 | 14407 | * |
michael@0 | 14408 | * @param pbr Phonebook Reference File to be read. |
michael@0 | 14409 | * @param onsuccess Callback to be called when success. |
michael@0 | 14410 | * @param onerror Callback to be called when error. |
michael@0 | 14411 | */ |
michael@0 | 14412 | updatePhonebookSet: function(pbr, contact, onsuccess, onerror) { |
michael@0 | 14413 | let updateAdnCb = function() { |
michael@0 | 14414 | this.updateSupportedPBRFields(pbr, contact, onsuccess, onerror); |
michael@0 | 14415 | }.bind(this); |
michael@0 | 14416 | |
michael@0 | 14417 | this.context.ICCRecordHelper.updateADNLike(pbr.adn.fileId, contact, null, |
michael@0 | 14418 | updateAdnCb, onerror); |
michael@0 | 14419 | }, |
michael@0 | 14420 | |
michael@0 | 14421 | /** |
michael@0 | 14422 | * Update supported Phonebook fields. |
michael@0 | 14423 | * |
michael@0 | 14424 | * @param pbr Phone Book Reference file. |
michael@0 | 14425 | * @param contact Contact to be updated. |
michael@0 | 14426 | * @param onsuccess Callback to be called when success. |
michael@0 | 14427 | * @param onerror Callback to be called when error. |
michael@0 | 14428 | */ |
michael@0 | 14429 | updateSupportedPBRFields: function(pbr, contact, onsuccess, onerror) { |
michael@0 | 14430 | let fieldIndex = 0; |
michael@0 | 14431 | (function updateField() { |
michael@0 | 14432 | let field = USIM_PBR_FIELDS[fieldIndex]; |
michael@0 | 14433 | fieldIndex += 1; |
michael@0 | 14434 | if (!field) { |
michael@0 | 14435 | if (onsuccess) { |
michael@0 | 14436 | onsuccess(); |
michael@0 | 14437 | } |
michael@0 | 14438 | return; |
michael@0 | 14439 | } |
michael@0 | 14440 | |
michael@0 | 14441 | // Check if PBR has this field. |
michael@0 | 14442 | if (!pbr[field]) { |
michael@0 | 14443 | updateField.call(this); |
michael@0 | 14444 | return; |
michael@0 | 14445 | } |
michael@0 | 14446 | |
michael@0 | 14447 | this.updateContactField(pbr, contact, field, updateField.bind(this), onerror); |
michael@0 | 14448 | }).call(this); |
michael@0 | 14449 | }, |
michael@0 | 14450 | |
michael@0 | 14451 | /** |
michael@0 | 14452 | * Update contact's field from USIM. |
michael@0 | 14453 | * |
michael@0 | 14454 | * @param pbr The phonebook reference file. |
michael@0 | 14455 | * @param contact The contact needs to be updated. |
michael@0 | 14456 | * @param field Phonebook field to be updated. |
michael@0 | 14457 | * @param onsuccess Callback to be called when success. |
michael@0 | 14458 | * @param onerror Callback to be called when error. |
michael@0 | 14459 | */ |
michael@0 | 14460 | updateContactField: function(pbr, contact, field, onsuccess, onerror) { |
michael@0 | 14461 | if (pbr[field].fileType === ICC_USIM_TYPE1_TAG) { |
michael@0 | 14462 | this.updateContactFieldType1(pbr, contact, field, onsuccess, onerror); |
michael@0 | 14463 | } else if (pbr[field].fileType === ICC_USIM_TYPE2_TAG) { |
michael@0 | 14464 | this.updateContactFieldType2(pbr, contact, field, onsuccess, onerror); |
michael@0 | 14465 | } else { |
michael@0 | 14466 | if (DEBUG) { |
michael@0 | 14467 | this.context.debug("USIM PBR files in Type 3 format are not supported."); |
michael@0 | 14468 | } |
michael@0 | 14469 | onerror(CONTACT_ERR_REQUEST_NOT_SUPPORTED); |
michael@0 | 14470 | } |
michael@0 | 14471 | }, |
michael@0 | 14472 | |
michael@0 | 14473 | /** |
michael@0 | 14474 | * Update Type 1 USIM contact fields. |
michael@0 | 14475 | * |
michael@0 | 14476 | * @param pbr The phonebook reference file. |
michael@0 | 14477 | * @param contact The contact needs to be updated. |
michael@0 | 14478 | * @param field Phonebook field to be updated. |
michael@0 | 14479 | * @param onsuccess Callback to be called when success. |
michael@0 | 14480 | * @param onerror Callback to be called when error. |
michael@0 | 14481 | */ |
michael@0 | 14482 | updateContactFieldType1: function(pbr, contact, field, onsuccess, onerror) { |
michael@0 | 14483 | let ICCRecordHelper = this.context.ICCRecordHelper; |
michael@0 | 14484 | |
michael@0 | 14485 | if (field === USIM_PBR_EMAIL) { |
michael@0 | 14486 | ICCRecordHelper.updateEmail(pbr, contact.recordId, contact.email, null, onsuccess, onerror); |
michael@0 | 14487 | } else if (field === USIM_PBR_ANR0) { |
michael@0 | 14488 | let anr = Array.isArray(contact.anr) ? contact.anr[0] : null; |
michael@0 | 14489 | ICCRecordHelper.updateANR(pbr, contact.recordId, anr, null, onsuccess, onerror); |
michael@0 | 14490 | } else { |
michael@0 | 14491 | if (DEBUG) { |
michael@0 | 14492 | this.context.debug("Unsupported field :" + field); |
michael@0 | 14493 | } |
michael@0 | 14494 | onerror(CONTACT_ERR_FIELD_NOT_SUPPORTED); |
michael@0 | 14495 | } |
michael@0 | 14496 | }, |
michael@0 | 14497 | |
michael@0 | 14498 | /** |
michael@0 | 14499 | * Update Type 2 USIM contact fields. |
michael@0 | 14500 | * |
michael@0 | 14501 | * @param pbr The phonebook reference file. |
michael@0 | 14502 | * @param contact The contact needs to be updated. |
michael@0 | 14503 | * @param field Phonebook field to be updated. |
michael@0 | 14504 | * @param onsuccess Callback to be called when success. |
michael@0 | 14505 | * @param onerror Callback to be called when error. |
michael@0 | 14506 | */ |
michael@0 | 14507 | updateContactFieldType2: function(pbr, contact, field, onsuccess, onerror) { |
michael@0 | 14508 | let ICCRecordHelper = this.context.ICCRecordHelper; |
michael@0 | 14509 | |
michael@0 | 14510 | // Case 1 : EF_IAP[adnRecordId] doesn't have a value(0xff) |
michael@0 | 14511 | // Find a free recordId for EF_field |
michael@0 | 14512 | // Update field with that free recordId. |
michael@0 | 14513 | // Update IAP. |
michael@0 | 14514 | // |
michael@0 | 14515 | // Case 2: EF_IAP[adnRecordId] has a value |
michael@0 | 14516 | // update EF_field[iap[field.indexInIAP]] |
michael@0 | 14517 | |
michael@0 | 14518 | let gotIapCb = function gotIapCb(iap) { |
michael@0 | 14519 | let recordId = iap[pbr[field].indexInIAP]; |
michael@0 | 14520 | if (recordId === 0xff) { |
michael@0 | 14521 | // If the value in IAP[index] is 0xff, which means the contact stored on |
michael@0 | 14522 | // the SIM doesn't have the additional attribute (email or anr). |
michael@0 | 14523 | // So if the contact to be updated doesn't have the attribute either, |
michael@0 | 14524 | // we don't have to update it. |
michael@0 | 14525 | if ((field === USIM_PBR_EMAIL && contact.email) || |
michael@0 | 14526 | (field === USIM_PBR_ANR0 && |
michael@0 | 14527 | (Array.isArray(contact.anr) && contact.anr[0]))) { |
michael@0 | 14528 | // Case 1. |
michael@0 | 14529 | this.addContactFieldType2(pbr, contact, field, onsuccess, onerror); |
michael@0 | 14530 | } else { |
michael@0 | 14531 | if (onsuccess) { |
michael@0 | 14532 | onsuccess(); |
michael@0 | 14533 | } |
michael@0 | 14534 | } |
michael@0 | 14535 | return; |
michael@0 | 14536 | } |
michael@0 | 14537 | |
michael@0 | 14538 | // Case 2. |
michael@0 | 14539 | if (field === USIM_PBR_EMAIL) { |
michael@0 | 14540 | ICCRecordHelper.updateEmail(pbr, recordId, contact.email, contact.recordId, onsuccess, onerror); |
michael@0 | 14541 | } else if (field === USIM_PBR_ANR0) { |
michael@0 | 14542 | let anr = Array.isArray(contact.anr) ? contact.anr[0] : null; |
michael@0 | 14543 | ICCRecordHelper.updateANR(pbr, recordId, anr, contact.recordId, onsuccess, onerror); |
michael@0 | 14544 | } else { |
michael@0 | 14545 | if (DEBUG) { |
michael@0 | 14546 | this.context.debug("Unsupported field :" + field); |
michael@0 | 14547 | } |
michael@0 | 14548 | onerror(CONTACT_ERR_FIELD_NOT_SUPPORTED); |
michael@0 | 14549 | } |
michael@0 | 14550 | |
michael@0 | 14551 | }.bind(this); |
michael@0 | 14552 | |
michael@0 | 14553 | ICCRecordHelper.readIAP(pbr.iap.fileId, contact.recordId, gotIapCb, onerror); |
michael@0 | 14554 | }, |
michael@0 | 14555 | |
michael@0 | 14556 | /** |
michael@0 | 14557 | * Add Type 2 USIM contact fields. |
michael@0 | 14558 | * |
michael@0 | 14559 | * @param pbr The phonebook reference file. |
michael@0 | 14560 | * @param contact The contact needs to be updated. |
michael@0 | 14561 | * @param field Phonebook field to be updated. |
michael@0 | 14562 | * @param onsuccess Callback to be called when success. |
michael@0 | 14563 | * @param onerror Callback to be called when error. |
michael@0 | 14564 | */ |
michael@0 | 14565 | addContactFieldType2: function(pbr, contact, field, onsuccess, onerror) { |
michael@0 | 14566 | let ICCRecordHelper = this.context.ICCRecordHelper; |
michael@0 | 14567 | |
michael@0 | 14568 | let successCb = function successCb(recordId) { |
michael@0 | 14569 | let updateCb = function updateCb() { |
michael@0 | 14570 | this.updateContactFieldIndexInIAP(pbr, contact.recordId, field, recordId, onsuccess, onerror); |
michael@0 | 14571 | }.bind(this); |
michael@0 | 14572 | |
michael@0 | 14573 | if (field === USIM_PBR_EMAIL) { |
michael@0 | 14574 | ICCRecordHelper.updateEmail(pbr, recordId, contact.email, contact.recordId, updateCb, onerror); |
michael@0 | 14575 | } else if (field === USIM_PBR_ANR0) { |
michael@0 | 14576 | ICCRecordHelper.updateANR(pbr, recordId, contact.anr[0], contact.recordId, updateCb, onerror); |
michael@0 | 14577 | } |
michael@0 | 14578 | }.bind(this); |
michael@0 | 14579 | |
michael@0 | 14580 | let errorCb = function errorCb(errorMsg) { |
michael@0 | 14581 | if (DEBUG) { |
michael@0 | 14582 | this.context.debug(errorMsg + " USIM field " + field); |
michael@0 | 14583 | } |
michael@0 | 14584 | onerror(errorMsg); |
michael@0 | 14585 | }.bind(this); |
michael@0 | 14586 | |
michael@0 | 14587 | ICCRecordHelper.findFreeRecordId(pbr[field].fileId, successCb, errorCb); |
michael@0 | 14588 | }, |
michael@0 | 14589 | |
michael@0 | 14590 | /** |
michael@0 | 14591 | * Update IAP value. |
michael@0 | 14592 | * |
michael@0 | 14593 | * @param pbr The phonebook reference file. |
michael@0 | 14594 | * @param recordNumber The record identifier of EF_IAP. |
michael@0 | 14595 | * @param field Phonebook field. |
michael@0 | 14596 | * @param value The value of 'field' in IAP. |
michael@0 | 14597 | * @param onsuccess Callback to be called when success. |
michael@0 | 14598 | * @param onerror Callback to be called when error. |
michael@0 | 14599 | * |
michael@0 | 14600 | */ |
michael@0 | 14601 | updateContactFieldIndexInIAP: function(pbr, recordNumber, field, value, onsuccess, onerror) { |
michael@0 | 14602 | let ICCRecordHelper = this.context.ICCRecordHelper; |
michael@0 | 14603 | |
michael@0 | 14604 | let gotIAPCb = function gotIAPCb(iap) { |
michael@0 | 14605 | iap[pbr[field].indexInIAP] = value; |
michael@0 | 14606 | ICCRecordHelper.updateIAP(pbr.iap.fileId, recordNumber, iap, onsuccess, onerror); |
michael@0 | 14607 | }.bind(this); |
michael@0 | 14608 | ICCRecordHelper.readIAP(pbr.iap.fileId, recordNumber, gotIAPCb, onerror); |
michael@0 | 14609 | }, |
michael@0 | 14610 | }; |
michael@0 | 14611 | |
michael@0 | 14612 | /** |
michael@0 | 14613 | * Global stuff. |
michael@0 | 14614 | */ |
michael@0 | 14615 | |
michael@0 | 14616 | function Context(aClientId) { |
michael@0 | 14617 | this.clientId = aClientId; |
michael@0 | 14618 | |
michael@0 | 14619 | this.Buf = new BufObject(this); |
michael@0 | 14620 | this.Buf.init(); |
michael@0 | 14621 | |
michael@0 | 14622 | this.RIL = new RilObject(this); |
michael@0 | 14623 | this.RIL.initRILState(); |
michael@0 | 14624 | } |
michael@0 | 14625 | Context.prototype = { |
michael@0 | 14626 | clientId: null, |
michael@0 | 14627 | Buf: null, |
michael@0 | 14628 | RIL: null, |
michael@0 | 14629 | |
michael@0 | 14630 | debug: function(aMessage) { |
michael@0 | 14631 | GLOBAL.debug("[" + this.clientId + "] " + aMessage); |
michael@0 | 14632 | } |
michael@0 | 14633 | }; |
michael@0 | 14634 | |
michael@0 | 14635 | (function() { |
michael@0 | 14636 | let lazySymbols = [ |
michael@0 | 14637 | "BerTlvHelper", "BitBufferHelper", "CdmaPDUHelper", |
michael@0 | 14638 | "ComprehensionTlvHelper", "GsmPDUHelper", "ICCContactHelper", |
michael@0 | 14639 | "ICCFileHelper", "ICCIOHelper", "ICCPDUHelper", "ICCRecordHelper", |
michael@0 | 14640 | "ICCUtilsHelper", "RuimRecordHelper", "SimRecordHelper", |
michael@0 | 14641 | "StkCommandParamsFactory", "StkProactiveCmdHelper", |
michael@0 | 14642 | ]; |
michael@0 | 14643 | |
michael@0 | 14644 | for (let i = 0; i < lazySymbols.length; i++) { |
michael@0 | 14645 | let symbol = lazySymbols[i]; |
michael@0 | 14646 | Object.defineProperty(Context.prototype, symbol, { |
michael@0 | 14647 | get: function() { |
michael@0 | 14648 | let real = new GLOBAL[symbol + "Object"](this); |
michael@0 | 14649 | Object.defineProperty(this, symbol, { |
michael@0 | 14650 | value: real, |
michael@0 | 14651 | enumerable: true |
michael@0 | 14652 | }); |
michael@0 | 14653 | return real; |
michael@0 | 14654 | }, |
michael@0 | 14655 | configurable: true, |
michael@0 | 14656 | enumerable: true |
michael@0 | 14657 | }); |
michael@0 | 14658 | } |
michael@0 | 14659 | })(); |
michael@0 | 14660 | |
michael@0 | 14661 | let ContextPool = { |
michael@0 | 14662 | _contexts: [], |
michael@0 | 14663 | |
michael@0 | 14664 | handleRilMessage: function(aClientId, aUint8Array) { |
michael@0 | 14665 | let context = this._contexts[aClientId]; |
michael@0 | 14666 | context.Buf.processIncoming(aUint8Array); |
michael@0 | 14667 | }, |
michael@0 | 14668 | |
michael@0 | 14669 | handleChromeMessage: function(aMessage) { |
michael@0 | 14670 | let clientId = aMessage.rilMessageClientId; |
michael@0 | 14671 | if (clientId != null) { |
michael@0 | 14672 | let context = this._contexts[clientId]; |
michael@0 | 14673 | context.RIL.handleChromeMessage(aMessage); |
michael@0 | 14674 | return; |
michael@0 | 14675 | } |
michael@0 | 14676 | |
michael@0 | 14677 | if (DEBUG) debug("Received global chrome message " + JSON.stringify(aMessage)); |
michael@0 | 14678 | let method = this[aMessage.rilMessageType]; |
michael@0 | 14679 | if (typeof method != "function") { |
michael@0 | 14680 | if (DEBUG) { |
michael@0 | 14681 | debug("Don't know what to do"); |
michael@0 | 14682 | } |
michael@0 | 14683 | return; |
michael@0 | 14684 | } |
michael@0 | 14685 | method.call(this, aMessage); |
michael@0 | 14686 | }, |
michael@0 | 14687 | |
michael@0 | 14688 | setInitialOptions: function(aOptions) { |
michael@0 | 14689 | DEBUG = DEBUG_WORKER || aOptions.debug; |
michael@0 | 14690 | RIL_EMERGENCY_NUMBERS = aOptions.rilEmergencyNumbers; |
michael@0 | 14691 | RIL_CELLBROADCAST_DISABLED = aOptions.cellBroadcastDisabled; |
michael@0 | 14692 | RIL_CLIR_MODE = aOptions.clirMode; |
michael@0 | 14693 | |
michael@0 | 14694 | let quirks = aOptions.quirks; |
michael@0 | 14695 | RILQUIRKS_CALLSTATE_EXTRA_UINT32 = quirks.callstateExtraUint32; |
michael@0 | 14696 | RILQUIRKS_V5_LEGACY = quirks.v5Legacy; |
michael@0 | 14697 | RILQUIRKS_REQUEST_USE_DIAL_EMERGENCY_CALL = quirks.requestUseDialEmergencyCall; |
michael@0 | 14698 | RILQUIRKS_SIM_APP_STATE_EXTRA_FIELDS = quirks.simAppStateExtraFields; |
michael@0 | 14699 | RILQUIRKS_EXTRA_UINT32_2ND_CALL = quirks.extraUint2ndCall; |
michael@0 | 14700 | RILQUIRKS_HAVE_QUERY_ICC_LOCK_RETRY_COUNT = quirks.haveQueryIccLockRetryCount; |
michael@0 | 14701 | RILQUIRKS_SEND_STK_PROFILE_DOWNLOAD = quirks.sendStkProfileDownload; |
michael@0 | 14702 | RILQUIRKS_DATA_REGISTRATION_ON_DEMAND = quirks.dataRegistrationOnDemand; |
michael@0 | 14703 | }, |
michael@0 | 14704 | |
michael@0 | 14705 | registerClient: function(aOptions) { |
michael@0 | 14706 | let clientId = aOptions.clientId; |
michael@0 | 14707 | this._contexts[clientId] = new Context(clientId); |
michael@0 | 14708 | }, |
michael@0 | 14709 | }; |
michael@0 | 14710 | |
michael@0 | 14711 | function onRILMessage(aClientId, aUint8Array) { |
michael@0 | 14712 | ContextPool.handleRilMessage(aClientId, aUint8Array); |
michael@0 | 14713 | } |
michael@0 | 14714 | |
michael@0 | 14715 | onmessage = function onmessage(event) { |
michael@0 | 14716 | ContextPool.handleChromeMessage(event.data); |
michael@0 | 14717 | }; |
michael@0 | 14718 | |
michael@0 | 14719 | onerror = function onerror(event) { |
michael@0 | 14720 | if (DEBUG) debug("onerror" + event.message + "\n"); |
michael@0 | 14721 | }; |