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.
1 /* Copyright 2012 Mozilla Foundation and Mozilla contributors
2 *
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
16 /**
17 * This file implements the RIL worker thread. It communicates with
18 * the main thread to provide a high-level API to the phone's RIL
19 * stack, and with the RIL IPC thread to communicate with the RIL
20 * device itself. These communication channels use message events as
21 * known from Web Workers:
22 *
23 * - postMessage()/"message" events for main thread communication
24 *
25 * - postRILMessage()/"RILMessageEvent" events for RIL IPC thread
26 * communication.
27 *
28 * The two main objects in this file represent individual parts of this
29 * communication chain:
30 *
31 * - RILMessageEvent -> Buf -> RIL -> postMessage() -> nsIRadioInterfaceLayer
32 * - nsIRadioInterfaceLayer -> postMessage() -> RIL -> Buf -> postRILMessage()
33 *
34 * Note: The code below is purposely lean on abstractions to be as lean in
35 * terms of object allocations. As a result, it may look more like C than
36 * JavaScript, and that's intended.
37 */
39 "use strict";
41 importScripts("ril_consts.js");
42 importScripts("resource://gre/modules/workers/require.js");
44 // set to true in ril_consts.js to see debug messages
45 let DEBUG = DEBUG_WORKER;
46 let GLOBAL = this;
48 if (!this.debug) {
49 // Debugging stub that goes nowhere.
50 this.debug = function debug(message) {
51 dump("RIL Worker: " + message + "\n");
52 };
53 }
55 let RIL_CELLBROADCAST_DISABLED;
56 let RIL_CLIR_MODE;
57 let RIL_EMERGENCY_NUMBERS;
58 const DEFAULT_EMERGENCY_NUMBERS = ["112", "911"];
60 // Timeout value for emergency callback mode.
61 const EMERGENCY_CB_MODE_TIMEOUT_MS = 300000; // 5 mins = 300000 ms.
63 const ICC_MAX_LINEAR_FIXED_RECORDS = 0xfe;
65 // MMI match groups
66 const MMI_MATCH_GROUP_FULL_MMI = 1;
67 const MMI_MATCH_GROUP_MMI_PROCEDURE = 2;
68 const MMI_MATCH_GROUP_SERVICE_CODE = 3;
69 const MMI_MATCH_GROUP_SIA = 5;
70 const MMI_MATCH_GROUP_SIB = 7;
71 const MMI_MATCH_GROUP_SIC = 9;
72 const MMI_MATCH_GROUP_PWD_CONFIRM = 11;
73 const MMI_MATCH_GROUP_DIALING_NUMBER = 12;
75 const MMI_MAX_LENGTH_SHORT_CODE = 2;
77 const MMI_END_OF_USSD = "#";
79 // Should match the value we set in dom/telephony/TelephonyCommon.h
80 const OUTGOING_PLACEHOLDER_CALL_INDEX = 0xffffffff;
82 let RILQUIRKS_CALLSTATE_EXTRA_UINT32;
83 // This may change at runtime since in RIL v6 and later, we get the version
84 // number via the UNSOLICITED_RIL_CONNECTED parcel.
85 let RILQUIRKS_V5_LEGACY;
86 let RILQUIRKS_REQUEST_USE_DIAL_EMERGENCY_CALL;
87 let RILQUIRKS_SIM_APP_STATE_EXTRA_FIELDS;
88 // Needed for call-waiting on Peak device
89 let RILQUIRKS_EXTRA_UINT32_2ND_CALL;
90 // On the emulator we support querying the number of lock retries
91 let RILQUIRKS_HAVE_QUERY_ICC_LOCK_RETRY_COUNT;
93 // Ril quirk to Send STK Profile Download
94 let RILQUIRKS_SEND_STK_PROFILE_DOWNLOAD;
96 // Ril quirk to attach data registration on demand.
97 let RILQUIRKS_DATA_REGISTRATION_ON_DEMAND;
99 function BufObject(aContext) {
100 this.context = aContext;
101 }
102 BufObject.prototype = {
103 context: null,
105 mToken: 0,
106 mTokenRequestMap: null,
108 init: function() {
109 this._init();
111 // This gets incremented each time we send out a parcel.
112 this.mToken = 1;
114 // Maps tokens we send out with requests to the request type, so that
115 // when we get a response parcel back, we know what request it was for.
116 this.mTokenRequestMap = new Map();
117 },
119 /**
120 * Process one parcel.
121 */
122 processParcel: function() {
123 let response_type = this.readInt32();
125 let request_type, options;
126 if (response_type == RESPONSE_TYPE_SOLICITED) {
127 let token = this.readInt32();
128 let error = this.readInt32();
130 options = this.mTokenRequestMap.get(token);
131 if (!options) {
132 if (DEBUG) {
133 this.context.debug("Suspicious uninvited request found: " +
134 token + ". Ignored!");
135 }
136 return;
137 }
139 this.mTokenRequestMap.delete(token);
140 request_type = options.rilRequestType;
142 options.rilRequestError = error;
143 if (DEBUG) {
144 this.context.debug("Solicited response for request type " + request_type +
145 ", token " + token + ", error " + error);
146 }
147 } else if (response_type == RESPONSE_TYPE_UNSOLICITED) {
148 request_type = this.readInt32();
149 if (DEBUG) {
150 this.context.debug("Unsolicited response for request type " + request_type);
151 }
152 } else {
153 if (DEBUG) {
154 this.context.debug("Unknown response type: " + response_type);
155 }
156 return;
157 }
159 this.context.RIL.handleParcel(request_type, this.readAvailable, options);
160 },
162 /**
163 * Start a new outgoing parcel.
164 *
165 * @param type
166 * Integer specifying the request type.
167 * @param options [optional]
168 * Object containing information about the request, e.g. the
169 * original main thread message object that led to the RIL request.
170 */
171 newParcel: function(type, options) {
172 if (DEBUG) this.context.debug("New outgoing parcel of type " + type);
174 // We're going to leave room for the parcel size at the beginning.
175 this.outgoingIndex = this.PARCEL_SIZE_SIZE;
176 this.writeInt32(type);
177 this.writeInt32(this.mToken);
179 if (!options) {
180 options = {};
181 }
182 options.rilRequestType = type;
183 options.rilRequestError = null;
184 this.mTokenRequestMap.set(this.mToken, options);
185 this.mToken++;
186 return this.mToken;
187 },
189 simpleRequest: function(type, options) {
190 this.newParcel(type, options);
191 this.sendParcel();
192 },
194 onSendParcel: function(parcel) {
195 postRILMessage(this.context.clientId, parcel);
196 }
197 };
199 (function() {
200 let base = require("resource://gre/modules/workers/worker_buf.js").Buf;
201 for (let p in base) {
202 BufObject.prototype[p] = base[p];
203 }
204 })();
206 /**
207 * The RIL state machine.
208 *
209 * This object communicates with rild via parcels and with the main thread
210 * via post messages. It maintains state about the radio, ICC, calls, etc.
211 * and acts upon state changes accordingly.
212 */
213 function RilObject(aContext) {
214 this.context = aContext;
216 this.currentCalls = {};
217 this.currentConference = {state: null, participants: {}};
218 this.currentDataCalls = {};
219 this._pendingSentSmsMap = {};
220 this.pendingNetworkType = {};
221 this._receivedSmsCbPagesMap = {};
223 // Init properties that are only initialized once.
224 this.v5Legacy = RILQUIRKS_V5_LEGACY;
225 this.cellBroadcastDisabled = RIL_CELLBROADCAST_DISABLED;
226 this.clirMode = RIL_CLIR_MODE;
227 }
228 RilObject.prototype = {
229 context: null,
231 v5Legacy: null,
233 /**
234 * Valid calls.
235 */
236 currentCalls: null,
238 /**
239 * Existing conference call and its participants.
240 */
241 currentConference: null,
243 /**
244 * Existing data calls.
245 */
246 currentDataCalls: null,
248 /**
249 * Outgoing messages waiting for SMS-STATUS-REPORT.
250 */
251 _pendingSentSmsMap: null,
253 /**
254 * Index of the RIL_PREFERRED_NETWORK_TYPE_TO_GECKO. Its value should be
255 * preserved over rild reset.
256 */
257 preferredNetworkType: null,
259 /**
260 * Marker object.
261 */
262 pendingNetworkType: null,
264 /**
265 * Global Cell Broadcast switch.
266 */
267 cellBroadcastDisabled: false,
269 /**
270 * Global CLIR mode settings.
271 */
272 clirMode: CLIR_DEFAULT,
274 /**
275 * Parsed Cell Broadcast search lists.
276 * cellBroadcastConfigs.MMI should be preserved over rild reset.
277 */
278 cellBroadcastConfigs: null,
279 mergedCellBroadcastConfig: null,
281 _receivedSmsCbPagesMap: null,
283 initRILState: function() {
284 /**
285 * One of the RADIO_STATE_* constants.
286 */
287 this.radioState = GECKO_RADIOSTATE_UNAVAILABLE;
289 /**
290 * True if we are on a CDMA phone.
291 */
292 this._isCdma = false;
294 /**
295 * True if we are in emergency callback mode.
296 */
297 this._isInEmergencyCbMode = false;
299 /**
300 * Set when radio is ready but radio tech is unknown. That is, we are
301 * waiting for REQUEST_VOICE_RADIO_TECH
302 */
303 this._waitingRadioTech = false;
305 /**
306 * ICC status. Keeps a reference of the data response to the
307 * getICCStatus request.
308 */
309 this.iccStatus = null;
311 /**
312 * Card state
313 */
314 this.cardState = GECKO_CARDSTATE_UNINITIALIZED;
316 /**
317 * Strings
318 */
319 this.IMEI = null;
320 this.IMEISV = null;
321 this.ESN = null;
322 this.MEID = null;
323 this.SMSC = null;
325 /**
326 * ICC information that is not exposed to Gaia.
327 */
328 this.iccInfoPrivate = {};
330 /**
331 * ICC information, such as MSISDN, MCC, MNC, SPN...etc.
332 */
333 this.iccInfo = {};
335 /**
336 * CDMA specific information. ex. CDMA Network ID, CDMA System ID... etc.
337 */
338 this.cdmaHome = null;
340 /**
341 * Application identification for apps in ICC.
342 */
343 this.aid = null;
345 /**
346 * Application type for apps in ICC.
347 */
348 this.appType = null;
350 this.networkSelectionMode = null;
352 this.voiceRegistrationState = {};
353 this.dataRegistrationState = {};
355 /**
356 * List of strings identifying the network operator.
357 */
358 this.operator = null;
360 /**
361 * String containing the baseband version.
362 */
363 this.basebandVersion = null;
365 // Clean up this.currentCalls: rild might have restarted.
366 for each (let currentCall in this.currentCalls) {
367 delete this.currentCalls[currentCall.callIndex];
368 this._handleDisconnectedCall(currentCall);
369 }
371 // Deactivate this.currentDataCalls: rild might have restarted.
372 for each (let datacall in this.currentDataCalls) {
373 this.deactivateDataCall(datacall);
374 }
376 // Don't clean up this._pendingSentSmsMap
377 // because on rild restart: we may continue with the pending segments.
379 /**
380 * Whether or not the multiple requests in requestNetworkInfo() are currently
381 * being processed
382 */
383 this._processingNetworkInfo = false;
385 /**
386 * Multiple requestNetworkInfo() in a row before finishing the first
387 * request, hence we need to fire requestNetworkInfo() again after
388 * gathering all necessary stuffs. This is to make sure that ril_worker
389 * gets precise network information.
390 */
391 this._needRepollNetworkInfo = false;
393 /**
394 * Pending messages to be send in batch from requestNetworkInfo()
395 */
396 this._pendingNetworkInfo = {rilMessageType: "networkinfochanged"};
398 /**
399 * USSD session flag.
400 * Only one USSD session may exist at a time, and the session is assumed
401 * to exist until:
402 * a) There's a call to cancelUSSD()
403 * b) The implementation sends a UNSOLICITED_ON_USSD with a type code
404 * of "0" (USSD-Notify/no further action) or "2" (session terminated)
405 */
406 this._ussdSession = null;
408 /**
409 * Regular expresion to parse MMI codes.
410 */
411 this._mmiRegExp = null;
413 /**
414 * Cell Broadcast Search Lists.
415 */
416 let cbmmi = this.cellBroadcastConfigs && this.cellBroadcastConfigs.MMI;
417 this.cellBroadcastConfigs = {
418 MMI: cbmmi || null
419 };
420 this.mergedCellBroadcastConfig = null;
421 },
423 /**
424 * Parse an integer from a string, falling back to a default value
425 * if the the provided value is not a string or does not contain a valid
426 * number.
427 *
428 * @param string
429 * String to be parsed.
430 * @param defaultValue [optional]
431 * Default value to be used.
432 * @param radix [optional]
433 * A number that represents the numeral system to be used. Default 10.
434 */
435 parseInt: function(string, defaultValue, radix) {
436 let number = parseInt(string, radix || 10);
437 if (!isNaN(number)) {
438 return number;
439 }
440 if (defaultValue === undefined) {
441 defaultValue = null;
442 }
443 return defaultValue;
444 },
447 /**
448 * Outgoing requests to the RIL. These can be triggered from the
449 * main thread via messages that look like this:
450 *
451 * {rilMessageType: "methodName",
452 * extra: "parameters",
453 * go: "here"}
454 *
455 * So if one of the following methods takes arguments, it takes only one,
456 * an object, which then contains all of the parameters as attributes.
457 * The "@param" documentation is to be interpreted accordingly.
458 */
460 /**
461 * Retrieve the ICC's status.
462 */
463 getICCStatus: function() {
464 this.context.Buf.simpleRequest(REQUEST_GET_SIM_STATUS);
465 },
467 /**
468 * Helper function for unlocking ICC locks.
469 */
470 iccUnlockCardLock: function(options) {
471 switch (options.lockType) {
472 case GECKO_CARDLOCK_PIN:
473 this.enterICCPIN(options);
474 break;
475 case GECKO_CARDLOCK_PIN2:
476 this.enterICCPIN2(options);
477 break;
478 case GECKO_CARDLOCK_PUK:
479 this.enterICCPUK(options);
480 break;
481 case GECKO_CARDLOCK_PUK2:
482 this.enterICCPUK2(options);
483 break;
484 case GECKO_CARDLOCK_NCK:
485 case GECKO_CARDLOCK_NCK1:
486 case GECKO_CARDLOCK_NCK2:
487 case GECKO_CARDLOCK_HNCK:
488 case GECKO_CARDLOCK_CCK:
489 case GECKO_CARDLOCK_SPCK:
490 case GECKO_CARDLOCK_RCCK: // Fall through.
491 case GECKO_CARDLOCK_RSPCK: {
492 let type = GECKO_PERSO_LOCK_TO_CARD_PERSO_LOCK[options.lockType];
493 this.enterDepersonalization(type, options.pin, options);
494 break;
495 }
496 case GECKO_CARDLOCK_NCK_PUK:
497 case GECKO_CARDLOCK_NCK1_PUK:
498 case GECKO_CARDLOCK_NCK2_PUK:
499 case GECKO_CARDLOCK_HNCK_PUK:
500 case GECKO_CARDLOCK_CCK_PUK:
501 case GECKO_CARDLOCK_SPCK_PUK:
502 case GECKO_CARDLOCK_RCCK_PUK: // Fall through.
503 case GECKO_CARDLOCK_RSPCK_PUK: {
504 let type = GECKO_PERSO_LOCK_TO_CARD_PERSO_LOCK[options.lockType];
505 this.enterDepersonalization(type, options.puk, options);
506 break;
507 }
508 default:
509 options.errorMsg = "Unsupported Card Lock.";
510 options.success = false;
511 this.sendChromeMessage(options);
512 }
513 },
515 /**
516 * Enter a PIN to unlock the ICC.
517 *
518 * @param pin
519 * String containing the PIN.
520 * @param [optional] aid
521 * AID value.
522 */
523 enterICCPIN: function(options) {
524 let Buf = this.context.Buf;
525 Buf.newParcel(REQUEST_ENTER_SIM_PIN, options);
526 Buf.writeInt32(this.v5Legacy ? 1 : 2);
527 Buf.writeString(options.pin);
528 if (!this.v5Legacy) {
529 Buf.writeString(options.aid || this.aid);
530 }
531 Buf.sendParcel();
532 },
534 /**
535 * Enter a PIN2 to unlock the ICC.
536 *
537 * @param pin
538 * String containing the PIN2.
539 * @param [optional] aid
540 * AID value.
541 */
542 enterICCPIN2: function(options) {
543 let Buf = this.context.Buf;
544 Buf.newParcel(REQUEST_ENTER_SIM_PIN2, options);
545 Buf.writeInt32(this.v5Legacy ? 1 : 2);
546 Buf.writeString(options.pin);
547 if (!this.v5Legacy) {
548 Buf.writeString(options.aid || this.aid);
549 }
550 Buf.sendParcel();
551 },
553 /**
554 * Requests a network personalization be deactivated.
555 *
556 * @param type
557 * Integer indicating the network personalization be deactivated.
558 * @param password
559 * String containing the password.
560 */
561 enterDepersonalization: function(type, password, options) {
562 let Buf = this.context.Buf;
563 Buf.newParcel(REQUEST_ENTER_NETWORK_DEPERSONALIZATION_CODE, options);
564 Buf.writeInt32(type);
565 Buf.writeString(password);
566 Buf.sendParcel();
567 },
569 /**
570 * Helper function for changing ICC locks.
571 */
572 iccSetCardLock: function(options) {
573 if (options.newPin !== undefined) { // Change PIN lock.
574 switch (options.lockType) {
575 case GECKO_CARDLOCK_PIN:
576 this.changeICCPIN(options);
577 break;
578 case GECKO_CARDLOCK_PIN2:
579 this.changeICCPIN2(options);
580 break;
581 default:
582 options.errorMsg = "Unsupported Card Lock.";
583 options.success = false;
584 this.sendChromeMessage(options);
585 }
586 } else { // Enable/Disable lock.
587 switch (options.lockType) {
588 case GECKO_CARDLOCK_PIN:
589 options.facility = ICC_CB_FACILITY_SIM;
590 options.password = options.pin;
591 break;
592 case GECKO_CARDLOCK_FDN:
593 options.facility = ICC_CB_FACILITY_FDN;
594 options.password = options.pin2;
595 break;
596 default:
597 options.errorMsg = "Unsupported Card Lock.";
598 options.success = false;
599 this.sendChromeMessage(options);
600 return;
601 }
602 options.enabled = options.enabled;
603 options.serviceClass = ICC_SERVICE_CLASS_VOICE |
604 ICC_SERVICE_CLASS_DATA |
605 ICC_SERVICE_CLASS_FAX;
606 this.setICCFacilityLock(options);
607 }
608 },
610 /**
611 * Change the current ICC PIN number.
612 *
613 * @param pin
614 * String containing the old PIN value
615 * @param newPin
616 * String containing the new PIN value
617 * @param [optional] aid
618 * AID value.
619 */
620 changeICCPIN: function(options) {
621 let Buf = this.context.Buf;
622 Buf.newParcel(REQUEST_CHANGE_SIM_PIN, options);
623 Buf.writeInt32(this.v5Legacy ? 2 : 3);
624 Buf.writeString(options.pin);
625 Buf.writeString(options.newPin);
626 if (!this.v5Legacy) {
627 Buf.writeString(options.aid || this.aid);
628 }
629 Buf.sendParcel();
630 },
632 /**
633 * Change the current ICC PIN2 number.
634 *
635 * @param pin
636 * String containing the old PIN2 value
637 * @param newPin
638 * String containing the new PIN2 value
639 * @param [optional] aid
640 * AID value.
641 */
642 changeICCPIN2: function(options) {
643 let Buf = this.context.Buf;
644 Buf.newParcel(REQUEST_CHANGE_SIM_PIN2, options);
645 Buf.writeInt32(this.v5Legacy ? 2 : 3);
646 Buf.writeString(options.pin);
647 Buf.writeString(options.newPin);
648 if (!this.v5Legacy) {
649 Buf.writeString(options.aid || this.aid);
650 }
651 Buf.sendParcel();
652 },
653 /**
654 * Supplies ICC PUK and a new PIN to unlock the ICC.
655 *
656 * @param puk
657 * String containing the PUK value.
658 * @param newPin
659 * String containing the new PIN value.
660 * @param [optional] aid
661 * AID value.
662 */
663 enterICCPUK: function(options) {
664 let Buf = this.context.Buf;
665 Buf.newParcel(REQUEST_ENTER_SIM_PUK, options);
666 Buf.writeInt32(this.v5Legacy ? 2 : 3);
667 Buf.writeString(options.puk);
668 Buf.writeString(options.newPin);
669 if (!this.v5Legacy) {
670 Buf.writeString(options.aid || this.aid);
671 }
672 Buf.sendParcel();
673 },
675 /**
676 * Supplies ICC PUK2 and a new PIN2 to unlock the ICC.
677 *
678 * @param puk
679 * String containing the PUK2 value.
680 * @param newPin
681 * String containing the new PIN2 value.
682 * @param [optional] aid
683 * AID value.
684 */
685 enterICCPUK2: function(options) {
686 let Buf = this.context.Buf;
687 Buf.newParcel(REQUEST_ENTER_SIM_PUK2, options);
688 Buf.writeInt32(this.v5Legacy ? 2 : 3);
689 Buf.writeString(options.puk);
690 Buf.writeString(options.newPin);
691 if (!this.v5Legacy) {
692 Buf.writeString(options.aid || this.aid);
693 }
694 Buf.sendParcel();
695 },
697 /**
698 * Helper function for fetching the state of ICC locks.
699 */
700 iccGetCardLockState: function(options) {
701 switch (options.lockType) {
702 case GECKO_CARDLOCK_PIN:
703 options.facility = ICC_CB_FACILITY_SIM;
704 break;
705 case GECKO_CARDLOCK_FDN:
706 options.facility = ICC_CB_FACILITY_FDN;
707 break;
708 default:
709 options.errorMsg = "Unsupported Card Lock.";
710 options.success = false;
711 this.sendChromeMessage(options);
712 return;
713 }
715 options.password = ""; // For query no need to provide pin.
716 options.serviceClass = ICC_SERVICE_CLASS_VOICE |
717 ICC_SERVICE_CLASS_DATA |
718 ICC_SERVICE_CLASS_FAX;
719 this.queryICCFacilityLock(options);
720 },
722 /**
723 * Helper function for fetching the number of unlock retries of ICC locks.
724 *
725 * We only query the retry count when we're on the emulator. The phones do
726 * not support the request id and their rild doesn't return an error.
727 */
728 iccGetCardLockRetryCount: function(options) {
729 var selCode = {
730 pin: ICC_SEL_CODE_SIM_PIN,
731 puk: ICC_SEL_CODE_SIM_PUK,
732 pin2: ICC_SEL_CODE_SIM_PIN2,
733 puk2: ICC_SEL_CODE_SIM_PUK2,
734 nck: ICC_SEL_CODE_PH_NET_PIN,
735 cck: ICC_SEL_CODE_PH_CORP_PIN,
736 spck: ICC_SEL_CODE_PH_SP_PIN
737 };
739 if (typeof(selCode[options.lockType]) === 'undefined') {
740 /* unknown lock type */
741 options.errorMsg = GECKO_ERROR_GENERIC_FAILURE;
742 options.success = false;
743 this.sendChromeMessage(options);
744 return;
745 }
747 if (RILQUIRKS_HAVE_QUERY_ICC_LOCK_RETRY_COUNT) {
748 /* Only the emulator supports this request, ... */
749 options.selCode = selCode[options.lockType];
750 this.queryICCLockRetryCount(options);
751 } else {
752 /* ... while the phones do not. */
753 options.errorMsg = GECKO_ERROR_REQUEST_NOT_SUPPORTED;
754 options.success = false;
755 this.sendChromeMessage(options);
756 }
757 },
759 /**
760 * Query ICC lock retry count.
761 *
762 * @param selCode
763 * One of ICC_SEL_CODE_*.
764 * @param serviceClass
765 * One of ICC_SERVICE_CLASS_*.
766 */
767 queryICCLockRetryCount: function(options) {
768 let Buf = this.context.Buf;
769 Buf.newParcel(REQUEST_GET_UNLOCK_RETRY_COUNT, options);
770 Buf.writeInt32(1);
771 Buf.writeString(options.selCode);
772 Buf.sendParcel();
773 },
775 /**
776 * Query ICC facility lock.
777 *
778 * @param facility
779 * One of ICC_CB_FACILITY_*.
780 * @param password
781 * Password for the facility, or "" if not required.
782 * @param serviceClass
783 * One of ICC_SERVICE_CLASS_*.
784 * @param [optional] aid
785 * AID value.
786 */
787 queryICCFacilityLock: function(options) {
788 let Buf = this.context.Buf;
789 Buf.newParcel(REQUEST_QUERY_FACILITY_LOCK, options);
790 Buf.writeInt32(this.v5Legacy ? 3 : 4);
791 Buf.writeString(options.facility);
792 Buf.writeString(options.password);
793 Buf.writeString(options.serviceClass.toString());
794 if (!this.v5Legacy) {
795 Buf.writeString(options.aid || this.aid);
796 }
797 Buf.sendParcel();
798 },
800 /**
801 * Set ICC facility lock.
802 *
803 * @param facility
804 * One of ICC_CB_FACILITY_*.
805 * @param enabled
806 * true to enable, false to disable.
807 * @param password
808 * Password for the facility, or "" if not required.
809 * @param serviceClass
810 * One of ICC_SERVICE_CLASS_*.
811 * @param [optional] aid
812 * AID value.
813 */
814 setICCFacilityLock: function(options) {
815 let Buf = this.context.Buf;
816 Buf.newParcel(REQUEST_SET_FACILITY_LOCK, options);
817 Buf.writeInt32(this.v5Legacy ? 4 : 5);
818 Buf.writeString(options.facility);
819 Buf.writeString(options.enabled ? "1" : "0");
820 Buf.writeString(options.password);
821 Buf.writeString(options.serviceClass.toString());
822 if (!this.v5Legacy) {
823 Buf.writeString(options.aid || this.aid);
824 }
825 Buf.sendParcel();
826 },
828 /**
829 * Request an ICC I/O operation.
830 *
831 * See TS 27.007 "restricted SIM" operation, "AT Command +CRSM".
832 * The sequence is in the same order as how libril reads this parcel,
833 * see the struct RIL_SIM_IO_v5 or RIL_SIM_IO_v6 defined in ril.h
834 *
835 * @param command
836 * The I/O command, one of the ICC_COMMAND_* constants.
837 * @param fileId
838 * The file to operate on, one of the ICC_EF_* constants.
839 * @param pathId
840 * String type, check the 'pathid' parameter from TS 27.007 +CRSM.
841 * @param p1, p2, p3
842 * Arbitrary integer parameters for the command.
843 * @param [optional] dataWriter
844 * The function for writing string parameter for the ICC_COMMAND_UPDATE_RECORD.
845 * @param [optional] pin2
846 * String containing the PIN2.
847 * @param [optional] aid
848 * AID value.
849 */
850 iccIO: function(options) {
851 let Buf = this.context.Buf;
852 Buf.newParcel(REQUEST_SIM_IO, options);
853 Buf.writeInt32(options.command);
854 Buf.writeInt32(options.fileId);
855 Buf.writeString(options.pathId);
856 Buf.writeInt32(options.p1);
857 Buf.writeInt32(options.p2);
858 Buf.writeInt32(options.p3);
860 // Write data.
861 if (options.command == ICC_COMMAND_UPDATE_RECORD &&
862 options.dataWriter) {
863 options.dataWriter(options.p3);
864 } else {
865 Buf.writeString(null);
866 }
868 // Write pin2.
869 if (options.command == ICC_COMMAND_UPDATE_RECORD &&
870 options.pin2) {
871 Buf.writeString(options.pin2);
872 } else {
873 Buf.writeString(null);
874 }
876 if (!this.v5Legacy) {
877 Buf.writeString(options.aid || this.aid);
878 }
879 Buf.sendParcel();
880 },
882 /**
883 * Get IMSI.
884 *
885 * @param [optional] aid
886 * AID value.
887 */
888 getIMSI: function(aid) {
889 let Buf = this.context.Buf;
890 if (this.v5Legacy) {
891 Buf.simpleRequest(REQUEST_GET_IMSI);
892 return;
893 }
894 Buf.newParcel(REQUEST_GET_IMSI);
895 Buf.writeInt32(1);
896 Buf.writeString(aid || this.aid);
897 Buf.sendParcel();
898 },
900 /**
901 * Read UICC Phonebook contacts.
902 *
903 * @param contactType
904 * "adn" or "fdn".
905 * @param requestId
906 * Request id from RadioInterfaceLayer.
907 */
908 readICCContacts: function(options) {
909 if (!this.appType) {
910 options.errorMsg = CONTACT_ERR_REQUEST_NOT_SUPPORTED;
911 this.sendChromeMessage(options);
912 return;
913 }
915 this.context.ICCContactHelper.readICCContacts(
916 this.appType,
917 options.contactType,
918 function onsuccess(contacts) {
919 for (let i = 0; i < contacts.length; i++) {
920 let contact = contacts[i];
921 let pbrIndex = contact.pbrIndex || 0;
922 let recordIndex = pbrIndex * ICC_MAX_LINEAR_FIXED_RECORDS + contact.recordId;
923 contact.contactId = this.iccInfo.iccid + recordIndex;
924 }
925 // Reuse 'options' to get 'requestId' and 'contactType'.
926 options.contacts = contacts;
927 this.sendChromeMessage(options);
928 }.bind(this),
929 function onerror(errorMsg) {
930 options.errorMsg = errorMsg;
931 this.sendChromeMessage(options);
932 }.bind(this));
933 },
935 /**
936 * Update UICC Phonebook.
937 *
938 * @param contactType "adn" or "fdn".
939 * @param contact The contact will be updated.
940 * @param pin2 PIN2 is required for updating FDN.
941 * @param requestId Request id from RadioInterfaceLayer.
942 */
943 updateICCContact: function(options) {
944 let onsuccess = function onsuccess() {
945 let recordIndex =
946 contact.pbrIndex * ICC_MAX_LINEAR_FIXED_RECORDS + contact.recordId;
947 contact.contactId = this.iccInfo.iccid + recordIndex;
948 // Reuse 'options' to get 'requestId' and 'contactType'.
949 this.sendChromeMessage(options);
950 }.bind(this);
952 let onerror = function onerror(errorMsg) {
953 options.errorMsg = errorMsg;
954 this.sendChromeMessage(options);
955 }.bind(this);
957 if (!this.appType || !options.contact) {
958 onerror(CONTACT_ERR_REQUEST_NOT_SUPPORTED );
959 return;
960 }
962 let contact = options.contact;
963 let iccid = this.iccInfo.iccid;
964 let isValidRecordId = false;
965 if (typeof contact.contactId === "string" &&
966 contact.contactId.startsWith(iccid)) {
967 let recordIndex = contact.contactId.substring(iccid.length);
968 contact.pbrIndex = Math.floor(recordIndex / ICC_MAX_LINEAR_FIXED_RECORDS);
969 contact.recordId = recordIndex % ICC_MAX_LINEAR_FIXED_RECORDS;
970 isValidRecordId = contact.recordId > 0 && contact.recordId < 0xff;
971 }
973 if (DEBUG) {
974 this.context.debug("Update ICC Contact " + JSON.stringify(contact));
975 }
977 let ICCContactHelper = this.context.ICCContactHelper;
978 // If contact has 'recordId' property, updates corresponding record.
979 // If not, inserts the contact into a free record.
980 if (isValidRecordId) {
981 ICCContactHelper.updateICCContact(
982 this.appType, options.contactType, contact, options.pin2, onsuccess, onerror);
983 } else {
984 ICCContactHelper.addICCContact(
985 this.appType, options.contactType, contact, options.pin2, onsuccess, onerror);
986 }
987 },
989 /**
990 * Request the phone's radio to be enabled or disabled.
991 *
992 * @param enabled
993 * Boolean indicating the desired state.
994 */
995 setRadioEnabled: function(options) {
996 let Buf = this.context.Buf;
997 Buf.newParcel(REQUEST_RADIO_POWER, options);
998 Buf.writeInt32(1);
999 Buf.writeInt32(options.enabled ? 1 : 0);
1000 Buf.sendParcel();
1001 },
1003 /**
1004 * Query call waiting status via MMI.
1005 */
1006 _handleQueryMMICallWaiting: function(options) {
1007 let Buf = this.context.Buf;
1009 function callback(options) {
1010 options.length = Buf.readInt32();
1011 options.enabled = (Buf.readInt32() === 1);
1012 let services = Buf.readInt32();
1013 if (options.enabled) {
1014 options.statusMessage = MMI_SM_KS_SERVICE_ENABLED_FOR;
1015 let serviceClass = [];
1016 for (let serviceClassMask = 1;
1017 serviceClassMask <= ICC_SERVICE_CLASS_MAX;
1018 serviceClassMask <<= 1) {
1019 if ((serviceClassMask & services) !== 0) {
1020 serviceClass.push(MMI_KS_SERVICE_CLASS_MAPPING[serviceClassMask]);
1021 }
1022 }
1023 options.additionalInformation = serviceClass;
1024 } else {
1025 options.statusMessage = MMI_SM_KS_SERVICE_DISABLED;
1026 }
1028 // Prevent DataCloneError when sending chrome messages.
1029 delete options.callback;
1030 this.sendChromeMessage(options);
1031 }
1033 options.callback = callback;
1034 this.queryCallWaiting(options);
1035 },
1037 /**
1038 * Set call waiting status via MMI.
1039 */
1040 _handleSetMMICallWaiting: function(options) {
1041 function callback(options) {
1042 if (options.enabled) {
1043 options.statusMessage = MMI_SM_KS_SERVICE_ENABLED;
1044 } else {
1045 options.statusMessage = MMI_SM_KS_SERVICE_DISABLED;
1046 }
1048 // Prevent DataCloneError when sending chrome messages.
1049 delete options.callback;
1050 this.sendChromeMessage(options);
1051 }
1053 options.callback = callback;
1054 this.setCallWaiting(options);
1055 },
1057 /**
1058 * Query call waiting status.
1059 *
1060 */
1061 queryCallWaiting: function(options) {
1062 let Buf = this.context.Buf;
1063 Buf.newParcel(REQUEST_QUERY_CALL_WAITING, options);
1064 Buf.writeInt32(1);
1065 // As per 3GPP TS 24.083, section 1.6 UE doesn't need to send service
1066 // class parameter in call waiting interrogation to network
1067 Buf.writeInt32(ICC_SERVICE_CLASS_NONE);
1068 Buf.sendParcel();
1069 },
1071 /**
1072 * Set call waiting status.
1073 *
1074 * @param on
1075 * Boolean indicating the desired waiting status.
1076 */
1077 setCallWaiting: function(options) {
1078 let Buf = this.context.Buf;
1079 Buf.newParcel(REQUEST_SET_CALL_WAITING, options);
1080 Buf.writeInt32(2);
1081 Buf.writeInt32(options.enabled ? 1 : 0);
1082 Buf.writeInt32(options.serviceClass !== undefined ?
1083 options.serviceClass : ICC_SERVICE_CLASS_VOICE);
1084 Buf.sendParcel();
1085 },
1087 /**
1088 * Queries current CLIP status.
1089 *
1090 * (MMI request for code "*#30#")
1091 *
1092 */
1093 queryCLIP: function(options) {
1094 this.context.Buf.simpleRequest(REQUEST_QUERY_CLIP, options);
1095 },
1097 /**
1098 * Queries current CLIR status.
1099 *
1100 */
1101 getCLIR: function(options) {
1102 this.context.Buf.simpleRequest(REQUEST_GET_CLIR, options);
1103 },
1105 /**
1106 * Enables or disables the presentation of the calling line identity (CLI) to
1107 * the called party when originating a call.
1108 *
1109 * @param options.clirMode
1110 * Is one of the CLIR_* constants in
1111 * nsIDOMMozMobileConnection interface.
1112 */
1113 setCLIR: function(options) {
1114 if (options) {
1115 this.clirMode = options.clirMode;
1116 }
1117 let Buf = this.context.Buf;
1118 Buf.newParcel(REQUEST_SET_CLIR, options);
1119 Buf.writeInt32(1);
1120 Buf.writeInt32(this.clirMode);
1121 Buf.sendParcel();
1122 },
1124 /**
1125 * Set screen state.
1126 *
1127 * @param on
1128 * Boolean indicating whether the screen should be on or off.
1129 */
1130 setScreenState: function(options) {
1131 let Buf = this.context.Buf;
1132 Buf.newParcel(REQUEST_SCREEN_STATE);
1133 Buf.writeInt32(1);
1134 Buf.writeInt32(options.on ? 1 : 0);
1135 Buf.sendParcel();
1136 },
1138 getVoiceRegistrationState: function() {
1139 this.context.Buf.simpleRequest(REQUEST_VOICE_REGISTRATION_STATE);
1140 },
1142 getVoiceRadioTechnology: function() {
1143 this.context.Buf.simpleRequest(REQUEST_VOICE_RADIO_TECH);
1144 },
1146 getDataRegistrationState: function() {
1147 this.context.Buf.simpleRequest(REQUEST_DATA_REGISTRATION_STATE);
1148 },
1150 getOperator: function() {
1151 this.context.Buf.simpleRequest(REQUEST_OPERATOR);
1152 },
1154 /**
1155 * Set the preferred network type.
1156 *
1157 * @param options An object contains a valid index of
1158 * RIL_PREFERRED_NETWORK_TYPE_TO_GECKO as its `networkType`
1159 * attribute, or undefined to set current preferred network
1160 * type.
1161 */
1162 setPreferredNetworkType: function(options) {
1163 if (options) {
1164 this.preferredNetworkType = options.networkType;
1165 }
1166 if (this.preferredNetworkType == null) {
1167 return;
1168 }
1170 let Buf = this.context.Buf;
1171 Buf.newParcel(REQUEST_SET_PREFERRED_NETWORK_TYPE, options);
1172 Buf.writeInt32(1);
1173 Buf.writeInt32(this.preferredNetworkType);
1174 Buf.sendParcel();
1175 },
1177 /**
1178 * Get the preferred network type.
1179 */
1180 getPreferredNetworkType: function(options) {
1181 this.context.Buf.simpleRequest(REQUEST_GET_PREFERRED_NETWORK_TYPE, options);
1182 },
1184 /**
1185 * Request various states about the network.
1186 */
1187 requestNetworkInfo: function() {
1188 if (this._processingNetworkInfo) {
1189 if (DEBUG) {
1190 this.context.debug("Network info requested, but we're already " +
1191 "requesting network info.");
1192 }
1193 this._needRepollNetworkInfo = true;
1194 return;
1195 }
1197 if (DEBUG) this.context.debug("Requesting network info");
1199 this._processingNetworkInfo = true;
1200 this.getVoiceRegistrationState();
1201 this.getDataRegistrationState(); //TODO only GSM
1202 this.getOperator();
1203 this.getNetworkSelectionMode();
1204 this.getSignalStrength();
1205 },
1207 /**
1208 * Get the available networks
1209 */
1210 getAvailableNetworks: function(options) {
1211 if (DEBUG) this.context.debug("Getting available networks");
1212 let Buf = this.context.Buf;
1213 Buf.newParcel(REQUEST_QUERY_AVAILABLE_NETWORKS, options);
1214 Buf.sendParcel();
1215 },
1217 /**
1218 * Request the radio's network selection mode
1219 */
1220 getNetworkSelectionMode: function() {
1221 if (DEBUG) this.context.debug("Getting network selection mode");
1222 this.context.Buf.simpleRequest(REQUEST_QUERY_NETWORK_SELECTION_MODE);
1223 },
1225 /**
1226 * Tell the radio to automatically choose a voice/data network
1227 */
1228 selectNetworkAuto: function(options) {
1229 if (DEBUG) this.context.debug("Setting automatic network selection");
1230 this.context.Buf.simpleRequest(REQUEST_SET_NETWORK_SELECTION_AUTOMATIC, options);
1231 },
1233 /**
1234 * Set the roaming preference mode
1235 */
1236 setRoamingPreference: function(options) {
1237 let roamingMode = CDMA_ROAMING_PREFERENCE_TO_GECKO.indexOf(options.mode);
1239 if (roamingMode === -1) {
1240 options.errorMsg = GECKO_ERROR_INVALID_PARAMETER;
1241 this.sendChromeMessage(options);
1242 return;
1243 }
1245 let Buf = this.context.Buf;
1246 Buf.newParcel(REQUEST_CDMA_SET_ROAMING_PREFERENCE, options);
1247 Buf.writeInt32(1);
1248 Buf.writeInt32(roamingMode);
1249 Buf.sendParcel();
1250 },
1252 /**
1253 * Get the roaming preference mode
1254 */
1255 queryRoamingPreference: function(options) {
1256 this.context.Buf.simpleRequest(REQUEST_CDMA_QUERY_ROAMING_PREFERENCE, options);
1257 },
1259 /**
1260 * Set the voice privacy mode
1261 */
1262 setVoicePrivacyMode: function(options) {
1263 let Buf = this.context.Buf;
1264 Buf.newParcel(REQUEST_CDMA_SET_PREFERRED_VOICE_PRIVACY_MODE, options);
1265 Buf.writeInt32(1);
1266 Buf.writeInt32(options.enabled ? 1 : 0);
1267 Buf.sendParcel();
1268 },
1270 /**
1271 * Get the voice privacy mode
1272 */
1273 queryVoicePrivacyMode: function(options) {
1274 this.context.Buf.simpleRequest(REQUEST_CDMA_QUERY_PREFERRED_VOICE_PRIVACY_MODE, options);
1275 },
1277 /**
1278 * Open Logical UICC channel (aid) for Secure Element access
1279 */
1280 iccOpenChannel: function(options) {
1281 if (DEBUG) {
1282 this.context.debug("iccOpenChannel: " + JSON.stringify(options));
1283 }
1285 let Buf = this.context.Buf;
1286 Buf.newParcel(REQUEST_SIM_OPEN_CHANNEL, options);
1287 Buf.writeString(options.aid);
1288 Buf.sendParcel();
1289 },
1291 /**
1292 * Exchange APDU data on an open Logical UICC channel
1293 */
1294 iccExchangeAPDU: function(options) {
1295 if (DEBUG) this.context.debug("iccExchangeAPDU: " + JSON.stringify(options));
1297 let cla = options.apdu.cla;
1298 let command = options.apdu.command;
1299 let channel = options.channel;
1300 let path = options.apdu.path || "";
1301 let data = options.apdu.data || "";
1302 let data2 = options.apdu.data2 || "";
1304 let p1 = options.apdu.p1;
1305 let p2 = options.apdu.p2;
1306 let p3 = options.apdu.p3; // Extra
1308 let Buf = this.context.Buf;
1309 Buf.newParcel(REQUEST_SIM_ACCESS_CHANNEL, options);
1310 Buf.writeInt32(cla);
1311 Buf.writeInt32(command);
1312 Buf.writeInt32(channel);
1313 Buf.writeString(path); // path
1314 Buf.writeInt32(p1);
1315 Buf.writeInt32(p2);
1316 Buf.writeInt32(p3);
1317 Buf.writeString(data); // generic data field.
1318 Buf.writeString(data2);
1320 Buf.sendParcel();
1321 },
1323 /**
1324 * Close Logical UICC channel
1325 */
1326 iccCloseChannel: function(options) {
1327 if (DEBUG) this.context.debug("iccCloseChannel: " + JSON.stringify(options));
1329 let Buf = this.context.Buf;
1330 Buf.newParcel(REQUEST_SIM_CLOSE_CHANNEL, options);
1331 Buf.writeInt32(1);
1332 Buf.writeInt32(options.channel);
1333 Buf.sendParcel();
1334 },
1336 /**
1337 * Tell the radio to choose a specific voice/data network
1338 */
1339 selectNetwork: function(options) {
1340 if (DEBUG) {
1341 this.context.debug("Setting manual network selection: " +
1342 options.mcc + ", " + options.mnc);
1343 }
1345 let numeric = (options.mcc && options.mnc) ? options.mcc + options.mnc : null;
1346 let Buf = this.context.Buf;
1347 Buf.newParcel(REQUEST_SET_NETWORK_SELECTION_MANUAL, options);
1348 Buf.writeString(numeric);
1349 Buf.sendParcel();
1350 },
1352 /**
1353 * Get current calls.
1354 */
1355 getCurrentCalls: function() {
1356 this.context.Buf.simpleRequest(REQUEST_GET_CURRENT_CALLS);
1357 },
1359 /**
1360 * Get the signal strength.
1361 */
1362 getSignalStrength: function() {
1363 this.context.Buf.simpleRequest(REQUEST_SIGNAL_STRENGTH);
1364 },
1366 getIMEI: function(options) {
1367 this.context.Buf.simpleRequest(REQUEST_GET_IMEI, options);
1368 },
1370 getIMEISV: function() {
1371 this.context.Buf.simpleRequest(REQUEST_GET_IMEISV);
1372 },
1374 getDeviceIdentity: function() {
1375 this.context.Buf.simpleRequest(REQUEST_DEVICE_IDENTITY);
1376 },
1378 getBasebandVersion: function() {
1379 this.context.Buf.simpleRequest(REQUEST_BASEBAND_VERSION);
1380 },
1382 sendExitEmergencyCbModeRequest: function(options) {
1383 this.context.Buf.simpleRequest(REQUEST_EXIT_EMERGENCY_CALLBACK_MODE, options);
1384 },
1386 getCdmaSubscription: function() {
1387 this.context.Buf.simpleRequest(REQUEST_CDMA_SUBSCRIPTION);
1388 },
1390 exitEmergencyCbMode: function(options) {
1391 // The function could be called by an API from RadioInterfaceLayer or by
1392 // ril_worker itself. From ril_worker, we won't pass the parameter
1393 // 'options'. In this case, it is marked as internal.
1394 if (!options) {
1395 options = {internal: true};
1396 }
1397 this._cancelEmergencyCbModeTimeout();
1398 this.sendExitEmergencyCbModeRequest(options);
1399 },
1401 /**
1402 * Cache the request for making an emergency call when radio is off. The
1403 * request shall include two types of callback functions. 'callback' is
1404 * called when radio is ready, and 'onerror' is called when turning radio
1405 * on fails.
1406 */
1407 cachedDialRequest : null,
1409 /**
1410 * Dial the phone.
1411 *
1412 * @param number
1413 * String containing the number to dial.
1414 * @param clirMode
1415 * Integer for showing/hidding the caller Id to the called party.
1416 * @param uusInfo
1417 * Integer doing something XXX TODO
1418 */
1419 dial: function(options) {
1420 let onerror = (function onerror(options, errorMsg) {
1421 options.success = false;
1422 options.errorMsg = errorMsg;
1423 this.sendChromeMessage(options);
1424 }).bind(this, options);
1426 if (this._isEmergencyNumber(options.number)) {
1427 this.dialEmergencyNumber(options, onerror);
1428 } else {
1429 if (!this._isCdma) {
1430 // TODO: Both dial() and sendMMI() functions should be unified at some
1431 // point in the future. In the mean time we handle temporary CLIR MMI
1432 // commands through the dial() function. Please see bug 889737.
1433 let mmi = this._parseMMI(options.number);
1434 if (mmi && this._isTemporaryModeCLIR(mmi)) {
1435 options.number = mmi.dialNumber;
1436 // In temporary mode, MMI_PROCEDURE_ACTIVATION means allowing CLI
1437 // presentation, i.e. CLIR_SUPPRESSION. See TS 22.030, Annex B.
1438 options.clirMode = mmi.procedure == MMI_PROCEDURE_ACTIVATION ?
1439 CLIR_SUPPRESSION : CLIR_INVOCATION;
1440 }
1441 }
1442 this.dialNonEmergencyNumber(options, onerror);
1443 }
1444 },
1446 dialNonEmergencyNumber: function(options, onerror) {
1447 if (this.radioState == GECKO_RADIOSTATE_OFF) {
1448 // Notify error in establishing the call without radio.
1449 onerror(GECKO_ERROR_RADIO_NOT_AVAILABLE);
1450 return;
1451 }
1453 if (this.voiceRegistrationState.emergencyCallsOnly ||
1454 options.isDialEmergency) {
1455 onerror(RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[CALL_FAIL_UNOBTAINABLE_NUMBER]);
1456 return;
1457 }
1459 // Exit emergency callback mode when user dial a non-emergency call.
1460 if (this._isInEmergencyCbMode) {
1461 this.exitEmergencyCbMode();
1462 }
1464 if (this._isCdma && Object.keys(this.currentCalls).length == 1) {
1465 // Make a Cdma 3way call.
1466 options.featureStr = options.number;
1467 this.sendCdmaFlashCommand(options);
1468 } else {
1469 options.request = REQUEST_DIAL;
1470 this.sendDialRequest(options);
1471 }
1472 },
1474 dialEmergencyNumber: function(options, onerror) {
1475 options.request = RILQUIRKS_REQUEST_USE_DIAL_EMERGENCY_CALL ?
1476 REQUEST_DIAL_EMERGENCY_CALL : REQUEST_DIAL;
1477 if (this.radioState == GECKO_RADIOSTATE_OFF) {
1478 if (DEBUG) {
1479 this.context.debug("Automatically enable radio for an emergency call.");
1480 }
1482 if (!this.cachedDialRequest) {
1483 this.cachedDialRequest = {};
1484 }
1485 this.cachedDialRequest.onerror = onerror;
1486 this.cachedDialRequest.callback = this.sendDialRequest.bind(this, options);
1487 this.setRadioEnabled({enabled: true});
1488 return;
1489 }
1491 if (this._isCdma && Object.keys(this.currentCalls).length == 1) {
1492 // Make a Cdma 3way call.
1493 options.featureStr = options.number;
1494 this.sendCdmaFlashCommand(options);
1495 } else {
1496 this.sendDialRequest(options);
1497 }
1498 },
1500 sendDialRequest: function(options) {
1501 // Always succeed.
1502 options.success = true;
1503 this.sendChromeMessage(options);
1504 this._createPendingOutgoingCall(options);
1506 let Buf = this.context.Buf;
1507 Buf.newParcel(options.request, options);
1508 Buf.writeString(options.number);
1509 Buf.writeInt32(options.clirMode || 0);
1510 Buf.writeInt32(options.uusInfo || 0);
1511 // TODO Why do we need this extra 0? It was put it in to make this
1512 // match the format of the binary message.
1513 Buf.writeInt32(0);
1514 Buf.sendParcel();
1515 },
1517 sendCdmaFlashCommand: function(options) {
1518 let Buf = this.context.Buf;
1519 options.isCdma = true;
1520 options.request = REQUEST_CDMA_FLASH;
1521 Buf.newParcel(options.request, options);
1522 Buf.writeString(options.featureStr);
1523 Buf.sendParcel();
1524 },
1526 /**
1527 * Hang up all calls
1528 */
1529 hangUpAll: function() {
1530 for (let callIndex in this.currentCalls) {
1531 this.hangUp({callIndex: callIndex});
1532 }
1533 },
1535 /**
1536 * Hang up the phone.
1537 *
1538 * @param callIndex
1539 * Call index (1-based) as reported by REQUEST_GET_CURRENT_CALLS.
1540 */
1541 hangUp: function(options) {
1542 let call = this.currentCalls[options.callIndex];
1543 if (!call) {
1544 return;
1545 }
1547 let callIndex = call.callIndex;
1548 if (callIndex === OUTGOING_PLACEHOLDER_CALL_INDEX) {
1549 if (DEBUG) this.context.debug("Hang up pending outgoing call.");
1550 this._removeVoiceCall(call, GECKO_CALL_ERROR_NORMAL_CALL_CLEARING);
1551 return;
1552 }
1554 call.hangUpLocal = true;
1556 if (call.state === CALL_STATE_HOLDING) {
1557 this.sendHangUpBackgroundRequest(callIndex);
1558 } else {
1559 this.sendHangUpRequest(callIndex);
1560 }
1561 },
1563 sendHangUpRequest: function(callIndex) {
1564 let Buf = this.context.Buf;
1565 Buf.newParcel(REQUEST_HANGUP);
1566 Buf.writeInt32(1);
1567 Buf.writeInt32(callIndex);
1568 Buf.sendParcel();
1569 },
1571 sendHangUpBackgroundRequest: function(callIndex) {
1572 let Buf = this.context.Buf;
1573 Buf.simpleRequest(REQUEST_HANGUP_WAITING_OR_BACKGROUND);
1574 },
1576 /**
1577 * Mute or unmute the radio.
1578 *
1579 * @param mute
1580 * Boolean to indicate whether to mute or unmute the radio.
1581 */
1582 setMute: function(options) {
1583 let Buf = this.context.Buf;
1584 Buf.newParcel(REQUEST_SET_MUTE);
1585 Buf.writeInt32(1);
1586 Buf.writeInt32(options.muted ? 1 : 0);
1587 Buf.sendParcel();
1588 },
1590 /**
1591 * Answer an incoming/waiting call.
1592 *
1593 * @param callIndex
1594 * Call index of the call to answer.
1595 */
1596 answerCall: function(options) {
1597 // Check for races. Since we dispatched the incoming/waiting call
1598 // notification the incoming/waiting call may have changed. The main
1599 // thread thinks that it is answering the call with the given index,
1600 // so only answer if that is still incoming/waiting.
1601 let call = this.currentCalls[options.callIndex];
1602 if (!call) {
1603 return;
1604 }
1606 let Buf = this.context.Buf;
1607 switch (call.state) {
1608 case CALL_STATE_INCOMING:
1609 Buf.simpleRequest(REQUEST_ANSWER);
1610 break;
1611 case CALL_STATE_WAITING:
1612 // Answer the waiting (second) call, and hold the first call.
1613 Buf.simpleRequest(REQUEST_SWITCH_WAITING_OR_HOLDING_AND_ACTIVE);
1614 break;
1615 }
1616 },
1618 /**
1619 * Reject an incoming/waiting call.
1620 *
1621 * @param callIndex
1622 * Call index of the call to reject.
1623 */
1624 rejectCall: function(options) {
1625 // Check for races. Since we dispatched the incoming/waiting call
1626 // notification the incoming/waiting call may have changed. The main
1627 // thread thinks that it is rejecting the call with the given index,
1628 // so only reject if that is still incoming/waiting.
1629 let call = this.currentCalls[options.callIndex];
1630 if (!call) {
1631 return;
1632 }
1634 call.hangUpLocal = true;
1636 let Buf = this.context.Buf;
1637 if (this._isCdma) {
1638 // AT+CHLD=0 means "release held or UDUB."
1639 Buf.simpleRequest(REQUEST_HANGUP_WAITING_OR_BACKGROUND);
1640 return;
1641 }
1643 switch (call.state) {
1644 case CALL_STATE_INCOMING:
1645 Buf.simpleRequest(REQUEST_UDUB);
1646 break;
1647 case CALL_STATE_WAITING:
1648 // Reject the waiting (second) call, and remain the first call.
1649 Buf.simpleRequest(REQUEST_HANGUP_WAITING_OR_BACKGROUND);
1650 break;
1651 }
1652 },
1654 holdCall: function(options) {
1655 let call = this.currentCalls[options.callIndex];
1656 if (!call) {
1657 options.errorMsg = GECKO_ERROR_GENERIC_FAILURE;
1658 options.success = false;
1659 this.sendChromeMessage(options);
1660 return;
1661 }
1663 let Buf = this.context.Buf;
1664 if (this._isCdma) {
1665 options.featureStr = "";
1666 this.sendCdmaFlashCommand(options);
1667 } else if (call.state == CALL_STATE_ACTIVE) {
1668 Buf.simpleRequest(REQUEST_SWITCH_WAITING_OR_HOLDING_AND_ACTIVE, options);
1669 }
1670 },
1672 resumeCall: function(options) {
1673 let call = this.currentCalls[options.callIndex];
1674 if (!call) {
1675 options.errorMsg = GECKO_ERROR_GENERIC_FAILURE;
1676 options.success = false;
1677 this.sendChromeMessage(options);
1678 return;
1679 }
1681 let Buf = this.context.Buf;
1682 if (this._isCdma) {
1683 options.featureStr = "";
1684 this.sendCdmaFlashCommand(options);
1685 } else if (call.state == CALL_STATE_HOLDING) {
1686 Buf.simpleRequest(REQUEST_SWITCH_WAITING_OR_HOLDING_AND_ACTIVE, options);
1687 }
1688 },
1690 // Flag indicating whether user has requested making a conference call.
1691 _hasConferenceRequest: false,
1693 conferenceCall: function(options) {
1694 let Buf = this.context.Buf;
1695 if (this._isCdma) {
1696 options.featureStr = "";
1697 this.sendCdmaFlashCommand(options);
1698 } else {
1699 this._hasConferenceRequest = true;
1700 Buf.simpleRequest(REQUEST_CONFERENCE, options);
1701 }
1702 },
1704 separateCall: function(options) {
1705 let call = this.currentCalls[options.callIndex];
1706 if (!call) {
1707 options.errorName = "removeError";
1708 options.errorMsg = GECKO_ERROR_GENERIC_FAILURE;
1709 options.success = false;
1710 this.sendChromeMessage(options);
1711 return;
1712 }
1714 let Buf = this.context.Buf;
1715 if (this._isCdma) {
1716 options.featureStr = "";
1717 this.sendCdmaFlashCommand(options);
1718 } else {
1719 Buf.newParcel(REQUEST_SEPARATE_CONNECTION, options);
1720 Buf.writeInt32(1);
1721 Buf.writeInt32(options.callIndex);
1722 Buf.sendParcel();
1723 }
1724 },
1726 holdConference: function() {
1727 if (this._isCdma) {
1728 return;
1729 }
1731 this.context.Buf.simpleRequest(REQUEST_SWITCH_WAITING_OR_HOLDING_AND_ACTIVE);
1732 },
1734 resumeConference: function() {
1735 if (this._isCdma) {
1736 return;
1737 }
1739 this.context.Buf.simpleRequest(REQUEST_SWITCH_WAITING_OR_HOLDING_AND_ACTIVE);
1740 },
1742 /**
1743 * Send an SMS.
1744 *
1745 * The `options` parameter object should contain the following attributes:
1746 *
1747 * @param number
1748 * String containing the recipient number.
1749 * @param body
1750 * String containing the message text.
1751 * @param envelopeId
1752 * Numeric value identifying the sms request.
1753 */
1754 sendSMS: function(options) {
1755 options.langIndex = options.langIndex || PDU_NL_IDENTIFIER_DEFAULT;
1756 options.langShiftIndex = options.langShiftIndex || PDU_NL_IDENTIFIER_DEFAULT;
1758 if (!options.retryCount) {
1759 options.retryCount = 0;
1760 }
1762 if (!options.segmentSeq) {
1763 // Fist segment to send
1764 options.segmentSeq = 1;
1765 options.body = options.segments[0].body;
1766 options.encodedBodyLength = options.segments[0].encodedBodyLength;
1767 }
1769 let Buf = this.context.Buf;
1770 if (this._isCdma) {
1771 Buf.newParcel(REQUEST_CDMA_SEND_SMS, options);
1772 this.context.CdmaPDUHelper.writeMessage(options);
1773 } else {
1774 Buf.newParcel(REQUEST_SEND_SMS, options);
1775 Buf.writeInt32(2);
1776 Buf.writeString(options.SMSC);
1777 this.context.GsmPDUHelper.writeMessage(options);
1778 }
1779 Buf.sendParcel();
1780 },
1782 /**
1783 * Acknowledge the receipt and handling of an SMS.
1784 *
1785 * @param success
1786 * Boolean indicating whether the message was successfuly handled.
1787 * @param cause
1788 * SMS_* constant indicating the reason for unsuccessful handling.
1789 */
1790 acknowledgeGsmSms: function(success, cause) {
1791 let Buf = this.context.Buf;
1792 Buf.newParcel(REQUEST_SMS_ACKNOWLEDGE);
1793 Buf.writeInt32(2);
1794 Buf.writeInt32(success ? 1 : 0);
1795 Buf.writeInt32(cause);
1796 Buf.sendParcel();
1797 },
1799 /**
1800 * Acknowledge the receipt and handling of an SMS.
1801 *
1802 * @param success
1803 * Boolean indicating whether the message was successfuly handled.
1804 */
1805 ackSMS: function(options) {
1806 if (options.result == PDU_FCS_RESERVED) {
1807 return;
1808 }
1809 if (this._isCdma) {
1810 this.acknowledgeCdmaSms(options.result == PDU_FCS_OK, options.result);
1811 } else {
1812 this.acknowledgeGsmSms(options.result == PDU_FCS_OK, options.result);
1813 }
1814 },
1816 /**
1817 * Acknowledge the receipt and handling of a CDMA SMS.
1818 *
1819 * @param success
1820 * Boolean indicating whether the message was successfuly handled.
1821 * @param cause
1822 * SMS_* constant indicating the reason for unsuccessful handling.
1823 */
1824 acknowledgeCdmaSms: function(success, cause) {
1825 let Buf = this.context.Buf;
1826 Buf.newParcel(REQUEST_CDMA_SMS_ACKNOWLEDGE);
1827 Buf.writeInt32(success ? 0 : 1);
1828 Buf.writeInt32(cause);
1829 Buf.sendParcel();
1830 },
1832 /**
1833 * Update received MWI into EF_MWIS.
1834 */
1835 updateMwis: function(options) {
1836 if (this.context.ICCUtilsHelper.isICCServiceAvailable("MWIS")) {
1837 this.context.SimRecordHelper.updateMWIS(options.mwi);
1838 }
1839 },
1841 setCellBroadcastDisabled: function(options) {
1842 this.cellBroadcastDisabled = options.disabled;
1844 // If |this.mergedCellBroadcastConfig| is null, either we haven't finished
1845 // reading required SIM files, or no any channel is ever configured. In
1846 // the former case, we'll call |this.updateCellBroadcastConfig()| later
1847 // with correct configs; in the latter case, we don't bother resetting CB
1848 // to disabled again.
1849 if (this.mergedCellBroadcastConfig) {
1850 this.updateCellBroadcastConfig();
1851 }
1852 },
1854 setCellBroadcastSearchList: function(options) {
1855 let getSearchListStr = function(aSearchList) {
1856 if (typeof aSearchList === "string" || aSearchList instanceof String) {
1857 return aSearchList;
1858 }
1860 // TODO: Set search list for CDMA/GSM individually. Bug 990926
1861 let prop = this._isCdma ? "cdma" : "gsm";
1863 return aSearchList && aSearchList[prop];
1864 }.bind(this);
1866 try {
1867 let str = getSearchListStr(options.searchList);
1868 this.cellBroadcastConfigs.MMI = this._convertCellBroadcastSearchList(str);
1869 options.success = true;
1870 } catch (e) {
1871 if (DEBUG) {
1872 this.context.debug("Invalid Cell Broadcast search list: " + e);
1873 }
1874 options.success = false;
1875 }
1877 this.sendChromeMessage(options);
1878 if (!options.success) {
1879 return;
1880 }
1882 this._mergeAllCellBroadcastConfigs();
1883 },
1885 updateCellBroadcastConfig: function() {
1886 let activate = !this.cellBroadcastDisabled &&
1887 (this.mergedCellBroadcastConfig != null) &&
1888 (this.mergedCellBroadcastConfig.length > 0);
1889 if (activate) {
1890 this.setSmsBroadcastConfig(this.mergedCellBroadcastConfig);
1891 } else {
1892 // It's unnecessary to set config first if we're deactivating.
1893 this.setSmsBroadcastActivation(false);
1894 }
1895 },
1897 setGsmSmsBroadcastConfig: function(config) {
1898 let Buf = this.context.Buf;
1899 Buf.newParcel(REQUEST_GSM_SET_BROADCAST_SMS_CONFIG);
1901 let numConfigs = config ? config.length / 2 : 0;
1902 Buf.writeInt32(numConfigs);
1903 for (let i = 0; i < config.length;) {
1904 Buf.writeInt32(config[i++]);
1905 Buf.writeInt32(config[i++]);
1906 Buf.writeInt32(0x00);
1907 Buf.writeInt32(0xFF);
1908 Buf.writeInt32(1);
1909 }
1911 Buf.sendParcel();
1912 },
1914 /**
1915 * Send CDMA SMS broadcast config.
1916 *
1917 * @see 3GPP2 C.R1001 Sec. 9.2 and 9.3
1918 */
1919 setCdmaSmsBroadcastConfig: function(config) {
1920 let Buf = this.context.Buf;
1921 // |config| is an array of half-closed range: [[from, to), [from, to), ...].
1922 // It will be further decomposed, ex: [1, 4) => 1, 2, 3.
1923 Buf.newParcel(REQUEST_CDMA_SET_BROADCAST_SMS_CONFIG);
1925 let numConfigs = 0;
1926 for (let i = 0; i < config.length; i += 2) {
1927 numConfigs += (config[i+1] - config[i]);
1928 }
1930 Buf.writeInt32(numConfigs);
1931 for (let i = 0; i < config.length;) {
1932 let begin = config[i++];
1933 let end = config[i++];
1935 for (let j = begin; j < end; ++j) {
1936 Buf.writeInt32(j);
1937 Buf.writeInt32(0); // Language Indicator: Unknown or unspecified.
1938 Buf.writeInt32(1);
1939 }
1940 }
1942 Buf.sendParcel();
1943 },
1945 setSmsBroadcastConfig: function(config) {
1946 if (this._isCdma) {
1947 this.setCdmaSmsBroadcastConfig(config);
1948 } else {
1949 this.setGsmSmsBroadcastConfig(config);
1950 }
1951 },
1953 setSmsBroadcastActivation: function(activate) {
1954 let parcelType = this._isCdma ? REQUEST_CDMA_SMS_BROADCAST_ACTIVATION :
1955 REQUEST_GSM_SMS_BROADCAST_ACTIVATION;
1956 let Buf = this.context.Buf;
1957 Buf.newParcel(parcelType);
1958 Buf.writeInt32(1);
1959 // See hardware/ril/include/telephony/ril.h, 0 - Activate, 1 - Turn off.
1960 Buf.writeInt32(activate ? 0 : 1);
1961 Buf.sendParcel();
1962 },
1964 /**
1965 * Start a DTMF Tone.
1966 *
1967 * @param dtmfChar
1968 * DTMF signal to send, 0-9, *, +
1969 */
1970 startTone: function(options) {
1971 let Buf = this.context.Buf;
1972 Buf.newParcel(REQUEST_DTMF_START);
1973 Buf.writeString(options.dtmfChar);
1974 Buf.sendParcel();
1975 },
1977 stopTone: function() {
1978 this.context.Buf.simpleRequest(REQUEST_DTMF_STOP);
1979 },
1981 /**
1982 * Send a DTMF tone.
1983 *
1984 * @param dtmfChar
1985 * DTMF signal to send, 0-9, *, +
1986 */
1987 sendTone: function(options) {
1988 let Buf = this.context.Buf;
1989 Buf.newParcel(REQUEST_DTMF);
1990 Buf.writeString(options.dtmfChar);
1991 Buf.sendParcel();
1992 },
1994 /**
1995 * Get the Short Message Service Center address.
1996 */
1997 getSmscAddress: function(options) {
1998 if (!this.SMSC) {
1999 this.context.Buf.simpleRequest(REQUEST_GET_SMSC_ADDRESS, options);
2000 return;
2001 }
2003 if (!options || options.rilMessageType !== "getSmscAddress") {
2004 return;
2005 }
2007 options.smscAddress = this.SMSC;
2008 options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError];
2009 this.sendChromeMessage(options);
2010 },
2012 /**
2013 * Set the Short Message Service Center address.
2014 *
2015 * @param smscAddress
2016 * Short Message Service Center address in PDU format.
2017 */
2018 setSmscAddress: function(options) {
2019 let Buf = this.context.Buf;
2020 Buf.newParcel(REQUEST_SET_SMSC_ADDRESS, options);
2021 Buf.writeString(options.smscAddress);
2022 Buf.sendParcel();
2023 },
2025 /**
2026 * Setup a data call.
2027 *
2028 * @param radioTech
2029 * Integer to indicate radio technology.
2030 * DATACALL_RADIOTECHNOLOGY_CDMA => CDMA.
2031 * DATACALL_RADIOTECHNOLOGY_GSM => GSM.
2032 * @param apn
2033 * String containing the name of the APN to connect to.
2034 * @param user
2035 * String containing the username for the APN.
2036 * @param passwd
2037 * String containing the password for the APN.
2038 * @param chappap
2039 * Integer containing CHAP/PAP auth type.
2040 * DATACALL_AUTH_NONE => PAP and CHAP is never performed.
2041 * DATACALL_AUTH_PAP => PAP may be performed.
2042 * DATACALL_AUTH_CHAP => CHAP may be performed.
2043 * DATACALL_AUTH_PAP_OR_CHAP => PAP / CHAP may be performed.
2044 * @param pdptype
2045 * String containing PDP type to request. ("IP", "IPV6", ...)
2046 */
2047 setupDataCall: function(options) {
2048 // From ./hardware/ril/include/telephony/ril.h:
2049 // ((const char **)data)[0] Radio technology to use: 0-CDMA, 1-GSM/UMTS, 2...
2050 // for values above 2 this is RIL_RadioTechnology + 2.
2051 //
2052 // From frameworks/base/telephony/java/com/android/internal/telephony/DataConnection.java:
2053 // if the mRilVersion < 6, radio technology must be GSM/UMTS or CDMA.
2054 // Otherwise, it must be + 2
2055 //
2056 // See also bug 901232 and 867873
2057 let radioTech;
2058 if (this.v5Legacy) {
2059 radioTech = this._isCdma ? DATACALL_RADIOTECHNOLOGY_CDMA
2060 : DATACALL_RADIOTECHNOLOGY_GSM;
2061 } else {
2062 radioTech = options.radioTech + 2;
2063 }
2064 let Buf = this.context.Buf;
2065 let token = Buf.newParcel(REQUEST_SETUP_DATA_CALL, options);
2066 Buf.writeInt32(7);
2067 Buf.writeString(radioTech.toString());
2068 Buf.writeString(DATACALL_PROFILE_DEFAULT.toString());
2069 Buf.writeString(options.apn);
2070 Buf.writeString(options.user);
2071 Buf.writeString(options.passwd);
2072 Buf.writeString(options.chappap.toString());
2073 Buf.writeString(options.pdptype);
2074 Buf.sendParcel();
2075 return token;
2076 },
2078 /**
2079 * Deactivate a data call.
2080 *
2081 * @param cid
2082 * String containing CID.
2083 * @param reason
2084 * One of DATACALL_DEACTIVATE_* constants.
2085 */
2086 deactivateDataCall: function(options) {
2087 let datacall = this.currentDataCalls[options.cid];
2088 if (!datacall) {
2089 return;
2090 }
2092 let Buf = this.context.Buf;
2093 Buf.newParcel(REQUEST_DEACTIVATE_DATA_CALL, options);
2094 Buf.writeInt32(2);
2095 Buf.writeString(options.cid);
2096 Buf.writeString(options.reason || DATACALL_DEACTIVATE_NO_REASON);
2097 Buf.sendParcel();
2099 datacall.state = GECKO_NETWORK_STATE_DISCONNECTING;
2100 this.sendChromeMessage(datacall);
2101 },
2103 /**
2104 * Get a list of data calls.
2105 */
2106 getDataCallList: function() {
2107 this.context.Buf.simpleRequest(REQUEST_DATA_CALL_LIST);
2108 },
2110 _attachDataRegistration: false,
2111 /**
2112 * Manually attach/detach data registration.
2113 *
2114 * @param attach
2115 * Boolean value indicating attach or detach.
2116 */
2117 setDataRegistration: function(options) {
2118 let request = options.attach ? RIL_REQUEST_GPRS_ATTACH :
2119 RIL_REQUEST_GPRS_DETACH;
2120 this._attachDataRegistration = options.attach;
2121 this.context.Buf.simpleRequest(request);
2122 },
2124 /**
2125 * Get failure casue code for the most recently failed PDP context.
2126 */
2127 getFailCauseCode: function(callback) {
2128 this.context.Buf.simpleRequest(REQUEST_LAST_CALL_FAIL_CAUSE,
2129 {callback: callback});
2130 },
2132 /**
2133 * Helper to parse MMI/USSD string. TS.22.030 Figure 3.5.3.2.
2134 */
2135 _parseMMI: function(mmiString) {
2136 if (!mmiString || !mmiString.length) {
2137 return null;
2138 }
2140 let matches = this._matchMMIRegexp(mmiString);
2141 if (matches) {
2142 // After successfully executing the regular expresion over the MMI string,
2143 // the following match groups should contain:
2144 // 1 = full MMI string that might be used as a USSD request.
2145 // 2 = MMI procedure.
2146 // 3 = Service code.
2147 // 5 = SIA.
2148 // 7 = SIB.
2149 // 9 = SIC.
2150 // 11 = Password registration.
2151 // 12 = Dialing number.
2152 return {
2153 fullMMI: matches[MMI_MATCH_GROUP_FULL_MMI],
2154 procedure: matches[MMI_MATCH_GROUP_MMI_PROCEDURE],
2155 serviceCode: matches[MMI_MATCH_GROUP_SERVICE_CODE],
2156 sia: matches[MMI_MATCH_GROUP_SIA],
2157 sib: matches[MMI_MATCH_GROUP_SIB],
2158 sic: matches[MMI_MATCH_GROUP_SIC],
2159 pwd: matches[MMI_MATCH_GROUP_PWD_CONFIRM],
2160 dialNumber: matches[MMI_MATCH_GROUP_DIALING_NUMBER]
2161 };
2162 }
2164 if (this._isPoundString(mmiString) ||
2165 this._isMMIShortString(mmiString)) {
2166 return {
2167 fullMMI: mmiString
2168 };
2169 }
2171 return null;
2172 },
2174 /**
2175 * Helper to parse MMI string via regular expression. TS.22.030 Figure
2176 * 3.5.3.2.
2177 */
2178 _matchMMIRegexp: function(mmiString) {
2179 // Regexp to parse and process the MMI code.
2180 if (this._mmiRegExp == null) {
2181 // The first group of the regexp takes the whole MMI string.
2182 // The second group takes the MMI procedure that can be:
2183 // - Activation (*SC*SI#).
2184 // - Deactivation (#SC*SI#).
2185 // - Interrogation (*#SC*SI#).
2186 // - Registration (**SC*SI#).
2187 // - Erasure (##SC*SI#).
2188 // where SC = Service Code (2 or 3 digits) and SI = Supplementary Info
2189 // (variable length).
2190 let pattern = "((\\*[*#]?|##?)";
2192 // Third group of the regexp looks for the MMI Service code, which is a
2193 // 2 or 3 digits that uniquely specifies the Supplementary Service
2194 // associated with the MMI code.
2195 pattern += "(\\d{2,3})";
2197 // Groups from 4 to 9 looks for the MMI Supplementary Information SIA,
2198 // SIB and SIC. SIA may comprise e.g. a PIN code or Directory Number,
2199 // SIB may be used to specify the tele or bearer service and SIC to
2200 // specify the value of the "No Reply Condition Timer". Where a particular
2201 // service request does not require any SI, "*SI" is not entered. The use
2202 // of SIA, SIB and SIC is optional and shall be entered in any of the
2203 // following formats:
2204 // - *SIA*SIB*SIC#
2205 // - *SIA*SIB#
2206 // - *SIA**SIC#
2207 // - *SIA#
2208 // - **SIB*SIC#
2209 // - ***SISC#
2210 pattern += "(\\*([^*#]*)(\\*([^*#]*)(\\*([^*#]*)";
2212 // The eleventh group takes the password for the case of a password
2213 // registration procedure.
2214 pattern += "(\\*([^*#]*))?)?)?)?#)";
2216 // The last group takes the dial string after the #.
2217 pattern += "([^#]*)";
2219 this._mmiRegExp = new RegExp(pattern);
2220 }
2222 // Regex only applys for those well-defined MMI strings (refer to TS.22.030
2223 // Annex B), otherwise, null should be the expected return value.
2224 return this._mmiRegExp.exec(mmiString);
2225 },
2227 /**
2228 * Helper to parse # string. TS.22.030 Figure 3.5.3.2.
2229 */
2230 _isPoundString: function(mmiString) {
2231 return (mmiString.charAt(mmiString.length - 1) === MMI_END_OF_USSD);
2232 },
2234 /**
2235 * Helper to parse short string. TS.22.030 Figure 3.5.3.2.
2236 */
2237 _isMMIShortString: function(mmiString) {
2238 if (mmiString.length > 2) {
2239 return false;
2240 }
2242 if (this._isEmergencyNumber(mmiString)) {
2243 return false;
2244 }
2246 // In a call case.
2247 if (Object.getOwnPropertyNames(this.currentCalls).length > 0) {
2248 return true;
2249 }
2251 if ((mmiString.length != 2) || (mmiString.charAt(0) !== '1')) {
2252 return true;
2253 }
2255 return false;
2256 },
2258 sendMMI: function(options) {
2259 if (DEBUG) {
2260 this.context.debug("SendMMI " + JSON.stringify(options));
2261 }
2262 let mmiString = options.mmi;
2263 let mmi = this._parseMMI(mmiString);
2265 let _sendMMIError = (function(errorMsg, mmiServiceCode) {
2266 options.success = false;
2267 options.errorMsg = errorMsg;
2268 if (mmiServiceCode) {
2269 options.mmiServiceCode = mmiServiceCode;
2270 }
2271 this.sendChromeMessage(options);
2272 }).bind(this);
2274 function _isValidPINPUKRequest(mmiServiceCode) {
2275 // The only allowed MMI procedure for ICC PIN, PIN2, PUK and PUK2 handling
2276 // is "Registration" (**).
2277 if (!mmi.procedure || mmi.procedure != MMI_PROCEDURE_REGISTRATION ) {
2278 _sendMMIError(MMI_ERROR_KS_INVALID_ACTION, mmiServiceCode);
2279 return false;
2280 }
2282 if (!mmi.sia || !mmi.sia.length || !mmi.sib || !mmi.sib.length ||
2283 !mmi.sic || !mmi.sic.length) {
2284 _sendMMIError(MMI_ERROR_KS_ERROR, mmiServiceCode);
2285 return false;
2286 }
2288 if (mmi.sib != mmi.sic) {
2289 _sendMMIError(MMI_ERROR_KS_MISMATCH_PIN, mmiServiceCode);
2290 return false;
2291 }
2293 if (mmi.sia.length < 4 || mmi.sia.length > 8 ||
2294 mmi.sib.length < 4 || mmi.sib.length > 8 ||
2295 mmi.sic.length < 4 || mmi.sic.length > 8) {
2296 _sendMMIError(MMI_ERROR_KS_INVALID_PIN, mmiServiceCode);
2297 return false;
2298 }
2300 return true;
2301 }
2303 let _isRadioAvailable = (function(mmiServiceCode) {
2304 if (this.radioState !== GECKO_RADIOSTATE_READY) {
2305 _sendMMIError(GECKO_ERROR_RADIO_NOT_AVAILABLE, mmiServiceCode);
2306 return false;
2307 }
2308 return true;
2309 }).bind(this);
2311 // If we couldn't parse the MMI code, we'll send it as an USSD request.
2312 if (mmi === null) {
2313 if (this._ussdSession) {
2314 if (!_isRadioAvailable(MMI_KS_SC_USSD)) {
2315 return;
2316 }
2317 options.ussd = mmiString;
2318 this.sendUSSD(options);
2319 return;
2320 }
2322 _sendMMIError(MMI_ERROR_KS_ERROR);
2323 return;
2324 }
2326 if (DEBUG) {
2327 this.context.debug("MMI " + JSON.stringify(mmi));
2328 }
2330 // We check if the MMI service code is supported and in that case we
2331 // trigger the appropriate RIL request if possible.
2332 let sc = mmi.serviceCode;
2333 switch (sc) {
2334 // Call forwarding
2335 case MMI_SC_CFU:
2336 case MMI_SC_CF_BUSY:
2337 case MMI_SC_CF_NO_REPLY:
2338 case MMI_SC_CF_NOT_REACHABLE:
2339 case MMI_SC_CF_ALL:
2340 case MMI_SC_CF_ALL_CONDITIONAL:
2341 if (!_isRadioAvailable(MMI_KS_SC_CALL_FORWARDING)) {
2342 return;
2343 }
2344 // Call forwarding requires at least an action, given by the MMI
2345 // procedure, and a reason, given by the MMI service code, but there
2346 // is no way that we get this far without a valid procedure or service
2347 // code.
2348 options.mmiServiceCode = MMI_KS_SC_CALL_FORWARDING;
2349 options.action = MMI_PROC_TO_CF_ACTION[mmi.procedure];
2350 options.reason = MMI_SC_TO_CF_REASON[sc];
2351 options.number = mmi.sia;
2352 options.serviceClass = this._siToServiceClass(mmi.sib);
2353 if (options.action == CALL_FORWARD_ACTION_QUERY_STATUS) {
2354 this.queryCallForwardStatus(options);
2355 return;
2356 }
2358 options.isSetCallForward = true;
2359 options.timeSeconds = mmi.sic;
2360 this.setCallForward(options);
2361 return;
2363 // Change the current ICC PIN number.
2364 case MMI_SC_PIN:
2365 // As defined in TS.122.030 6.6.2 to change the ICC PIN we should expect
2366 // an MMI code of the form **04*OLD_PIN*NEW_PIN*NEW_PIN#, where old PIN
2367 // should be entered as the SIA parameter and the new PIN as SIB and
2368 // SIC.
2369 if (!_isRadioAvailable(MMI_KS_SC_PIN) ||
2370 !_isValidPINPUKRequest(MMI_KS_SC_PIN)) {
2371 return;
2372 }
2374 options.mmiServiceCode = MMI_KS_SC_PIN;
2375 options.pin = mmi.sia;
2376 options.newPin = mmi.sib;
2377 this.changeICCPIN(options);
2378 return;
2380 // Change the current ICC PIN2 number.
2381 case MMI_SC_PIN2:
2382 // As defined in TS.122.030 6.6.2 to change the ICC PIN2 we should
2383 // enter and MMI code of the form **042*OLD_PIN2*NEW_PIN2*NEW_PIN2#,
2384 // where the old PIN2 should be entered as the SIA parameter and the
2385 // new PIN2 as SIB and SIC.
2386 if (!_isRadioAvailable(MMI_KS_SC_PIN2) ||
2387 !_isValidPINPUKRequest(MMI_KS_SC_PIN2)) {
2388 return;
2389 }
2391 options.mmiServiceCode = MMI_KS_SC_PIN2;
2392 options.pin = mmi.sia;
2393 options.newPin = mmi.sib;
2394 this.changeICCPIN2(options);
2395 return;
2397 // Unblock ICC PIN.
2398 case MMI_SC_PUK:
2399 // As defined in TS.122.030 6.6.3 to unblock the ICC PIN we should
2400 // enter an MMI code of the form **05*PUK*NEW_PIN*NEW_PIN#, where PUK
2401 // should be entered as the SIA parameter and the new PIN as SIB and
2402 // SIC.
2403 if (!_isRadioAvailable(MMI_KS_SC_PUK) ||
2404 !_isValidPINPUKRequest(MMI_KS_SC_PUK)) {
2405 return;
2406 }
2408 options.mmiServiceCode = MMI_KS_SC_PUK;
2409 options.puk = mmi.sia;
2410 options.newPin = mmi.sib;
2411 this.enterICCPUK(options);
2412 return;
2414 // Unblock ICC PIN2.
2415 case MMI_SC_PUK2:
2416 // As defined in TS.122.030 6.6.3 to unblock the ICC PIN2 we should
2417 // enter an MMI code of the form **052*PUK2*NEW_PIN2*NEW_PIN2#, where
2418 // PUK2 should be entered as the SIA parameter and the new PIN2 as SIB
2419 // and SIC.
2420 if (!_isRadioAvailable(MMI_KS_SC_PUK2) ||
2421 !_isValidPINPUKRequest(MMI_KS_SC_PUK2)) {
2422 return;
2423 }
2425 options.mmiServiceCode = MMI_KS_SC_PUK2;
2426 options.puk = mmi.sia;
2427 options.newPin = mmi.sib;
2428 this.enterICCPUK2(options);
2429 return;
2431 // IMEI
2432 case MMI_SC_IMEI:
2433 // A device's IMEI can't change, so we only need to request it once.
2434 if (this.IMEI == null) {
2435 this.getIMEI(options);
2436 return;
2437 }
2438 // If we already had the device's IMEI, we just send it to chrome.
2439 options.mmiServiceCode = MMI_KS_SC_IMEI;
2440 options.success = true;
2441 options.statusMessage = this.IMEI;
2442 this.sendChromeMessage(options);
2443 return;
2445 // CLIP
2446 case MMI_SC_CLIP:
2447 options.mmiServiceCode = MMI_KS_SC_CLIP;
2448 options.procedure = mmi.procedure;
2449 if (options.procedure === MMI_PROCEDURE_INTERROGATION) {
2450 this.queryCLIP(options);
2451 } else {
2452 _sendMMIError(MMI_ERROR_KS_NOT_SUPPORTED, MMI_KS_SC_CLIP);
2453 }
2454 return;
2456 // CLIR (non-temporary ones)
2457 // TODO: Both dial() and sendMMI() functions should be unified at some
2458 // point in the future. In the mean time we handle temporary CLIR MMI
2459 // commands through the dial() function. Please see bug 889737.
2460 case MMI_SC_CLIR:
2461 options.mmiServiceCode = MMI_KS_SC_CLIR;
2462 options.procedure = mmi.procedure;
2463 switch (options.procedure) {
2464 case MMI_PROCEDURE_INTERROGATION:
2465 this.getCLIR(options);
2466 return;
2467 case MMI_PROCEDURE_ACTIVATION:
2468 options.clirMode = CLIR_INVOCATION;
2469 break;
2470 case MMI_PROCEDURE_DEACTIVATION:
2471 options.clirMode = CLIR_SUPPRESSION;
2472 break;
2473 default:
2474 _sendMMIError(MMI_ERROR_KS_NOT_SUPPORTED, MMI_KS_SC_CLIR);
2475 return;
2476 }
2477 options.isSetCLIR = true;
2478 this.setCLIR(options);
2479 return;
2481 // Call barring
2482 case MMI_SC_BAOC:
2483 case MMI_SC_BAOIC:
2484 case MMI_SC_BAOICxH:
2485 case MMI_SC_BAIC:
2486 case MMI_SC_BAICr:
2487 case MMI_SC_BA_ALL:
2488 case MMI_SC_BA_MO:
2489 case MMI_SC_BA_MT:
2490 options.mmiServiceCode = MMI_KS_SC_CALL_BARRING;
2491 options.password = mmi.sia || "";
2492 options.serviceClass = this._siToServiceClass(mmi.sib);
2493 options.facility = MMI_SC_TO_CB_FACILITY[sc];
2494 options.procedure = mmi.procedure;
2495 if (mmi.procedure === MMI_PROCEDURE_INTERROGATION) {
2496 this.queryICCFacilityLock(options);
2497 return;
2498 }
2499 if (mmi.procedure === MMI_PROCEDURE_ACTIVATION) {
2500 options.enabled = 1;
2501 } else if (mmi.procedure === MMI_PROCEDURE_DEACTIVATION) {
2502 options.enabled = 0;
2503 } else {
2504 _sendMMIError(MMI_ERROR_KS_NOT_SUPPORTED, MMI_KS_SC_CALL_BARRING);
2505 return;
2506 }
2507 this.setICCFacilityLock(options);
2508 return;
2510 // Call waiting
2511 case MMI_SC_CALL_WAITING:
2512 if (!_isRadioAvailable(MMI_KS_SC_CALL_WAITING)) {
2513 return;
2514 }
2516 options.mmiServiceCode = MMI_KS_SC_CALL_WAITING;
2518 if (mmi.procedure === MMI_PROCEDURE_INTERROGATION) {
2519 this._handleQueryMMICallWaiting(options);
2520 return;
2521 }
2523 if (mmi.procedure === MMI_PROCEDURE_ACTIVATION) {
2524 options.enabled = true;
2525 } else if (mmi.procedure === MMI_PROCEDURE_DEACTIVATION) {
2526 options.enabled = false;
2527 } else {
2528 _sendMMIError(MMI_ERROR_KS_NOT_SUPPORTED, MMI_KS_SC_CALL_WAITING);
2529 return;
2530 }
2532 options.serviceClass = this._siToServiceClass(mmi.sia);
2533 this._handleSetMMICallWaiting(options);
2534 return;
2535 }
2537 // If the MMI code is not a known code and is a recognized USSD request,
2538 // it shall still be sent as a USSD request.
2539 if (mmi.fullMMI) {
2540 if (!_isRadioAvailable(MMI_KS_SC_USSD)) {
2541 return;
2542 }
2544 options.ussd = mmi.fullMMI;
2545 options.mmiServiceCode = MMI_KS_SC_USSD;
2546 this.sendUSSD(options);
2547 return;
2548 }
2550 // At this point, the MMI string is considered as not valid MMI code and
2551 // not valid USSD code.
2552 _sendMMIError(MMI_ERROR_KS_ERROR);
2553 },
2555 /**
2556 * Send USSD.
2557 *
2558 * @param ussd
2559 * String containing the USSD code.
2560 *
2561 */
2562 sendUSSD: function(options) {
2563 let Buf = this.context.Buf;
2564 Buf.newParcel(REQUEST_SEND_USSD, options);
2565 Buf.writeString(options.ussd);
2566 Buf.sendParcel();
2567 },
2569 /**
2570 * Cancel pending USSD.
2571 */
2572 cancelUSSD: function(options) {
2573 options.mmiServiceCode = MMI_KS_SC_USSD;
2574 this.context.Buf.simpleRequest(REQUEST_CANCEL_USSD, options);
2575 },
2577 /**
2578 * Queries current call forward rules.
2579 *
2580 * @param reason
2581 * One of nsIDOMMozMobileCFInfo.CALL_FORWARD_REASON_* constants.
2582 * @param serviceClass
2583 * One of ICC_SERVICE_CLASS_* constants.
2584 * @param number
2585 * Phone number of forwarding address.
2586 */
2587 queryCallForwardStatus: function(options) {
2588 let Buf = this.context.Buf;
2589 let number = options.number || "";
2590 Buf.newParcel(REQUEST_QUERY_CALL_FORWARD_STATUS, options);
2591 Buf.writeInt32(CALL_FORWARD_ACTION_QUERY_STATUS);
2592 Buf.writeInt32(options.reason);
2593 Buf.writeInt32(options.serviceClass || ICC_SERVICE_CLASS_NONE);
2594 Buf.writeInt32(this._toaFromString(number));
2595 Buf.writeString(number);
2596 Buf.writeInt32(0);
2597 Buf.sendParcel();
2598 },
2600 /**
2601 * Configures call forward rule.
2602 *
2603 * @param action
2604 * One of nsIDOMMozMobileCFInfo.CALL_FORWARD_ACTION_* constants.
2605 * @param reason
2606 * One of nsIDOMMozMobileCFInfo.CALL_FORWARD_REASON_* constants.
2607 * @param serviceClass
2608 * One of ICC_SERVICE_CLASS_* constants.
2609 * @param number
2610 * Phone number of forwarding address.
2611 * @param timeSeconds
2612 * Time in seconds to wait beforec all is forwarded.
2613 */
2614 setCallForward: function(options) {
2615 let Buf = this.context.Buf;
2616 Buf.newParcel(REQUEST_SET_CALL_FORWARD, options);
2617 Buf.writeInt32(options.action);
2618 Buf.writeInt32(options.reason);
2619 Buf.writeInt32(options.serviceClass);
2620 Buf.writeInt32(this._toaFromString(options.number));
2621 Buf.writeString(options.number);
2622 Buf.writeInt32(options.timeSeconds);
2623 Buf.sendParcel();
2624 },
2626 /**
2627 * Queries current call barring rules.
2628 *
2629 * @param program
2630 * One of nsIDOMMozMobileConnection.CALL_BARRING_PROGRAM_* constants.
2631 * @param serviceClass
2632 * One of ICC_SERVICE_CLASS_* constants.
2633 */
2634 queryCallBarringStatus: function(options) {
2635 options.facility = CALL_BARRING_PROGRAM_TO_FACILITY[options.program];
2636 options.password = ""; // For query no need to provide it.
2637 this.queryICCFacilityLock(options);
2638 },
2640 /**
2641 * Configures call barring rule.
2642 *
2643 * @param program
2644 * One of nsIDOMMozMobileConnection.CALL_BARRING_PROGRAM_* constants.
2645 * @param enabled
2646 * Enable or disable the call barring.
2647 * @param password
2648 * Barring password.
2649 * @param serviceClass
2650 * One of ICC_SERVICE_CLASS_* constants.
2651 */
2652 setCallBarring: function(options) {
2653 options.facility = CALL_BARRING_PROGRAM_TO_FACILITY[options.program];
2654 this.setICCFacilityLock(options);
2655 },
2657 /**
2658 * Change call barring facility password.
2659 *
2660 * @param pin
2661 * Old password.
2662 * @param newPin
2663 * New password.
2664 */
2665 changeCallBarringPassword: function(options) {
2666 let Buf = this.context.Buf;
2667 Buf.newParcel(REQUEST_CHANGE_BARRING_PASSWORD, options);
2668 Buf.writeInt32(3);
2669 // Set facility to ICC_CB_FACILITY_BA_ALL by following TS.22.030 clause
2670 // 6.5.4 and Table B.1.
2671 Buf.writeString(ICC_CB_FACILITY_BA_ALL);
2672 Buf.writeString(options.pin);
2673 Buf.writeString(options.newPin);
2674 Buf.sendParcel();
2675 },
2677 /**
2678 * Handle STK CALL_SET_UP request.
2679 *
2680 * @param hasConfirmed
2681 * Does use have confirmed the call requested from ICC?
2682 */
2683 stkHandleCallSetup: function(options) {
2684 let Buf = this.context.Buf;
2685 Buf.newParcel(REQUEST_STK_HANDLE_CALL_SETUP_REQUESTED_FROM_SIM);
2686 Buf.writeInt32(1);
2687 Buf.writeInt32(options.hasConfirmed ? 1 : 0);
2688 Buf.sendParcel();
2689 },
2691 /**
2692 * Send STK Profile Download.
2693 *
2694 * @param profile Profile supported by ME.
2695 */
2696 sendStkTerminalProfile: function(profile) {
2697 let Buf = this.context.Buf;
2698 let GsmPDUHelper = this.context.GsmPDUHelper;
2700 Buf.newParcel(REQUEST_STK_SET_PROFILE);
2701 Buf.writeInt32(profile.length * 2);
2702 for (let i = 0; i < profile.length; i++) {
2703 GsmPDUHelper.writeHexOctet(profile[i]);
2704 }
2705 Buf.writeInt32(0);
2706 Buf.sendParcel();
2707 },
2709 /**
2710 * Send STK terminal response.
2711 *
2712 * @param command
2713 * @param deviceIdentities
2714 * @param resultCode
2715 * @param [optional] itemIdentifier
2716 * @param [optional] input
2717 * @param [optional] isYesNo
2718 * @param [optional] hasConfirmed
2719 * @param [optional] localInfo
2720 * @param [optional] timer
2721 */
2722 sendStkTerminalResponse: function(response) {
2723 if (response.hasConfirmed !== undefined) {
2724 this.stkHandleCallSetup(response);
2725 return;
2726 }
2728 let Buf = this.context.Buf;
2729 let ComprehensionTlvHelper = this.context.ComprehensionTlvHelper;
2730 let GsmPDUHelper = this.context.GsmPDUHelper;
2732 let command = response.command;
2733 Buf.newParcel(REQUEST_STK_SEND_TERMINAL_RESPONSE);
2735 // 1st mark for Parcel size
2736 Buf.startCalOutgoingSize(function(size) {
2737 // Parcel size is in string length, which costs 2 uint8 per char.
2738 Buf.writeInt32(size / 2);
2739 });
2741 // Command Details
2742 GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_COMMAND_DETAILS |
2743 COMPREHENSIONTLV_FLAG_CR);
2744 GsmPDUHelper.writeHexOctet(3);
2745 if (response.command) {
2746 GsmPDUHelper.writeHexOctet(command.commandNumber);
2747 GsmPDUHelper.writeHexOctet(command.typeOfCommand);
2748 GsmPDUHelper.writeHexOctet(command.commandQualifier);
2749 } else {
2750 GsmPDUHelper.writeHexOctet(0x00);
2751 GsmPDUHelper.writeHexOctet(0x00);
2752 GsmPDUHelper.writeHexOctet(0x00);
2753 }
2755 // Device Identifier
2756 // According to TS102.223/TS31.111 section 6.8 Structure of
2757 // TERMINAL RESPONSE, "For all SIMPLE-TLV objects with Min=N,
2758 // the ME should set the CR(comprehension required) flag to
2759 // comprehension not required.(CR=0)"
2760 // Since DEVICE_IDENTITIES and DURATION TLVs have Min=N,
2761 // the CR flag is not set.
2762 GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_DEVICE_ID);
2763 GsmPDUHelper.writeHexOctet(2);
2764 GsmPDUHelper.writeHexOctet(STK_DEVICE_ID_ME);
2765 GsmPDUHelper.writeHexOctet(STK_DEVICE_ID_SIM);
2767 // Result
2768 GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_RESULT |
2769 COMPREHENSIONTLV_FLAG_CR);
2770 GsmPDUHelper.writeHexOctet(1);
2771 GsmPDUHelper.writeHexOctet(response.resultCode);
2773 // Item Identifier
2774 if (response.itemIdentifier != null) {
2775 GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_ITEM_ID |
2776 COMPREHENSIONTLV_FLAG_CR);
2777 GsmPDUHelper.writeHexOctet(1);
2778 GsmPDUHelper.writeHexOctet(response.itemIdentifier);
2779 }
2781 // No need to process Text data if user requests help information.
2782 if (response.resultCode != STK_RESULT_HELP_INFO_REQUIRED) {
2783 let text;
2784 if (response.isYesNo !== undefined) {
2785 // GET_INKEY
2786 // When the ME issues a successful TERMINAL RESPONSE for a GET INKEY
2787 // ("Yes/No") command with command qualifier set to "Yes/No", it shall
2788 // supply the value '01' when the answer is "positive" and the value
2789 // '00' when the answer is "negative" in the Text string data object.
2790 text = response.isYesNo ? 0x01 : 0x00;
2791 } else {
2792 text = response.input;
2793 }
2795 if (text) {
2796 GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_TEXT_STRING |
2797 COMPREHENSIONTLV_FLAG_CR);
2799 // 2nd mark for text length
2800 Buf.startCalOutgoingSize(function(size) {
2801 // Text length is in number of hexOctets, which costs 4 uint8 per hexOctet.
2802 GsmPDUHelper.writeHexOctet(size / 4);
2803 });
2805 let coding = command.options.isUCS2 ?
2806 STK_TEXT_CODING_UCS2 :
2807 (command.options.isPacked ?
2808 STK_TEXT_CODING_GSM_7BIT_PACKED :
2809 STK_TEXT_CODING_GSM_8BIT);
2810 GsmPDUHelper.writeHexOctet(coding);
2812 // Write Text String.
2813 switch (coding) {
2814 case STK_TEXT_CODING_UCS2:
2815 GsmPDUHelper.writeUCS2String(text);
2816 break;
2817 case STK_TEXT_CODING_GSM_7BIT_PACKED:
2818 GsmPDUHelper.writeStringAsSeptets(text, 0, 0, 0);
2819 break;
2820 case STK_TEXT_CODING_GSM_8BIT:
2821 for (let i = 0; i < text.length; i++) {
2822 GsmPDUHelper.writeHexOctet(text.charCodeAt(i));
2823 }
2824 break;
2825 }
2827 // Calculate and write text length to 2nd mark
2828 Buf.stopCalOutgoingSize();
2829 }
2830 }
2832 // Local Information
2833 if (response.localInfo) {
2834 let localInfo = response.localInfo;
2836 // Location Infomation
2837 if (localInfo.locationInfo) {
2838 ComprehensionTlvHelper.writeLocationInfoTlv(localInfo.locationInfo);
2839 }
2841 // IMEI
2842 if (localInfo.imei != null) {
2843 let imei = localInfo.imei;
2844 if (imei.length == 15) {
2845 imei = imei + "0";
2846 }
2848 GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_IMEI);
2849 GsmPDUHelper.writeHexOctet(8);
2850 for (let i = 0; i < imei.length / 2; i++) {
2851 GsmPDUHelper.writeHexOctet(parseInt(imei.substr(i * 2, 2), 16));
2852 }
2853 }
2855 // Date and Time Zone
2856 if (localInfo.date != null) {
2857 ComprehensionTlvHelper.writeDateTimeZoneTlv(localInfo.date);
2858 }
2860 // Language
2861 if (localInfo.language) {
2862 ComprehensionTlvHelper.writeLanguageTlv(localInfo.language);
2863 }
2864 }
2866 // Timer
2867 if (response.timer) {
2868 let timer = response.timer;
2870 if (timer.timerId) {
2871 GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_TIMER_IDENTIFIER);
2872 GsmPDUHelper.writeHexOctet(1);
2873 GsmPDUHelper.writeHexOctet(timer.timerId);
2874 }
2876 if (timer.timerValue) {
2877 ComprehensionTlvHelper.writeTimerValueTlv(timer.timerValue, false);
2878 }
2879 }
2881 // Calculate and write Parcel size to 1st mark
2882 Buf.stopCalOutgoingSize();
2884 Buf.writeInt32(0);
2885 Buf.sendParcel();
2886 },
2888 /**
2889 * Send STK Envelope(Menu Selection) command.
2890 *
2891 * @param itemIdentifier
2892 * @param helpRequested
2893 */
2894 sendStkMenuSelection: function(command) {
2895 command.tag = BER_MENU_SELECTION_TAG;
2896 command.deviceId = {
2897 sourceId :STK_DEVICE_ID_KEYPAD,
2898 destinationId: STK_DEVICE_ID_SIM
2899 };
2900 this.sendICCEnvelopeCommand(command);
2901 },
2903 /**
2904 * Send STK Envelope(Timer Expiration) command.
2905 *
2906 * @param timer
2907 */
2908 sendStkTimerExpiration: function(command) {
2909 command.tag = BER_TIMER_EXPIRATION_TAG;
2910 command.deviceId = {
2911 sourceId: STK_DEVICE_ID_ME,
2912 destinationId: STK_DEVICE_ID_SIM
2913 };
2914 command.timerId = command.timer.timerId;
2915 command.timerValue = command.timer.timerValue;
2916 this.sendICCEnvelopeCommand(command);
2917 },
2919 /**
2920 * Send STK Envelope(Event Download) command.
2921 * @param event
2922 */
2923 sendStkEventDownload: function(command) {
2924 command.tag = BER_EVENT_DOWNLOAD_TAG;
2925 command.eventList = command.event.eventType;
2926 switch (command.eventList) {
2927 case STK_EVENT_TYPE_LOCATION_STATUS:
2928 command.deviceId = {
2929 sourceId :STK_DEVICE_ID_ME,
2930 destinationId: STK_DEVICE_ID_SIM
2931 };
2932 command.locationStatus = command.event.locationStatus;
2933 // Location info should only be provided when locationStatus is normal.
2934 if (command.locationStatus == STK_SERVICE_STATE_NORMAL) {
2935 command.locationInfo = command.event.locationInfo;
2936 }
2937 break;
2938 case STK_EVENT_TYPE_MT_CALL:
2939 command.deviceId = {
2940 sourceId: STK_DEVICE_ID_NETWORK,
2941 destinationId: STK_DEVICE_ID_SIM
2942 };
2943 command.transactionId = 0;
2944 command.address = command.event.number;
2945 break;
2946 case STK_EVENT_TYPE_CALL_DISCONNECTED:
2947 command.cause = command.event.error;
2948 // Fall through.
2949 case STK_EVENT_TYPE_CALL_CONNECTED:
2950 command.deviceId = {
2951 sourceId: (command.event.isIssuedByRemote ?
2952 STK_DEVICE_ID_NETWORK : STK_DEVICE_ID_ME),
2953 destinationId: STK_DEVICE_ID_SIM
2954 };
2955 command.transactionId = 0;
2956 break;
2957 case STK_EVENT_TYPE_USER_ACTIVITY:
2958 command.deviceId = {
2959 sourceId: STK_DEVICE_ID_ME,
2960 destinationId: STK_DEVICE_ID_SIM
2961 };
2962 break;
2963 case STK_EVENT_TYPE_IDLE_SCREEN_AVAILABLE:
2964 command.deviceId = {
2965 sourceId: STK_DEVICE_ID_DISPLAY,
2966 destinationId: STK_DEVICE_ID_SIM
2967 };
2968 break;
2969 case STK_EVENT_TYPE_LANGUAGE_SELECTION:
2970 command.deviceId = {
2971 sourceId: STK_DEVICE_ID_ME,
2972 destinationId: STK_DEVICE_ID_SIM
2973 };
2974 command.language = command.event.language;
2975 break;
2976 case STK_EVENT_TYPE_BROWSER_TERMINATION:
2977 command.deviceId = {
2978 sourceId: STK_DEVICE_ID_ME,
2979 destinationId: STK_DEVICE_ID_SIM
2980 };
2981 command.terminationCause = command.event.terminationCause;
2982 break;
2983 }
2984 this.sendICCEnvelopeCommand(command);
2985 },
2987 /**
2988 * Send REQUEST_STK_SEND_ENVELOPE_COMMAND to ICC.
2989 *
2990 * @param tag
2991 * @patam deviceId
2992 * @param [optioanl] itemIdentifier
2993 * @param [optional] helpRequested
2994 * @param [optional] eventList
2995 * @param [optional] locationStatus
2996 * @param [optional] locationInfo
2997 * @param [optional] address
2998 * @param [optional] transactionId
2999 * @param [optional] cause
3000 * @param [optional] timerId
3001 * @param [optional] timerValue
3002 * @param [optional] terminationCause
3003 */
3004 sendICCEnvelopeCommand: function(options) {
3005 if (DEBUG) {
3006 this.context.debug("Stk Envelope " + JSON.stringify(options));
3007 }
3009 let Buf = this.context.Buf;
3010 let ComprehensionTlvHelper = this.context.ComprehensionTlvHelper;
3011 let GsmPDUHelper = this.context.GsmPDUHelper;
3013 Buf.newParcel(REQUEST_STK_SEND_ENVELOPE_COMMAND);
3015 // 1st mark for Parcel size
3016 Buf.startCalOutgoingSize(function(size) {
3017 // Parcel size is in string length, which costs 2 uint8 per char.
3018 Buf.writeInt32(size / 2);
3019 });
3021 // Write a BER-TLV
3022 GsmPDUHelper.writeHexOctet(options.tag);
3023 // 2nd mark for BER length
3024 Buf.startCalOutgoingSize(function(size) {
3025 // BER length is in number of hexOctets, which costs 4 uint8 per hexOctet.
3026 GsmPDUHelper.writeHexOctet(size / 4);
3027 });
3029 // Device Identifies
3030 GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_DEVICE_ID |
3031 COMPREHENSIONTLV_FLAG_CR);
3032 GsmPDUHelper.writeHexOctet(2);
3033 GsmPDUHelper.writeHexOctet(options.deviceId.sourceId);
3034 GsmPDUHelper.writeHexOctet(options.deviceId.destinationId);
3036 // Item Identifier
3037 if (options.itemIdentifier != null) {
3038 GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_ITEM_ID |
3039 COMPREHENSIONTLV_FLAG_CR);
3040 GsmPDUHelper.writeHexOctet(1);
3041 GsmPDUHelper.writeHexOctet(options.itemIdentifier);
3042 }
3044 // Help Request
3045 if (options.helpRequested) {
3046 GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_HELP_REQUEST |
3047 COMPREHENSIONTLV_FLAG_CR);
3048 GsmPDUHelper.writeHexOctet(0);
3049 // Help Request doesn't have value
3050 }
3052 // Event List
3053 if (options.eventList != null) {
3054 GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_EVENT_LIST |
3055 COMPREHENSIONTLV_FLAG_CR);
3056 GsmPDUHelper.writeHexOctet(1);
3057 GsmPDUHelper.writeHexOctet(options.eventList);
3058 }
3060 // Location Status
3061 if (options.locationStatus != null) {
3062 let len = options.locationStatus.length;
3063 GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_LOCATION_STATUS |
3064 COMPREHENSIONTLV_FLAG_CR);
3065 GsmPDUHelper.writeHexOctet(1);
3066 GsmPDUHelper.writeHexOctet(options.locationStatus);
3067 }
3069 // Location Info
3070 if (options.locationInfo) {
3071 ComprehensionTlvHelper.writeLocationInfoTlv(options.locationInfo);
3072 }
3074 // Transaction Id
3075 if (options.transactionId != null) {
3076 GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_TRANSACTION_ID |
3077 COMPREHENSIONTLV_FLAG_CR);
3078 GsmPDUHelper.writeHexOctet(1);
3079 GsmPDUHelper.writeHexOctet(options.transactionId);
3080 }
3082 // Address
3083 if (options.address) {
3084 GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_ADDRESS |
3085 COMPREHENSIONTLV_FLAG_CR);
3086 ComprehensionTlvHelper.writeLength(
3087 Math.ceil(options.address.length/2) + 1 // address BCD + TON
3088 );
3089 this.context.ICCPDUHelper.writeDiallingNumber(options.address);
3090 }
3092 // Cause of disconnection.
3093 if (options.cause != null) {
3094 ComprehensionTlvHelper.writeCauseTlv(options.cause);
3095 }
3097 // Timer Identifier
3098 if (options.timerId != null) {
3099 GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_TIMER_IDENTIFIER |
3100 COMPREHENSIONTLV_FLAG_CR);
3101 GsmPDUHelper.writeHexOctet(1);
3102 GsmPDUHelper.writeHexOctet(options.timerId);
3103 }
3105 // Timer Value
3106 if (options.timerValue != null) {
3107 ComprehensionTlvHelper.writeTimerValueTlv(options.timerValue, true);
3108 }
3110 // Language
3111 if (options.language) {
3112 ComprehensionTlvHelper.writeLanguageTlv(options.language);
3113 }
3115 // Browser Termination
3116 if (options.terminationCause != null) {
3117 GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_BROWSER_TERMINATION_CAUSE |
3118 COMPREHENSIONTLV_FLAG_CR);
3119 GsmPDUHelper.writeHexOctet(1);
3120 GsmPDUHelper.writeHexOctet(options.terminationCause);
3121 }
3123 // Calculate and write BER length to 2nd mark
3124 Buf.stopCalOutgoingSize();
3126 // Calculate and write Parcel size to 1st mark
3127 Buf.stopCalOutgoingSize();
3129 Buf.writeInt32(0);
3130 Buf.sendParcel();
3131 },
3133 /**
3134 * Check a given number against the list of emergency numbers provided by the RIL.
3135 *
3136 * @param number
3137 * The number to look up.
3138 */
3139 _isEmergencyNumber: function(number) {
3140 // Check ril provided numbers first.
3141 let numbers = RIL_EMERGENCY_NUMBERS;
3143 if (numbers) {
3144 numbers = numbers.split(",");
3145 } else {
3146 // No ecclist system property, so use our own list.
3147 numbers = DEFAULT_EMERGENCY_NUMBERS;
3148 }
3150 return numbers.indexOf(number) != -1;
3151 },
3153 /**
3154 * Checks whether to temporarily suppress caller id for the call.
3155 *
3156 * @param mmi
3157 * MMI full object.
3158 */
3159 _isTemporaryModeCLIR: function(mmi) {
3160 return (mmi &&
3161 mmi.serviceCode == MMI_SC_CLIR &&
3162 mmi.dialNumber &&
3163 (mmi.procedure == MMI_PROCEDURE_ACTIVATION ||
3164 mmi.procedure == MMI_PROCEDURE_DEACTIVATION));
3165 },
3167 /**
3168 * Report STK Service is running.
3169 */
3170 reportStkServiceIsRunning: function() {
3171 this.context.Buf.simpleRequest(REQUEST_REPORT_STK_SERVICE_IS_RUNNING);
3172 },
3174 /**
3175 * Process ICC status.
3176 */
3177 _processICCStatus: function(iccStatus) {
3178 // If |_waitingRadioTech| is true, we should not get app information because
3179 // the |_isCdma| flag is not ready yet. Otherwise we may use wrong index to
3180 // get app information, especially for the case that icc card has both cdma
3181 // and gsm subscription.
3182 if (this._waitingRadioTech) {
3183 return;
3184 }
3186 this.iccStatus = iccStatus;
3187 let newCardState;
3188 let index = this._isCdma ? iccStatus.cdmaSubscriptionAppIndex :
3189 iccStatus.gsmUmtsSubscriptionAppIndex;
3190 let app = iccStatus.apps[index];
3192 // When |iccStatus.cardState| is not CARD_STATE_PRESENT or have incorrect
3193 // app information, we can not get iccId. So treat ICC as undetected.
3194 if (iccStatus.cardState !== CARD_STATE_PRESENT || !app) {
3195 if (this.cardState !== GECKO_CARDSTATE_UNDETECTED) {
3196 this.operator = null;
3197 // We should send |cardstatechange| before |iccinfochange|, otherwise we
3198 // may lost cardstatechange event when icc card becomes undetected.
3199 this.cardState = GECKO_CARDSTATE_UNDETECTED;
3200 this.sendChromeMessage({rilMessageType: "cardstatechange",
3201 cardState: this.cardState});
3203 this.iccInfo = {iccType: null};
3204 this.context.ICCUtilsHelper.handleICCInfoChange();
3205 }
3206 return;
3207 }
3209 let ICCRecordHelper = this.context.ICCRecordHelper;
3210 // fetchICCRecords will need to read aid, so read aid here.
3211 this.aid = app.aid;
3212 this.appType = app.app_type;
3213 this.iccInfo.iccType = GECKO_CARD_TYPE[this.appType];
3214 // Try to get iccId only when cardState left GECKO_CARDSTATE_UNDETECTED.
3215 if (iccStatus.cardState === CARD_STATE_PRESENT &&
3216 (this.cardState === GECKO_CARDSTATE_UNINITIALIZED ||
3217 this.cardState === GECKO_CARDSTATE_UNDETECTED)) {
3218 ICCRecordHelper.readICCID();
3219 }
3221 switch (app.app_state) {
3222 case CARD_APPSTATE_ILLEGAL:
3223 newCardState = GECKO_CARDSTATE_ILLEGAL;
3224 break;
3225 case CARD_APPSTATE_PIN:
3226 newCardState = GECKO_CARDSTATE_PIN_REQUIRED;
3227 break;
3228 case CARD_APPSTATE_PUK:
3229 newCardState = GECKO_CARDSTATE_PUK_REQUIRED;
3230 break;
3231 case CARD_APPSTATE_SUBSCRIPTION_PERSO:
3232 newCardState = PERSONSUBSTATE[app.perso_substate];
3233 break;
3234 case CARD_APPSTATE_READY:
3235 newCardState = GECKO_CARDSTATE_READY;
3236 break;
3237 case CARD_APPSTATE_UNKNOWN:
3238 case CARD_APPSTATE_DETECTED:
3239 // Fall through.
3240 default:
3241 newCardState = GECKO_CARDSTATE_UNKNOWN;
3242 }
3244 let pin1State = app.pin1_replaced ? iccStatus.universalPINState :
3245 app.pin1;
3246 if (pin1State === CARD_PINSTATE_ENABLED_PERM_BLOCKED) {
3247 newCardState = GECKO_CARDSTATE_PERMANENT_BLOCKED;
3248 }
3250 if (this.cardState == newCardState) {
3251 return;
3252 }
3254 // This was moved down from CARD_APPSTATE_READY
3255 this.requestNetworkInfo();
3256 if (newCardState == GECKO_CARDSTATE_READY) {
3257 // For type SIM, we need to check EF_phase first.
3258 // Other types of ICC we can send Terminal_Profile immediately.
3259 if (this.appType == CARD_APPTYPE_SIM) {
3260 this.context.SimRecordHelper.readSimPhase();
3261 } else if (RILQUIRKS_SEND_STK_PROFILE_DOWNLOAD) {
3262 this.sendStkTerminalProfile(STK_SUPPORTED_TERMINAL_PROFILE);
3263 }
3265 ICCRecordHelper.fetchICCRecords();
3266 }
3268 this.cardState = newCardState;
3269 this.sendChromeMessage({rilMessageType: "cardstatechange",
3270 cardState: this.cardState});
3271 },
3273 /**
3274 * Helper for processing responses of functions such as enterICC* and changeICC*.
3275 */
3276 _processEnterAndChangeICCResponses: function(length, options) {
3277 options.success = (options.rilRequestError === 0);
3278 if (!options.success) {
3279 options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError];
3280 }
3281 options.retryCount = length ? this.context.Buf.readInt32List()[0] : -1;
3282 if (options.rilMessageType != "sendMMI") {
3283 this.sendChromeMessage(options);
3284 return;
3285 }
3287 let mmiServiceCode = options.mmiServiceCode;
3289 if (options.success) {
3290 switch (mmiServiceCode) {
3291 case MMI_KS_SC_PIN:
3292 options.statusMessage = MMI_SM_KS_PIN_CHANGED;
3293 break;
3294 case MMI_KS_SC_PIN2:
3295 options.statusMessage = MMI_SM_KS_PIN2_CHANGED;
3296 break;
3297 case MMI_KS_SC_PUK:
3298 options.statusMessage = MMI_SM_KS_PIN_UNBLOCKED;
3299 break;
3300 case MMI_KS_SC_PUK2:
3301 options.statusMessage = MMI_SM_KS_PIN2_UNBLOCKED;
3302 break;
3303 }
3304 } else {
3305 if (options.retryCount <= 0) {
3306 if (mmiServiceCode === MMI_KS_SC_PUK) {
3307 options.errorMsg = MMI_ERROR_KS_SIM_BLOCKED;
3308 } else if (mmiServiceCode === MMI_KS_SC_PIN) {
3309 options.errorMsg = MMI_ERROR_KS_NEEDS_PUK;
3310 }
3311 } else {
3312 if (mmiServiceCode === MMI_KS_SC_PIN ||
3313 mmiServiceCode === MMI_KS_SC_PIN2) {
3314 options.errorMsg = MMI_ERROR_KS_BAD_PIN;
3315 } else if (mmiServiceCode === MMI_KS_SC_PUK ||
3316 mmiServiceCode === MMI_KS_SC_PUK2) {
3317 options.errorMsg = MMI_ERROR_KS_BAD_PUK;
3318 }
3319 if (options.retryCount !== undefined) {
3320 options.additionalInformation = options.retryCount;
3321 }
3322 }
3323 }
3325 this.sendChromeMessage(options);
3326 },
3328 // We combine all of the NETWORK_INFO_MESSAGE_TYPES into one "networkinfochange"
3329 // message to the RadioInterfaceLayer, so we can avoid sending multiple
3330 // VoiceInfoChanged events for both operator / voice_data_registration
3331 //
3332 // State management here is a little tricky. We need to know both:
3333 // 1. Whether or not a response was received for each of the
3334 // NETWORK_INFO_MESSAGE_TYPES
3335 // 2. The outbound message that corresponds with that response -- but this
3336 // only happens when internal state changes (i.e. it isn't guaranteed)
3337 //
3338 // To collect this state, each message response function first calls
3339 // _receivedNetworkInfo, to mark the response as received. When the
3340 // final response is received, a call to _sendPendingNetworkInfo is placed
3341 // on the next tick of the worker thread.
3342 //
3343 // Since the original call to _receivedNetworkInfo happens at the top
3344 // of the response handler, this gives the final handler a chance to
3345 // queue up it's "changed" message by calling _sendNetworkInfoMessage if/when
3346 // the internal state has actually changed.
3347 _sendNetworkInfoMessage: function(type, message) {
3348 if (!this._processingNetworkInfo) {
3349 // We only combine these messages in the case of the combined request
3350 // in requestNetworkInfo()
3351 this.sendChromeMessage(message);
3352 return;
3353 }
3355 if (DEBUG) {
3356 this.context.debug("Queuing " + type + " network info message: " +
3357 JSON.stringify(message));
3358 }
3359 this._pendingNetworkInfo[type] = message;
3360 },
3362 _receivedNetworkInfo: function(type) {
3363 if (DEBUG) this.context.debug("Received " + type + " network info.");
3364 if (!this._processingNetworkInfo) {
3365 return;
3366 }
3368 let pending = this._pendingNetworkInfo;
3370 // We still need to track states for events that aren't fired.
3371 if (!(type in pending)) {
3372 pending[type] = this.pendingNetworkType;
3373 }
3375 // Pending network info is ready to be sent when no more messages
3376 // are waiting for responses, but the combined payload hasn't been sent.
3377 for (let i = 0; i < NETWORK_INFO_MESSAGE_TYPES.length; i++) {
3378 let msgType = NETWORK_INFO_MESSAGE_TYPES[i];
3379 if (!(msgType in pending)) {
3380 if (DEBUG) {
3381 this.context.debug("Still missing some more network info, not " +
3382 "notifying main thread.");
3383 }
3384 return;
3385 }
3386 }
3388 // Do a pass to clean up the processed messages that didn't create
3389 // a response message, so we don't have unused keys in the outbound
3390 // networkinfochanged message.
3391 for (let key in pending) {
3392 if (pending[key] == this.pendingNetworkType) {
3393 delete pending[key];
3394 }
3395 }
3397 if (DEBUG) {
3398 this.context.debug("All pending network info has been received: " +
3399 JSON.stringify(pending));
3400 }
3402 // Send the message on the next tick of the worker's loop, so we give the
3403 // last message a chance to call _sendNetworkInfoMessage first.
3404 setTimeout(this._sendPendingNetworkInfo.bind(this), 0);
3405 },
3407 _sendPendingNetworkInfo: function() {
3408 this.sendChromeMessage(this._pendingNetworkInfo);
3410 this._processingNetworkInfo = false;
3411 for (let i = 0; i < NETWORK_INFO_MESSAGE_TYPES.length; i++) {
3412 delete this._pendingNetworkInfo[NETWORK_INFO_MESSAGE_TYPES[i]];
3413 }
3415 if (this._needRepollNetworkInfo) {
3416 this._needRepollNetworkInfo = false;
3417 this.requestNetworkInfo();
3418 }
3419 },
3421 /**
3422 * Normalize the signal strength in dBm to the signal level from 0 to 100.
3423 *
3424 * @param signal
3425 * The signal strength in dBm to normalize.
3426 * @param min
3427 * The signal strength in dBm maps to level 0.
3428 * @param max
3429 * The signal strength in dBm maps to level 100.
3430 *
3431 * @return level
3432 * The signal level from 0 to 100.
3433 */
3434 _processSignalLevel: function(signal, min, max) {
3435 if (signal <= min) {
3436 return 0;
3437 }
3439 if (signal >= max) {
3440 return 100;
3441 }
3443 return Math.floor((signal - min) * 100 / (max - min));
3444 },
3446 /**
3447 * Process LTE signal strength to the signal info object.
3448 *
3449 * @param signal
3450 * The signal object reported from RIL/modem.
3451 *
3452 * @return The object of signal strength info.
3453 * Or null if invalid signal input.
3454 */
3455 _processLteSignal: function(signal) {
3456 // Valid values are 0-63 as defined in TS 27.007 clause 8.69.
3457 if (signal.lteSignalStrength === undefined ||
3458 signal.lteSignalStrength < 0 ||
3459 signal.lteSignalStrength > 63) {
3460 return null;
3461 }
3463 let info = {
3464 voice: {
3465 signalStrength: null,
3466 relSignalStrength: null
3467 },
3468 data: {
3469 signalStrength: null,
3470 relSignalStrength: null
3471 }
3472 };
3474 // TODO: Bug 982013: reconsider signalStrength/relSignalStrength APIs for
3475 // GSM/CDMA/LTE, and take rsrp/rssnr into account for LTE case then.
3476 let signalStrength = -111 + signal.lteSignalStrength;
3477 info.voice.signalStrength = info.data.signalStrength = signalStrength;
3478 // 0 and 12 are referred to AOSP's implementation. These values are not
3479 // constants and can be customized based on different requirements.
3480 let signalLevel = this._processSignalLevel(signal.lteSignalStrength, 0, 12);
3481 info.voice.relSignalStrength = info.data.relSignalStrength = signalLevel;
3483 return info;
3484 },
3486 _processSignalStrength: function(signal) {
3487 let info = {
3488 voice: {
3489 signalStrength: null,
3490 relSignalStrength: null
3491 },
3492 data: {
3493 signalStrength: null,
3494 relSignalStrength: null
3495 }
3496 };
3498 // During startup, |radioTech| is not yet defined, so we need to
3499 // check it separately.
3500 if (("radioTech" in this.voiceRegistrationState) &&
3501 !this._isGsmTechGroup(this.voiceRegistrationState.radioTech)) {
3502 // CDMA RSSI.
3503 // Valid values are positive integers. This value is the actual RSSI value
3504 // multiplied by -1. Example: If the actual RSSI is -75, then this
3505 // response value will be 75.
3506 if (signal.cdmaDBM && signal.cdmaDBM > 0) {
3507 let signalStrength = -1 * signal.cdmaDBM;
3508 info.voice.signalStrength = signalStrength;
3510 // -105 and -70 are referred to AOSP's implementation. These values are
3511 // not constants and can be customized based on different requirement.
3512 let signalLevel = this._processSignalLevel(signalStrength, -105, -70);
3513 info.voice.relSignalStrength = signalLevel;
3514 }
3516 // EVDO RSSI.
3517 // Valid values are positive integers. This value is the actual RSSI value
3518 // multiplied by -1. Example: If the actual RSSI is -75, then this
3519 // response value will be 75.
3520 if (signal.evdoDBM && signal.evdoDBM > 0) {
3521 let signalStrength = -1 * signal.evdoDBM;
3522 info.data.signalStrength = signalStrength;
3524 // -105 and -70 are referred to AOSP's implementation. These values are
3525 // not constants and can be customized based on different requirement.
3526 let signalLevel = this._processSignalLevel(signalStrength, -105, -70);
3527 info.data.relSignalStrength = signalLevel;
3528 }
3529 } else {
3530 // Check LTE level first, and check GSM/UMTS level next if LTE one is not
3531 // valid.
3532 let lteInfo = this._processLteSignal(signal);
3533 if (lteInfo) {
3534 info = lteInfo;
3535 } else {
3536 // GSM signal strength.
3537 // Valid values are 0-31 as defined in TS 27.007 8.5.
3538 // 0 : -113 dBm or less
3539 // 1 : -111 dBm
3540 // 2...30: -109...-53 dBm
3541 // 31 : -51 dBm
3542 if (signal.gsmSignalStrength &&
3543 signal.gsmSignalStrength >= 0 &&
3544 signal.gsmSignalStrength <= 31) {
3545 let signalStrength = -113 + 2 * signal.gsmSignalStrength;
3546 info.voice.signalStrength = info.data.signalStrength = signalStrength;
3548 // -115 and -85 are referred to AOSP's implementation. These values are
3549 // not constants and can be customized based on different requirement.
3550 let signalLevel = this._processSignalLevel(signalStrength, -110, -85);
3551 info.voice.relSignalStrength = info.data.relSignalStrength = signalLevel;
3552 }
3553 }
3554 }
3556 info.rilMessageType = "signalstrengthchange";
3557 this._sendNetworkInfoMessage(NETWORK_INFO_SIGNAL, info);
3559 if (this.cachedDialRequest && info.voice.signalStrength) {
3560 // Radio is ready for making the cached emergency call.
3561 this.cachedDialRequest.callback();
3562 this.cachedDialRequest = null;
3563 }
3564 },
3566 /**
3567 * Process the network registration flags.
3568 *
3569 * @return true if the state changed, false otherwise.
3570 */
3571 _processCREG: function(curState, newState) {
3572 let changed = false;
3574 let regState = this.parseInt(newState[0], NETWORK_CREG_STATE_UNKNOWN);
3575 if (curState.regState === undefined || curState.regState !== regState) {
3576 changed = true;
3577 curState.regState = regState;
3579 curState.state = NETWORK_CREG_TO_GECKO_MOBILE_CONNECTION_STATE[regState];
3580 curState.connected = regState == NETWORK_CREG_STATE_REGISTERED_HOME ||
3581 regState == NETWORK_CREG_STATE_REGISTERED_ROAMING;
3582 curState.roaming = regState == NETWORK_CREG_STATE_REGISTERED_ROAMING;
3583 curState.emergencyCallsOnly = !curState.connected;
3584 }
3586 if (!curState.cell) {
3587 curState.cell = {};
3588 }
3590 // From TS 23.003, 0000 and 0xfffe are indicated that no valid LAI exists
3591 // in MS. So we still need to report the '0000' as well.
3592 let lac = this.parseInt(newState[1], -1, 16);
3593 if (curState.cell.gsmLocationAreaCode === undefined ||
3594 curState.cell.gsmLocationAreaCode !== lac) {
3595 curState.cell.gsmLocationAreaCode = lac;
3596 changed = true;
3597 }
3599 let cid = this.parseInt(newState[2], -1, 16);
3600 if (curState.cell.gsmCellId === undefined ||
3601 curState.cell.gsmCellId !== cid) {
3602 curState.cell.gsmCellId = cid;
3603 changed = true;
3604 }
3606 let radioTech = (newState[3] === undefined ?
3607 NETWORK_CREG_TECH_UNKNOWN :
3608 this.parseInt(newState[3], NETWORK_CREG_TECH_UNKNOWN));
3609 if (curState.radioTech === undefined || curState.radioTech !== radioTech) {
3610 changed = true;
3611 curState.radioTech = radioTech;
3612 curState.type = GECKO_RADIO_TECH[radioTech] || null;
3613 }
3614 return changed;
3615 },
3617 _processVoiceRegistrationState: function(state) {
3618 let rs = this.voiceRegistrationState;
3619 let stateChanged = this._processCREG(rs, state);
3620 if (stateChanged && rs.connected) {
3621 this.getSmscAddress();
3622 }
3624 let cell = rs.cell;
3625 if (this._isCdma) {
3626 // Some variables below are not used. Comment them instead of removing to
3627 // keep the information about state[x].
3628 let cdmaBaseStationId = this.parseInt(state[4], -1);
3629 let cdmaBaseStationLatitude = this.parseInt(state[5], -2147483648);
3630 let cdmaBaseStationLongitude = this.parseInt(state[6], -2147483648);
3631 // let cssIndicator = this.parseInt(state[7]);
3632 let cdmaSystemId = this.parseInt(state[8], -1);
3633 let cdmaNetworkId = this.parseInt(state[9], -1);
3634 // let roamingIndicator = this.parseInt(state[10]);
3635 // let systemIsInPRL = this.parseInt(state[11]);
3636 // let defaultRoamingIndicator = this.parseInt(state[12]);
3637 // let reasonForDenial = this.parseInt(state[13]);
3639 if (cell.cdmaBaseStationId !== cdmaBaseStationId ||
3640 cell.cdmaBaseStationLatitude !== cdmaBaseStationLatitude ||
3641 cell.cdmaBaseStationLongitude !== cdmaBaseStationLongitude ||
3642 cell.cdmaSystemId !== cdmaSystemId ||
3643 cell.cdmaNetworkId !== cdmaNetworkId) {
3644 stateChanged = true;
3645 cell.cdmaBaseStationId = cdmaBaseStationId;
3646 cell.cdmaBaseStationLatitude = cdmaBaseStationLatitude;
3647 cell.cdmaBaseStationLongitude = cdmaBaseStationLongitude;
3648 cell.cdmaSystemId = cdmaSystemId;
3649 cell.cdmaNetworkId = cdmaNetworkId;
3650 }
3651 }
3653 if (stateChanged) {
3654 rs.rilMessageType = "voiceregistrationstatechange";
3655 this._sendNetworkInfoMessage(NETWORK_INFO_VOICE_REGISTRATION_STATE, rs);
3656 }
3657 },
3659 _processDataRegistrationState: function(state) {
3660 let rs = this.dataRegistrationState;
3661 let stateChanged = this._processCREG(rs, state);
3662 if (stateChanged) {
3663 rs.rilMessageType = "dataregistrationstatechange";
3664 this._sendNetworkInfoMessage(NETWORK_INFO_DATA_REGISTRATION_STATE, rs);
3665 }
3666 },
3668 _processOperator: function(operatorData) {
3669 if (operatorData.length < 3) {
3670 if (DEBUG) {
3671 this.context.debug("Expected at least 3 strings for operator.");
3672 }
3673 }
3675 if (!this.operator) {
3676 this.operator = {
3677 rilMessageType: "operatorchange",
3678 longName: null,
3679 shortName: null
3680 };
3681 }
3683 let [longName, shortName, networkTuple] = operatorData;
3684 let thisTuple = (this.operator.mcc || "") + (this.operator.mnc || "");
3686 if (this.operator.longName !== longName ||
3687 this.operator.shortName !== shortName ||
3688 thisTuple !== networkTuple) {
3690 this.operator.mcc = null;
3691 this.operator.mnc = null;
3693 if (networkTuple) {
3694 try {
3695 this._processNetworkTuple(networkTuple, this.operator);
3696 } catch (e) {
3697 if (DEBUG) this.context.debug("Error processing operator tuple: " + e);
3698 }
3699 } else {
3700 // According to ril.h, the operator fields will be NULL when the operator
3701 // is not currently registered. We can avoid trying to parse the numeric
3702 // tuple in that case.
3703 if (DEBUG) {
3704 this.context.debug("Operator is currently unregistered");
3705 }
3706 }
3708 let ICCUtilsHelper = this.context.ICCUtilsHelper;
3709 let networkName;
3710 // We won't get network name using PNN and OPL if voice registration isn't ready
3711 if (this.voiceRegistrationState.cell &&
3712 this.voiceRegistrationState.cell.gsmLocationAreaCode != -1) {
3713 networkName = ICCUtilsHelper.getNetworkNameFromICC(
3714 this.operator.mcc,
3715 this.operator.mnc,
3716 this.voiceRegistrationState.cell.gsmLocationAreaCode);
3717 }
3719 if (networkName) {
3720 if (DEBUG) {
3721 this.context.debug("Operator names will be overriden: " +
3722 "longName = " + networkName.fullName + ", " +
3723 "shortName = " + networkName.shortName);
3724 }
3726 this.operator.longName = networkName.fullName;
3727 this.operator.shortName = networkName.shortName;
3728 } else {
3729 this.operator.longName = longName;
3730 this.operator.shortName = shortName;
3731 }
3733 if (ICCUtilsHelper.updateDisplayCondition()) {
3734 ICCUtilsHelper.handleICCInfoChange();
3735 }
3736 this._sendNetworkInfoMessage(NETWORK_INFO_OPERATOR, this.operator);
3737 }
3738 },
3740 /**
3741 * Helpers for processing call state and handle the active call.
3742 */
3743 _processCalls: function(newCalls) {
3744 let conferenceChanged = false;
3745 let clearConferenceRequest = false;
3746 let pendingOutgoingCall = null;
3748 // Go through the calls we currently have on file and see if any of them
3749 // changed state. Remove them from the newCalls map as we deal with them
3750 // so that only new calls remain in the map after we're done.
3751 for each (let currentCall in this.currentCalls) {
3752 if (currentCall.callIndex == OUTGOING_PLACEHOLDER_CALL_INDEX) {
3753 pendingOutgoingCall = currentCall;
3754 continue;
3755 }
3757 let newCall;
3758 if (newCalls) {
3759 newCall = newCalls[currentCall.callIndex];
3760 delete newCalls[currentCall.callIndex];
3761 }
3763 // Call is no longer reported by the radio. Remove from our map and send
3764 // disconnected state change.
3765 if (!newCall) {
3766 if (this.currentConference.participants[currentCall.callIndex]) {
3767 conferenceChanged = true;
3768 }
3769 this._removeVoiceCall(currentCall,
3770 currentCall.hangUpLocal ?
3771 GECKO_CALL_ERROR_NORMAL_CALL_CLEARING : null);
3772 continue;
3773 }
3775 // Call is still valid.
3776 if (newCall.state == currentCall.state &&
3777 newCall.isMpty == currentCall.isMpty) {
3778 continue;
3779 }
3781 // State has changed.
3782 if (newCall.state == CALL_STATE_INCOMING &&
3783 currentCall.state == CALL_STATE_WAITING) {
3784 // Update the call internally but we don't notify chrome since these two
3785 // states are viewed as the same one there.
3786 currentCall.state = newCall.state;
3787 continue;
3788 }
3790 if (!currentCall.started && newCall.state == CALL_STATE_ACTIVE) {
3791 currentCall.started = new Date().getTime();
3792 }
3794 if (currentCall.isMpty == newCall.isMpty &&
3795 newCall.state != currentCall.state) {
3796 currentCall.state = newCall.state;
3797 if (currentCall.isConference) {
3798 conferenceChanged = true;
3799 }
3800 this._handleChangedCallState(currentCall);
3801 continue;
3802 }
3804 // '.isMpty' becomes false when the conference call is put on hold.
3805 // We need to introduce additional 'isConference' to correctly record the
3806 // real conference status
3808 // Update a possible conference participant when .isMpty changes.
3809 if (!currentCall.isMpty && newCall.isMpty) {
3810 if (this._hasConferenceRequest) {
3811 conferenceChanged = true;
3812 clearConferenceRequest = true;
3813 currentCall.state = newCall.state;
3814 currentCall.isMpty = newCall.isMpty;
3815 currentCall.isConference = true;
3816 this.currentConference.participants[currentCall.callIndex] = currentCall;
3817 this._handleChangedCallState(currentCall);
3818 } else if (currentCall.isConference) {
3819 // The case happens when resuming a held conference call.
3820 conferenceChanged = true;
3821 currentCall.state = newCall.state;
3822 currentCall.isMpty = newCall.isMpty;
3823 this.currentConference.participants[currentCall.callIndex] = currentCall;
3824 this._handleChangedCallState(currentCall);
3825 } else {
3826 // Weird. This sometimes happens when we switch two calls, but it is
3827 // not a conference call.
3828 currentCall.state = newCall.state;
3829 this._handleChangedCallState(currentCall);
3830 }
3831 } else if (currentCall.isMpty && !newCall.isMpty) {
3832 if (!this.currentConference.participants[newCall.callIndex]) {
3833 continue;
3834 }
3836 // '.isMpty' of a conference participant is set to false by rild when
3837 // the conference call is put on hold. We don't actually know if the call
3838 // still attends the conference until updating all calls finishes. We
3839 // cache it for further determination.
3840 if (newCall.state != CALL_STATE_HOLDING) {
3841 delete this.currentConference.participants[newCall.callIndex];
3842 currentCall.state = newCall.state;
3843 currentCall.isMpty = newCall.isMpty;
3844 currentCall.isConference = false;
3845 conferenceChanged = true;
3846 this._handleChangedCallState(currentCall);
3847 continue;
3848 }
3850 if (!this.currentConference.cache) {
3851 this.currentConference.cache = {};
3852 }
3853 this.currentConference.cache[currentCall.callIndex] = newCall;
3854 currentCall.state = newCall.state;
3855 currentCall.isMpty = newCall.isMpty;
3856 conferenceChanged = true;
3857 }
3858 }
3860 if (pendingOutgoingCall) {
3861 if (!newCalls || Object.keys(newCalls).length === 0) {
3862 // We don't get a successful call for pendingOutgoingCall.
3863 this._removePendingOutgoingCall(GECKO_CALL_ERROR_UNSPECIFIED);
3864 } else {
3865 // Only remove it from currentCalls map. Will use the new call to
3866 // replace the placeholder.
3867 delete this.currentCalls[OUTGOING_PLACEHOLDER_CALL_INDEX];
3868 }
3869 }
3871 // Go through any remaining calls that are new to us.
3872 for each (let newCall in newCalls) {
3873 if (newCall.isVoice) {
3874 if (newCall.isMpty) {
3875 conferenceChanged = true;
3876 }
3877 if (!pendingOutgoingCall &&
3878 (newCall.state === CALL_STATE_DIALING ||
3879 newCall.state === CALL_STATE_ALERTING)) {
3880 // Receive a new outgoing call which is already hung up by user.
3881 if (DEBUG) this.context.debug("Pending outgoing call is hung up by user.");
3882 this.sendHangUpRequest(newCall.callIndex);
3883 } else {
3884 this._addNewVoiceCall(newCall);
3885 }
3886 }
3887 }
3889 if (clearConferenceRequest) {
3890 this._hasConferenceRequest = false;
3891 }
3892 if (conferenceChanged) {
3893 this._ensureConference();
3894 }
3895 },
3897 _addNewVoiceCall: function(newCall) {
3898 // Format international numbers appropriately.
3899 if (newCall.number && newCall.toa == TOA_INTERNATIONAL &&
3900 newCall.number[0] != "+") {
3901 newCall.number = "+" + newCall.number;
3902 }
3904 if (newCall.state == CALL_STATE_INCOMING) {
3905 newCall.isOutgoing = false;
3906 } else if (newCall.state == CALL_STATE_DIALING) {
3907 newCall.isOutgoing = true;
3908 }
3910 // Set flag for outgoing emergency call.
3911 newCall.isEmergency = newCall.isOutgoing &&
3912 this._isEmergencyNumber(newCall.number);
3914 // Set flag for conference.
3915 newCall.isConference = newCall.isMpty ? true : false;
3917 // Add to our map.
3918 if (newCall.isMpty) {
3919 this.currentConference.participants[newCall.callIndex] = newCall;
3920 }
3921 this._handleChangedCallState(newCall);
3922 this.currentCalls[newCall.callIndex] = newCall;
3923 },
3925 _removeVoiceCall: function(removedCall, failCause) {
3926 if (this.currentConference.participants[removedCall.callIndex]) {
3927 removedCall.isConference = false;
3928 delete this.currentConference.participants[removedCall.callIndex];
3929 delete this.currentCalls[removedCall.callIndex];
3930 // We don't query the fail cause here as it triggers another asynchrouns
3931 // request that leads to a problem of updating all conferece participants
3932 // in one task.
3933 this._handleDisconnectedCall(removedCall);
3934 } else {
3935 delete this.currentCalls[removedCall.callIndex];
3936 if (failCause) {
3937 removedCall.failCause = failCause;
3938 this._handleDisconnectedCall(removedCall);
3939 } else {
3940 this.getFailCauseCode((function(call, failCause) {
3941 call.failCause = failCause;
3942 this._handleDisconnectedCall(call);
3943 }).bind(this, removedCall));
3944 }
3945 }
3946 },
3948 _createPendingOutgoingCall: function(options) {
3949 if (DEBUG) this.context.debug("Create a pending outgoing call.");
3950 this._addNewVoiceCall({
3951 number: options.number,
3952 state: CALL_STATE_DIALING,
3953 callIndex: OUTGOING_PLACEHOLDER_CALL_INDEX
3954 });
3955 },
3957 _removePendingOutgoingCall: function(failCause) {
3958 let call = this.currentCalls[OUTGOING_PLACEHOLDER_CALL_INDEX];
3959 if (!call) {
3960 return;
3961 }
3963 if (DEBUG) this.context.debug("Remove pending outgoing call.");
3964 this._removeVoiceCall(pendingOutgoingCall, failCause);
3965 },
3967 _ensureConference: function() {
3968 let oldState = this.currentConference.state;
3969 let remaining = Object.keys(this.currentConference.participants);
3971 if (remaining.length == 1) {
3972 // Remove that if only does one remain in a conference call.
3973 let call = this.currentCalls[remaining[0]];
3974 call.isConference = false;
3975 this._handleChangedCallState(call);
3976 delete this.currentConference.participants[call.callIndex];
3977 } else if (remaining.length > 1) {
3978 for each (let call in this.currentConference.cache) {
3979 call.isConference = true;
3980 this.currentConference.participants[call.callIndex] = call;
3981 this.currentCalls[call.callIndex] = call;
3982 this._handleChangedCallState(call);
3983 }
3984 }
3985 delete this.currentConference.cache;
3987 // Update the conference call's state.
3988 let state = CALL_STATE_UNKNOWN;
3989 for each (let call in this.currentConference.participants) {
3990 if (state != CALL_STATE_UNKNOWN && state != call.state) {
3991 // Each participant should have the same state, otherwise something
3992 // wrong happens.
3993 state = CALL_STATE_UNKNOWN;
3994 break;
3995 }
3996 state = call.state;
3997 }
3998 if (oldState != state) {
3999 this.currentConference.state = state;
4000 let message = {rilMessageType: "conferenceCallStateChanged",
4001 state: state};
4002 this.sendChromeMessage(message);
4003 }
4004 },
4006 _handleChangedCallState: function(changedCall) {
4007 let message = {rilMessageType: "callStateChange",
4008 call: changedCall};
4009 this.sendChromeMessage(message);
4010 },
4012 _handleDisconnectedCall: function(disconnectedCall) {
4013 let message = {rilMessageType: "callDisconnected",
4014 call: disconnectedCall};
4015 this.sendChromeMessage(message);
4016 },
4018 _sendDataCallError: function(message, errorCode) {
4019 // Should not include token for unsolicited response.
4020 delete message.rilMessageToken;
4021 message.rilMessageType = "datacallerror";
4022 if (errorCode == ERROR_GENERIC_FAILURE) {
4023 message.errorMsg = RIL_ERROR_TO_GECKO_ERROR[errorCode];
4024 } else {
4025 message.errorMsg = RIL_DATACALL_FAILCAUSE_TO_GECKO_DATACALL_ERROR[errorCode];
4026 }
4027 this.sendChromeMessage(message);
4028 },
4030 /**
4031 * @return "deactivate" if <ifname> changes or one of the currentDataCall
4032 * addresses is missing in updatedDataCall, or "identical" if no
4033 * changes found, or "changed" otherwise.
4034 */
4035 _compareDataCallLink: function(updatedDataCall, currentDataCall) {
4036 // If network interface is changed, report as "deactivate".
4037 if (updatedDataCall.ifname != currentDataCall.ifname) {
4038 return "deactivate";
4039 }
4041 // If any existing address is missing, report as "deactivate".
4042 for (let i = 0; i < currentDataCall.addresses.length; i++) {
4043 let address = currentDataCall.addresses[i];
4044 if (updatedDataCall.addresses.indexOf(address) < 0) {
4045 return "deactivate";
4046 }
4047 }
4049 if (currentDataCall.addresses.length != updatedDataCall.addresses.length) {
4050 // Since now all |currentDataCall.addresses| are found in
4051 // |updatedDataCall.addresses|, this means one or more new addresses are
4052 // reported.
4053 return "changed";
4054 }
4056 let fields = ["gateways", "dnses"];
4057 for (let i = 0; i < fields.length; i++) {
4058 // Compare <datacall>.<field>.
4059 let field = fields[i];
4060 let lhs = updatedDataCall[field], rhs = currentDataCall[field];
4061 if (lhs.length != rhs.length) {
4062 return "changed";
4063 }
4064 for (let i = 0; i < lhs.length; i++) {
4065 if (lhs[i] != rhs[i]) {
4066 return "changed";
4067 }
4068 }
4069 }
4071 return "identical";
4072 },
4074 _processDataCallList: function(datacalls, newDataCallOptions) {
4075 // Check for possible PDP errors: We check earlier because the datacall
4076 // can be removed if is the same as the current one.
4077 for each (let newDataCall in datacalls) {
4078 if (newDataCall.status != DATACALL_FAIL_NONE) {
4079 if (newDataCallOptions) {
4080 newDataCall.apn = newDataCallOptions.apn;
4081 }
4082 this._sendDataCallError(newDataCall, newDataCall.status);
4083 }
4084 }
4086 for each (let currentDataCall in this.currentDataCalls) {
4087 let updatedDataCall;
4088 if (datacalls) {
4089 updatedDataCall = datacalls[currentDataCall.cid];
4090 delete datacalls[currentDataCall.cid];
4091 }
4093 if (!updatedDataCall) {
4094 // If datacalls list is coming from REQUEST_SETUP_DATA_CALL response,
4095 // we do not change state for any currentDataCalls not in datacalls list.
4096 if (!newDataCallOptions) {
4097 delete this.currentDataCalls[currentDataCall.cid];
4098 currentDataCall.state = GECKO_NETWORK_STATE_DISCONNECTED;
4099 currentDataCall.rilMessageType = "datacallstatechange";
4100 this.sendChromeMessage(currentDataCall);
4101 }
4102 continue;
4103 }
4105 if (updatedDataCall && !updatedDataCall.ifname) {
4106 delete this.currentDataCalls[currentDataCall.cid];
4107 currentDataCall.state = GECKO_NETWORK_STATE_UNKNOWN;
4108 currentDataCall.rilMessageType = "datacallstatechange";
4109 this.sendChromeMessage(currentDataCall);
4110 continue;
4111 }
4113 this._setDataCallGeckoState(updatedDataCall);
4114 if (updatedDataCall.state != currentDataCall.state) {
4115 if (updatedDataCall.state == GECKO_NETWORK_STATE_DISCONNECTED) {
4116 delete this.currentDataCalls[currentDataCall.cid];
4117 }
4118 currentDataCall.status = updatedDataCall.status;
4119 currentDataCall.active = updatedDataCall.active;
4120 currentDataCall.state = updatedDataCall.state;
4121 currentDataCall.rilMessageType = "datacallstatechange";
4122 this.sendChromeMessage(currentDataCall);
4123 continue;
4124 }
4126 // State not changed, now check links.
4127 let result =
4128 this._compareDataCallLink(updatedDataCall, currentDataCall);
4129 if (result == "identical") {
4130 if (DEBUG) this.context.debug("No changes in data call.");
4131 continue;
4132 }
4133 if (result == "deactivate") {
4134 if (DEBUG) this.context.debug("Data link changed, cleanup.");
4135 this.deactivateDataCall(currentDataCall);
4136 continue;
4137 }
4138 // Minor change, just update and notify.
4139 if (DEBUG) {
4140 this.context.debug("Data link minor change, just update and notify.");
4141 }
4142 currentDataCall.addresses = updatedDataCall.addresses.slice();
4143 currentDataCall.dnses = updatedDataCall.dnses.slice();
4144 currentDataCall.gateways = updatedDataCall.gateways.slice();
4145 currentDataCall.rilMessageType = "datacallstatechange";
4146 this.sendChromeMessage(currentDataCall);
4147 }
4149 for each (let newDataCall in datacalls) {
4150 if (!newDataCall.ifname) {
4151 continue;
4152 }
4154 if (!newDataCallOptions) {
4155 if (DEBUG) {
4156 this.context.debug("Unexpected new data call: " +
4157 JSON.stringify(newDataCall));
4158 }
4159 continue;
4160 }
4162 this.currentDataCalls[newDataCall.cid] = newDataCall;
4163 this._setDataCallGeckoState(newDataCall);
4165 newDataCall.radioTech = newDataCallOptions.radioTech;
4166 newDataCall.apn = newDataCallOptions.apn;
4167 newDataCall.user = newDataCallOptions.user;
4168 newDataCall.passwd = newDataCallOptions.passwd;
4169 newDataCall.chappap = newDataCallOptions.chappap;
4170 newDataCall.pdptype = newDataCallOptions.pdptype;
4171 newDataCallOptions = null;
4173 newDataCall.rilMessageType = "datacallstatechange";
4174 this.sendChromeMessage(newDataCall);
4175 }
4176 },
4178 _setDataCallGeckoState: function(datacall) {
4179 switch (datacall.active) {
4180 case DATACALL_INACTIVE:
4181 datacall.state = GECKO_NETWORK_STATE_DISCONNECTED;
4182 break;
4183 case DATACALL_ACTIVE_DOWN:
4184 case DATACALL_ACTIVE_UP:
4185 datacall.state = GECKO_NETWORK_STATE_CONNECTED;
4186 break;
4187 }
4188 },
4190 _processSuppSvcNotification: function(info) {
4191 if (DEBUG) {
4192 this.context.debug("handle supp svc notification: " + JSON.stringify(info));
4193 }
4195 if (info.notificationType !== 1) {
4196 // We haven't supported MO intermediate result code, i.e.
4197 // info.notificationType === 0, which refers to code1 defined in 3GPP
4198 // 27.007 7.17. We only support partial MT unsolicited result code,
4199 // referring to code2, for now.
4200 return;
4201 }
4203 let notification = null;
4204 let callIndex = -1;
4206 switch (info.code) {
4207 case SUPP_SVC_NOTIFICATION_CODE2_PUT_ON_HOLD:
4208 case SUPP_SVC_NOTIFICATION_CODE2_RETRIEVED:
4209 notification = GECKO_SUPP_SVC_NOTIFICATION_FROM_CODE2[info.code];
4210 break;
4211 default:
4212 // Notification type not supported.
4213 return;
4214 }
4216 // Get the target call object for this notification.
4217 let currentCallIndexes = Object.keys(this.currentCalls);
4218 if (currentCallIndexes.length === 1) {
4219 // Only one call exists. This should be the target.
4220 callIndex = currentCallIndexes[0];
4221 } else {
4222 // Find the call in |currentCalls| by the given number.
4223 if (info.number) {
4224 for each (let currentCall in this.currentCalls) {
4225 if (currentCall.number == info.number) {
4226 callIndex = currentCall.callIndex;
4227 break;
4228 }
4229 }
4230 }
4231 }
4233 let message = {rilMessageType: "suppSvcNotification",
4234 notification: notification,
4235 callIndex: callIndex};
4236 this.sendChromeMessage(message);
4237 },
4239 _cancelEmergencyCbModeTimeout: function() {
4240 if (this._exitEmergencyCbModeTimeoutID) {
4241 clearTimeout(this._exitEmergencyCbModeTimeoutID);
4242 this._exitEmergencyCbModeTimeoutID = null;
4243 }
4244 },
4246 _handleChangedEmergencyCbMode: function(active) {
4247 this._isInEmergencyCbMode = active;
4249 // Clear the existed timeout event.
4250 this._cancelEmergencyCbModeTimeout();
4252 // Start a new timeout event when entering the mode.
4253 if (active) {
4254 this._exitEmergencyCbModeTimeoutID = setTimeout(
4255 this.exitEmergencyCbMode.bind(this), EMERGENCY_CB_MODE_TIMEOUT_MS);
4256 }
4258 let message = {rilMessageType: "emergencyCbModeChange",
4259 active: active,
4260 timeoutMs: EMERGENCY_CB_MODE_TIMEOUT_MS};
4261 this.sendChromeMessage(message);
4262 },
4264 _processNetworks: function() {
4265 let strings = this.context.Buf.readStringList();
4266 let networks = [];
4268 for (let i = 0; i < strings.length; i += 4) {
4269 let network = {
4270 longName: strings[i],
4271 shortName: strings[i + 1],
4272 mcc: null,
4273 mnc: null,
4274 state: null
4275 };
4277 let networkTuple = strings[i + 2];
4278 try {
4279 this._processNetworkTuple(networkTuple, network);
4280 } catch (e) {
4281 if (DEBUG) this.context.debug("Error processing operator tuple: " + e);
4282 }
4284 let state = strings[i + 3];
4285 if (state === NETWORK_STATE_UNKNOWN) {
4286 // TODO: looks like this might conflict in style with
4287 // GECKO_NETWORK_STYLE_UNKNOWN / nsINetworkManager
4288 state = GECKO_QAN_STATE_UNKNOWN;
4289 }
4291 network.state = state;
4292 networks.push(network);
4293 }
4294 return networks;
4295 },
4297 /**
4298 * The "numeric" portion of the operator info is a tuple
4299 * containing MCC (country code) and MNC (network code).
4300 * AFAICT, MCC should always be 3 digits, making the remaining
4301 * portion the MNC.
4302 */
4303 _processNetworkTuple: function(networkTuple, network) {
4304 let tupleLen = networkTuple.length;
4306 if (tupleLen == 5 || tupleLen == 6) {
4307 network.mcc = networkTuple.substr(0, 3);
4308 network.mnc = networkTuple.substr(3);
4309 } else {
4310 network.mcc = null;
4311 network.mnc = null;
4313 throw new Error("Invalid network tuple (should be 5 or 6 digits): " + networkTuple);
4314 }
4315 },
4317 /**
4318 * Check if GSM radio access technology group.
4319 */
4320 _isGsmTechGroup: function(radioTech) {
4321 if (!radioTech) {
4322 return true;
4323 }
4325 switch(radioTech) {
4326 case NETWORK_CREG_TECH_GPRS:
4327 case NETWORK_CREG_TECH_EDGE:
4328 case NETWORK_CREG_TECH_UMTS:
4329 case NETWORK_CREG_TECH_HSDPA:
4330 case NETWORK_CREG_TECH_HSUPA:
4331 case NETWORK_CREG_TECH_HSPA:
4332 case NETWORK_CREG_TECH_LTE:
4333 case NETWORK_CREG_TECH_HSPAP:
4334 case NETWORK_CREG_TECH_GSM:
4335 return true;
4336 }
4338 return false;
4339 },
4341 /**
4342 * Process radio technology change.
4343 */
4344 _processRadioTech: function(radioTech) {
4345 let isCdma = !this._isGsmTechGroup(radioTech);
4346 this.radioTech = radioTech;
4348 if (DEBUG) {
4349 this.context.debug("Radio tech is set to: " + GECKO_RADIO_TECH[radioTech] +
4350 ", it is a " + (isCdma?"cdma":"gsm") + " technology");
4351 }
4353 // We should request SIM information when
4354 // 1. Radio state has been changed, so we are waiting for radioTech or
4355 // 2. isCdma is different from this._isCdma.
4356 if (this._waitingRadioTech || isCdma != this._isCdma) {
4357 this._isCdma = isCdma;
4358 this._waitingRadioTech = false;
4359 if (this._isCdma) {
4360 this.getDeviceIdentity();
4361 } else {
4362 this.getIMEI();
4363 this.getIMEISV();
4364 }
4365 this.getICCStatus();
4366 }
4367 },
4369 /**
4370 * Helper for returning the TOA for the given dial string.
4371 */
4372 _toaFromString: function(number) {
4373 let toa = TOA_UNKNOWN;
4374 if (number && number.length > 0 && number[0] == '+') {
4375 toa = TOA_INTERNATIONAL;
4376 }
4377 return toa;
4378 },
4380 /**
4381 * Helper for translating basic service group to call forwarding service class
4382 * parameter.
4383 */
4384 _siToServiceClass: function(si) {
4385 if (!si) {
4386 return ICC_SERVICE_CLASS_NONE;
4387 }
4389 let serviceCode = parseInt(si, 10);
4390 switch (serviceCode) {
4391 case 10:
4392 return ICC_SERVICE_CLASS_SMS + ICC_SERVICE_CLASS_FAX + ICC_SERVICE_CLASS_VOICE;
4393 case 11:
4394 return ICC_SERVICE_CLASS_VOICE;
4395 case 12:
4396 return ICC_SERVICE_CLASS_SMS + ICC_SERVICE_CLASS_FAX;
4397 case 13:
4398 return ICC_SERVICE_CLASS_FAX;
4399 case 16:
4400 return ICC_SERVICE_CLASS_SMS;
4401 case 19:
4402 return ICC_SERVICE_CLASS_FAX + ICC_SERVICE_CLASS_VOICE;
4403 case 21:
4404 return ICC_SERVICE_CLASS_PAD + ICC_SERVICE_CLASS_DATA_ASYNC;
4405 case 22:
4406 return ICC_SERVICE_CLASS_PACKET + ICC_SERVICE_CLASS_DATA_SYNC;
4407 case 25:
4408 return ICC_SERVICE_CLASS_DATA_ASYNC;
4409 case 26:
4410 return ICC_SERVICE_CLASS_DATA_SYNC + SERVICE_CLASS_VOICE;
4411 case 99:
4412 return ICC_SERVICE_CLASS_PACKET;
4413 default:
4414 return ICC_SERVICE_CLASS_NONE;
4415 }
4416 },
4418 /**
4419 * @param message A decoded SMS-DELIVER message.
4420 *
4421 * @see 3GPP TS 31.111 section 7.1.1
4422 */
4423 dataDownloadViaSMSPP: function(message) {
4424 let Buf = this.context.Buf;
4425 let GsmPDUHelper = this.context.GsmPDUHelper;
4427 let options = {
4428 pid: message.pid,
4429 dcs: message.dcs,
4430 encoding: message.encoding,
4431 };
4432 Buf.newParcel(REQUEST_STK_SEND_ENVELOPE_WITH_STATUS, options);
4434 Buf.seekIncoming(-1 * (Buf.getCurrentParcelSize() - Buf.getReadAvailable()
4435 - 2 * Buf.UINT32_SIZE)); // Skip response_type & request_type.
4436 let messageStringLength = Buf.readInt32(); // In semi-octets
4437 let smscLength = GsmPDUHelper.readHexOctet(); // In octets, inclusive of TOA
4438 let tpduLength = (messageStringLength / 2) - (smscLength + 1); // In octets
4440 // Device identities: 4 bytes
4441 // Address: 0 or (2 + smscLength)
4442 // SMS TPDU: (2 or 3) + tpduLength
4443 let berLen = 4 +
4444 (smscLength ? (2 + smscLength) : 0) +
4445 (tpduLength <= 127 ? 2 : 3) + tpduLength; // In octets
4447 let parcelLength = (berLen <= 127 ? 2 : 3) + berLen; // In octets
4448 Buf.writeInt32(parcelLength * 2); // In semi-octets
4450 // Write a BER-TLV
4451 GsmPDUHelper.writeHexOctet(BER_SMS_PP_DOWNLOAD_TAG);
4452 if (berLen > 127) {
4453 GsmPDUHelper.writeHexOctet(0x81);
4454 }
4455 GsmPDUHelper.writeHexOctet(berLen);
4457 // Device Identifies-TLV
4458 GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_DEVICE_ID |
4459 COMPREHENSIONTLV_FLAG_CR);
4460 GsmPDUHelper.writeHexOctet(0x02);
4461 GsmPDUHelper.writeHexOctet(STK_DEVICE_ID_NETWORK);
4462 GsmPDUHelper.writeHexOctet(STK_DEVICE_ID_SIM);
4464 // Address-TLV
4465 if (smscLength) {
4466 GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_ADDRESS);
4467 GsmPDUHelper.writeHexOctet(smscLength);
4468 Buf.copyIncomingToOutgoing(Buf.PDU_HEX_OCTET_SIZE * smscLength);
4469 }
4471 // SMS TPDU-TLV
4472 GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_SMS_TPDU |
4473 COMPREHENSIONTLV_FLAG_CR);
4474 if (tpduLength > 127) {
4475 GsmPDUHelper.writeHexOctet(0x81);
4476 }
4477 GsmPDUHelper.writeHexOctet(tpduLength);
4478 Buf.copyIncomingToOutgoing(Buf.PDU_HEX_OCTET_SIZE * tpduLength);
4480 // Write 2 string delimitors for the total string length must be even.
4481 Buf.writeStringDelimiter(0);
4483 Buf.sendParcel();
4484 },
4486 /**
4487 * @param success A boolean value indicating the result of previous
4488 * SMS-DELIVER message handling.
4489 * @param responsePduLen ICC IO response PDU length in octets.
4490 * @param options An object that contains four attributes: `pid`, `dcs`,
4491 * `encoding` and `responsePduLen`.
4492 *
4493 * @see 3GPP TS 23.040 section 9.2.2.1a
4494 */
4495 acknowledgeIncomingGsmSmsWithPDU: function(success, responsePduLen, options) {
4496 let Buf = this.context.Buf;
4497 let GsmPDUHelper = this.context.GsmPDUHelper;
4499 Buf.newParcel(REQUEST_ACKNOWLEDGE_INCOMING_GSM_SMS_WITH_PDU);
4501 // Two strings.
4502 Buf.writeInt32(2);
4504 // String 1: Success
4505 Buf.writeString(success ? "1" : "0");
4507 // String 2: RP-ACK/RP-ERROR PDU
4508 Buf.writeInt32(2 * (responsePduLen + (success ? 5 : 6))); // In semi-octet
4509 // 1. TP-MTI & TP-UDHI
4510 GsmPDUHelper.writeHexOctet(PDU_MTI_SMS_DELIVER);
4511 if (!success) {
4512 // 2. TP-FCS
4513 GsmPDUHelper.writeHexOctet(PDU_FCS_USIM_DATA_DOWNLOAD_ERROR);
4514 }
4515 // 3. TP-PI
4516 GsmPDUHelper.writeHexOctet(PDU_PI_USER_DATA_LENGTH |
4517 PDU_PI_DATA_CODING_SCHEME |
4518 PDU_PI_PROTOCOL_IDENTIFIER);
4519 // 4. TP-PID
4520 GsmPDUHelper.writeHexOctet(options.pid);
4521 // 5. TP-DCS
4522 GsmPDUHelper.writeHexOctet(options.dcs);
4523 // 6. TP-UDL
4524 if (options.encoding == PDU_DCS_MSG_CODING_7BITS_ALPHABET) {
4525 GsmPDUHelper.writeHexOctet(Math.floor(responsePduLen * 8 / 7));
4526 } else {
4527 GsmPDUHelper.writeHexOctet(responsePduLen);
4528 }
4529 // TP-UD
4530 Buf.copyIncomingToOutgoing(Buf.PDU_HEX_OCTET_SIZE * responsePduLen);
4531 // Write 2 string delimitors for the total string length must be even.
4532 Buf.writeStringDelimiter(0);
4534 Buf.sendParcel();
4535 },
4537 /**
4538 * @param message A decoded SMS-DELIVER message.
4539 */
4540 writeSmsToSIM: function(message) {
4541 let Buf = this.context.Buf;
4542 let GsmPDUHelper = this.context.GsmPDUHelper;
4544 Buf.newParcel(REQUEST_WRITE_SMS_TO_SIM);
4546 // Write EFsms Status
4547 Buf.writeInt32(EFSMS_STATUS_FREE);
4549 Buf.seekIncoming(-1 * (Buf.getCurrentParcelSize() - Buf.getReadAvailable()
4550 - 2 * Buf.UINT32_SIZE)); // Skip response_type & request_type.
4551 let messageStringLength = Buf.readInt32(); // In semi-octets
4552 let smscLength = GsmPDUHelper.readHexOctet(); // In octets, inclusive of TOA
4553 let pduLength = (messageStringLength / 2) - (smscLength + 1); // In octets
4555 // 1. Write PDU first.
4556 if (smscLength > 0) {
4557 Buf.seekIncoming(smscLength * Buf.PDU_HEX_OCTET_SIZE);
4558 }
4559 // Write EFsms PDU string length
4560 Buf.writeInt32(2 * pduLength); // In semi-octets
4561 if (pduLength) {
4562 Buf.copyIncomingToOutgoing(Buf.PDU_HEX_OCTET_SIZE * pduLength);
4563 }
4564 // Write 2 string delimitors for the total string length must be even.
4565 Buf.writeStringDelimiter(0);
4567 // 2. Write SMSC
4568 // Write EFsms SMSC string length
4569 Buf.writeInt32(2 * (smscLength + 1)); // Plus smscLength itself, in semi-octets
4570 // Write smscLength
4571 GsmPDUHelper.writeHexOctet(smscLength);
4572 // Write TOA & SMSC Address
4573 if (smscLength) {
4574 Buf.seekIncoming(-1 * (Buf.getCurrentParcelSize() - Buf.getReadAvailable()
4575 - 2 * Buf.UINT32_SIZE // Skip response_type, request_type.
4576 - 2 * Buf.PDU_HEX_OCTET_SIZE)); // Skip messageStringLength & smscLength.
4577 Buf.copyIncomingToOutgoing(Buf.PDU_HEX_OCTET_SIZE * smscLength);
4578 }
4579 // Write 2 string delimitors for the total string length must be even.
4580 Buf.writeStringDelimiter(0);
4582 Buf.sendParcel();
4583 },
4585 /**
4586 * Helper to delegate the received sms segment to RadioInterface to process.
4587 *
4588 * @param message
4589 * Received sms message.
4590 *
4591 * @return MOZ_FCS_WAIT_FOR_EXPLICIT_ACK
4592 */
4593 _processSmsMultipart: function(message) {
4594 message.rilMessageType = "sms-received";
4596 this.sendChromeMessage(message);
4598 return MOZ_FCS_WAIT_FOR_EXPLICIT_ACK;
4599 },
4601 /**
4602 * Helper for processing SMS-STATUS-REPORT PDUs.
4603 *
4604 * @param length
4605 * Length of SMS string in the incoming parcel.
4606 *
4607 * @return A failure cause defined in 3GPP 23.040 clause 9.2.3.22.
4608 */
4609 _processSmsStatusReport: function(length) {
4610 let [message, result] = this.context.GsmPDUHelper.processReceivedSms(length);
4611 if (!message) {
4612 if (DEBUG) this.context.debug("invalid SMS-STATUS-REPORT");
4613 return PDU_FCS_UNSPECIFIED;
4614 }
4616 let options = this._pendingSentSmsMap[message.messageRef];
4617 if (!options) {
4618 if (DEBUG) this.context.debug("no pending SMS-SUBMIT message");
4619 return PDU_FCS_OK;
4620 }
4622 let status = message.status;
4624 // 3GPP TS 23.040 9.2.3.15 `The MS shall interpret any reserved values as
4625 // "Service Rejected"(01100011) but shall store them exactly as received.`
4626 if ((status >= 0x80)
4627 || ((status >= PDU_ST_0_RESERVED_BEGIN)
4628 && (status < PDU_ST_0_SC_SPECIFIC_BEGIN))
4629 || ((status >= PDU_ST_1_RESERVED_BEGIN)
4630 && (status < PDU_ST_1_SC_SPECIFIC_BEGIN))
4631 || ((status >= PDU_ST_2_RESERVED_BEGIN)
4632 && (status < PDU_ST_2_SC_SPECIFIC_BEGIN))
4633 || ((status >= PDU_ST_3_RESERVED_BEGIN)
4634 && (status < PDU_ST_3_SC_SPECIFIC_BEGIN))
4635 ) {
4636 status = PDU_ST_3_SERVICE_REJECTED;
4637 }
4639 // Pending. Waiting for next status report.
4640 if ((status >>> 5) == 0x01) {
4641 if (DEBUG) this.context.debug("SMS-STATUS-REPORT: delivery still pending");
4642 return PDU_FCS_OK;
4643 }
4645 delete this._pendingSentSmsMap[message.messageRef];
4647 let deliveryStatus = ((status >>> 5) === 0x00)
4648 ? GECKO_SMS_DELIVERY_STATUS_SUCCESS
4649 : GECKO_SMS_DELIVERY_STATUS_ERROR;
4650 this.sendChromeMessage({
4651 rilMessageType: options.rilMessageType,
4652 rilMessageToken: options.rilMessageToken,
4653 deliveryStatus: deliveryStatus
4654 });
4656 return PDU_FCS_OK;
4657 },
4659 /**
4660 * Helper for processing CDMA SMS Delivery Acknowledgment Message
4661 *
4662 * @param message
4663 * decoded SMS Delivery ACK message from CdmaPDUHelper.
4664 *
4665 * @return A failure cause defined in 3GPP 23.040 clause 9.2.3.22.
4666 */
4667 _processCdmaSmsStatusReport: function(message) {
4668 let options = this._pendingSentSmsMap[message.msgId];
4669 if (!options) {
4670 if (DEBUG) this.context.debug("no pending SMS-SUBMIT message");
4671 return PDU_FCS_OK;
4672 }
4674 if (message.errorClass === 2) {
4675 if (DEBUG) {
4676 this.context.debug("SMS-STATUS-REPORT: delivery still pending, " +
4677 "msgStatus: " + message.msgStatus);
4678 }
4679 return PDU_FCS_OK;
4680 }
4682 delete this._pendingSentSmsMap[message.msgId];
4684 if (message.errorClass === -1 && message.body) {
4685 // Process as normal incoming SMS, if errorClass is invalid
4686 // but message body is available.
4687 return this._processSmsMultipart(message);
4688 }
4690 let deliveryStatus = (message.errorClass === 0)
4691 ? GECKO_SMS_DELIVERY_STATUS_SUCCESS
4692 : GECKO_SMS_DELIVERY_STATUS_ERROR;
4693 this.sendChromeMessage({
4694 rilMessageType: options.rilMessageType,
4695 rilMessageToken: options.rilMessageToken,
4696 deliveryStatus: deliveryStatus
4697 });
4699 return PDU_FCS_OK;
4700 },
4702 /**
4703 * Helper for processing CDMA SMS WAP Push Message
4704 *
4705 * @param message
4706 * decoded WAP message from CdmaPDUHelper.
4707 *
4708 * @return A failure cause defined in 3GPP 23.040 clause 9.2.3.22.
4709 */
4710 _processCdmaSmsWapPush: function(message) {
4711 if (!message.data) {
4712 if (DEBUG) this.context.debug("no data inside WAP Push message.");
4713 return PDU_FCS_OK;
4714 }
4716 // See 6.5. MAPPING OF WDP TO CDMA SMS in WAP-295-WDP.
4717 //
4718 // Field | Length (bits)
4719 // -----------------------------------------
4720 // MSG_TYPE | 8
4721 // TOTAL_SEGMENTS | 8
4722 // SEGMENT_NUMBER | 8
4723 // DATAGRAM | (NUM_FIELDS – 3) * 8
4724 let index = 0;
4725 if (message.data[index++] !== 0) {
4726 if (DEBUG) this.context.debug("Ignore a WAP Message which is not WDP.");
4727 return PDU_FCS_OK;
4728 }
4730 // 1. Originator Address in SMS-TL + Message_Id in SMS-TS are used to identify a unique WDP datagram.
4731 // 2. TOTAL_SEGMENTS, SEGMENT_NUMBER are used to verify that a complete
4732 // datagram has been received and is ready to be passed to a higher layer.
4733 message.header = {
4734 segmentRef: message.msgId,
4735 segmentMaxSeq: message.data[index++],
4736 segmentSeq: message.data[index++] + 1 // It's zero-based in CDMA WAP Push.
4737 };
4739 if (message.header.segmentSeq > message.header.segmentMaxSeq) {
4740 if (DEBUG) this.context.debug("Wrong WDP segment info.");
4741 return PDU_FCS_OK;
4742 }
4744 // Ports are only specified in 1st segment.
4745 if (message.header.segmentSeq == 1) {
4746 message.header.originatorPort = message.data[index++] << 8;
4747 message.header.originatorPort |= message.data[index++];
4748 message.header.destinationPort = message.data[index++] << 8;
4749 message.header.destinationPort |= message.data[index++];
4750 }
4752 message.data = message.data.subarray(index);
4754 return this._processSmsMultipart(message);
4755 },
4757 /**
4758 * Helper for processing sent multipart SMS.
4759 */
4760 _processSentSmsSegment: function(options) {
4761 // Setup attributes for sending next segment
4762 let next = options.segmentSeq;
4763 options.body = options.segments[next].body;
4764 options.encodedBodyLength = options.segments[next].encodedBodyLength;
4765 options.segmentSeq = next + 1;
4767 this.sendSMS(options);
4768 },
4770 /**
4771 * Helper for processing result of send SMS.
4772 *
4773 * @param length
4774 * Length of SMS string in the incoming parcel.
4775 * @param options
4776 * Sms information.
4777 */
4778 _processSmsSendResult: function(length, options) {
4779 if (options.rilRequestError) {
4780 if (DEBUG) {
4781 this.context.debug("_processSmsSendResult: rilRequestError = " +
4782 options.rilRequestError);
4783 }
4784 switch (options.rilRequestError) {
4785 case ERROR_SMS_SEND_FAIL_RETRY:
4786 if (options.retryCount < SMS_RETRY_MAX) {
4787 options.retryCount++;
4788 // TODO: bug 736702 TP-MR, retry interval, retry timeout
4789 this.sendSMS(options);
4790 break;
4791 }
4792 // Fallback to default error handling if it meets max retry count.
4793 // Fall through.
4794 default:
4795 this.sendChromeMessage({
4796 rilMessageType: options.rilMessageType,
4797 rilMessageToken: options.rilMessageToken,
4798 errorMsg: options.rilRequestError,
4799 });
4800 break;
4801 }
4802 return;
4803 }
4805 let Buf = this.context.Buf;
4806 options.messageRef = Buf.readInt32();
4807 options.ackPDU = Buf.readString();
4808 options.errorCode = Buf.readInt32();
4810 if ((options.segmentMaxSeq > 1)
4811 && (options.segmentSeq < options.segmentMaxSeq)) {
4812 // Not last segment
4813 this._processSentSmsSegment(options);
4814 } else {
4815 // Last segment sent with success.
4816 if (options.requestStatusReport) {
4817 if (DEBUG) {
4818 this.context.debug("waiting SMS-STATUS-REPORT for messageRef " +
4819 options.messageRef);
4820 }
4821 this._pendingSentSmsMap[options.messageRef] = options;
4822 }
4824 this.sendChromeMessage({
4825 rilMessageType: options.rilMessageType,
4826 rilMessageToken: options.rilMessageToken,
4827 });
4828 }
4829 },
4831 _processReceivedSmsCbPage: function(original) {
4832 if (original.numPages <= 1) {
4833 if (original.body) {
4834 original.fullBody = original.body;
4835 delete original.body;
4836 } else if (original.data) {
4837 original.fullData = original.data;
4838 delete original.data;
4839 }
4840 return original;
4841 }
4843 // Hash = <serial>:<mcc>:<mnc>:<lac>:<cid>
4844 let hash = original.serial + ":" + this.iccInfo.mcc + ":"
4845 + this.iccInfo.mnc + ":";
4846 switch (original.geographicalScope) {
4847 case CB_GSM_GEOGRAPHICAL_SCOPE_CELL_WIDE_IMMEDIATE:
4848 case CB_GSM_GEOGRAPHICAL_SCOPE_CELL_WIDE:
4849 hash += this.voiceRegistrationState.cell.gsmLocationAreaCode + ":"
4850 + this.voiceRegistrationState.cell.gsmCellId;
4851 break;
4852 case CB_GSM_GEOGRAPHICAL_SCOPE_LOCATION_AREA_WIDE:
4853 hash += this.voiceRegistrationState.cell.gsmLocationAreaCode + ":";
4854 break;
4855 default:
4856 hash += ":";
4857 break;
4858 }
4860 let index = original.pageIndex;
4862 let options = this._receivedSmsCbPagesMap[hash];
4863 if (!options) {
4864 options = original;
4865 this._receivedSmsCbPagesMap[hash] = options;
4867 options.receivedPages = 0;
4868 options.pages = [];
4869 } else if (options.pages[index]) {
4870 // Duplicated page?
4871 if (DEBUG) {
4872 this.context.debug("Got duplicated page no." + index +
4873 " of a multipage SMSCB: " + JSON.stringify(original));
4874 }
4875 return null;
4876 }
4878 if (options.encoding == PDU_DCS_MSG_CODING_8BITS_ALPHABET) {
4879 options.pages[index] = original.data;
4880 delete original.data;
4881 } else {
4882 options.pages[index] = original.body;
4883 delete original.body;
4884 }
4885 options.receivedPages++;
4886 if (options.receivedPages < options.numPages) {
4887 if (DEBUG) {
4888 this.context.debug("Got page no." + index + " of a multipage SMSCB: " +
4889 JSON.stringify(options));
4890 }
4891 return null;
4892 }
4894 // Remove from map
4895 delete this._receivedSmsCbPagesMap[hash];
4897 // Rebuild full body
4898 if (options.encoding == PDU_DCS_MSG_CODING_8BITS_ALPHABET) {
4899 // Uint8Array doesn't have `concat`, so we have to merge all pages by hand.
4900 let fullDataLen = 0;
4901 for (let i = 1; i <= options.numPages; i++) {
4902 fullDataLen += options.pages[i].length;
4903 }
4905 options.fullData = new Uint8Array(fullDataLen);
4906 for (let d= 0, i = 1; i <= options.numPages; i++) {
4907 let data = options.pages[i];
4908 for (let j = 0; j < data.length; j++) {
4909 options.fullData[d++] = data[j];
4910 }
4911 }
4912 } else {
4913 options.fullBody = options.pages.join("");
4914 }
4916 if (DEBUG) {
4917 this.context.debug("Got full multipage SMSCB: " + JSON.stringify(options));
4918 }
4920 return options;
4921 },
4923 _mergeCellBroadcastConfigs: function(list, from, to) {
4924 if (!list) {
4925 return [from, to];
4926 }
4928 for (let i = 0, f1, t1; i < list.length;) {
4929 f1 = list[i++];
4930 t1 = list[i++];
4931 if (to == f1) {
4932 // ...[from]...[to|f1]...(t1)
4933 list[i - 2] = from;
4934 return list;
4935 }
4937 if (to < f1) {
4938 // ...[from]...(to)...[f1] or ...[from]...(to)[f1]
4939 if (i > 2) {
4940 // Not the first range pair, merge three arrays.
4941 return list.slice(0, i - 2).concat([from, to]).concat(list.slice(i - 2));
4942 } else {
4943 return [from, to].concat(list);
4944 }
4945 }
4947 if (from > t1) {
4948 // ...[f1]...(t1)[from] or ...[f1]...(t1)...[from]
4949 continue;
4950 }
4952 // Have overlap or merge-able adjacency with [f1]...(t1). Replace it
4953 // with [min(from, f1)]...(max(to, t1)).
4955 let changed = false;
4956 if (from < f1) {
4957 // [from]...[f1]...(t1) or [from][f1]...(t1)
4958 // Save minimum from value.
4959 list[i - 2] = from;
4960 changed = true;
4961 }
4963 if (to <= t1) {
4964 // [from]...[to](t1) or [from]...(to|t1)
4965 // Can't have further merge-able adjacency. Return.
4966 return list;
4967 }
4969 // Try merging possible next adjacent range.
4970 let j = i;
4971 for (let f2, t2; j < list.length;) {
4972 f2 = list[j++];
4973 t2 = list[j++];
4974 if (to > t2) {
4975 // [from]...[f2]...[t2]...(to) or [from]...[f2]...[t2](to)
4976 // Merge next adjacent range again.
4977 continue;
4978 }
4980 if (to < t2) {
4981 if (to < f2) {
4982 // [from]...(to)[f2] or [from]...(to)...[f2]
4983 // Roll back and give up.
4984 j -= 2;
4985 } else if (to < t2) {
4986 // [from]...[to|f2]...(t2), or [from]...[f2]...[to](t2)
4987 // Merge to [from]...(t2) and give up.
4988 to = t2;
4989 }
4990 }
4992 break;
4993 }
4995 // Save maximum to value.
4996 list[i - 1] = to;
4998 if (j != i) {
4999 // Remove merged adjacent ranges.
5000 let ret = list.slice(0, i);
5001 if (j < list.length) {
5002 ret = ret.concat(list.slice(j));
5003 }
5004 return ret;
5005 }
5007 return list;
5008 }
5010 // Append to the end.
5011 list.push(from);
5012 list.push(to);
5014 return list;
5015 },
5017 _isCellBroadcastConfigReady: function() {
5018 if (!("MMI" in this.cellBroadcastConfigs)) {
5019 return false;
5020 }
5022 // CBMI should be ready in GSM.
5023 if (!this._isCdma &&
5024 (!("CBMI" in this.cellBroadcastConfigs) ||
5025 !("CBMID" in this.cellBroadcastConfigs) ||
5026 !("CBMIR" in this.cellBroadcastConfigs))) {
5027 return false;
5028 }
5030 return true;
5031 },
5033 /**
5034 * Merge all members of cellBroadcastConfigs into mergedCellBroadcastConfig.
5035 */
5036 _mergeAllCellBroadcastConfigs: function() {
5037 if (!this._isCellBroadcastConfigReady()) {
5038 if (DEBUG) {
5039 this.context.debug("cell broadcast configs not ready, waiting ...");
5040 }
5041 return;
5042 }
5044 // Prepare cell broadcast config. CBMI* are only used in GSM.
5045 let usedCellBroadcastConfigs = {MMI: this.cellBroadcastConfigs.MMI};
5046 if (!this._isCdma) {
5047 usedCellBroadcastConfigs.CBMI = this.cellBroadcastConfigs.CBMI;
5048 usedCellBroadcastConfigs.CBMID = this.cellBroadcastConfigs.CBMID;
5049 usedCellBroadcastConfigs.CBMIR = this.cellBroadcastConfigs.CBMIR;
5050 }
5052 if (DEBUG) {
5053 this.context.debug("Cell Broadcast search lists: " +
5054 JSON.stringify(usedCellBroadcastConfigs));
5055 }
5057 let list = null;
5058 for each (let ll in usedCellBroadcastConfigs) {
5059 if (ll == null) {
5060 continue;
5061 }
5063 for (let i = 0; i < ll.length; i += 2) {
5064 list = this._mergeCellBroadcastConfigs(list, ll[i], ll[i + 1]);
5065 }
5066 }
5068 if (DEBUG) {
5069 this.context.debug("Cell Broadcast search lists(merged): " +
5070 JSON.stringify(list));
5071 }
5072 this.mergedCellBroadcastConfig = list;
5073 this.updateCellBroadcastConfig();
5074 },
5076 /**
5077 * Check whether search list from settings is settable by MMI, that is,
5078 * whether the range is bounded in any entries of CB_NON_MMI_SETTABLE_RANGES.
5079 */
5080 _checkCellBroadcastMMISettable: function(from, to) {
5081 if ((to <= from) || (from >= 65536) || (from < 0)) {
5082 return false;
5083 }
5085 if (!this._isCdma) {
5086 // GSM not settable ranges.
5087 for (let i = 0, f, t; i < CB_NON_MMI_SETTABLE_RANGES.length;) {
5088 f = CB_NON_MMI_SETTABLE_RANGES[i++];
5089 t = CB_NON_MMI_SETTABLE_RANGES[i++];
5090 if ((from < t) && (to > f)) {
5091 // Have overlap.
5092 return false;
5093 }
5094 }
5095 }
5097 return true;
5098 },
5100 /**
5101 * Convert Cell Broadcast settings string into search list.
5102 */
5103 _convertCellBroadcastSearchList: function(searchListStr) {
5104 let parts = searchListStr && searchListStr.split(",");
5105 if (!parts) {
5106 return null;
5107 }
5109 let list = null;
5110 let result, from, to;
5111 for (let range of parts) {
5112 // Match "12" or "12-34". The result will be ["12", "12", null] or
5113 // ["12-34", "12", "34"].
5114 result = range.match(/^(\d+)(?:-(\d+))?$/);
5115 if (!result) {
5116 throw "Invalid format";
5117 }
5119 from = parseInt(result[1], 10);
5120 to = (result[2]) ? parseInt(result[2], 10) + 1 : from + 1;
5121 if (!this._checkCellBroadcastMMISettable(from, to)) {
5122 throw "Invalid range";
5123 }
5125 if (list == null) {
5126 list = [];
5127 }
5128 list.push(from);
5129 list.push(to);
5130 }
5132 return list;
5133 },
5135 /**
5136 * Handle incoming messages from the main UI thread.
5137 *
5138 * @param message
5139 * Object containing the message. Messages are supposed
5140 */
5141 handleChromeMessage: function(message) {
5142 if (DEBUG) {
5143 this.context.debug("Received chrome message " + JSON.stringify(message));
5144 }
5145 let method = this[message.rilMessageType];
5146 if (typeof method != "function") {
5147 if (DEBUG) {
5148 this.context.debug("Don't know what to do with message " +
5149 JSON.stringify(message));
5150 }
5151 return;
5152 }
5153 method.call(this, message);
5154 },
5156 /**
5157 * Get a list of current voice calls.
5158 */
5159 enumerateCalls: function(options) {
5160 if (DEBUG) this.context.debug("Sending all current calls");
5161 let calls = [];
5162 for each (let call in this.currentCalls) {
5163 calls.push(call);
5164 }
5165 options.calls = calls;
5166 this.sendChromeMessage(options);
5167 },
5169 /**
5170 * Process STK Proactive Command.
5171 */
5172 processStkProactiveCommand: function() {
5173 let Buf = this.context.Buf;
5174 let length = Buf.readInt32();
5175 let berTlv;
5176 try {
5177 berTlv = this.context.BerTlvHelper.decode(length / 2);
5178 } catch (e) {
5179 if (DEBUG) this.context.debug("processStkProactiveCommand : " + e);
5180 this.sendStkTerminalResponse({
5181 resultCode: STK_RESULT_CMD_DATA_NOT_UNDERSTOOD});
5182 return;
5183 }
5185 Buf.readStringDelimiter(length);
5187 let ctlvs = berTlv.value;
5188 let ctlv = this.context.StkProactiveCmdHelper.searchForTag(
5189 COMPREHENSIONTLV_TAG_COMMAND_DETAILS, ctlvs);
5190 if (!ctlv) {
5191 this.sendStkTerminalResponse({
5192 resultCode: STK_RESULT_CMD_DATA_NOT_UNDERSTOOD});
5193 throw new Error("Can't find COMMAND_DETAILS ComprehensionTlv");
5194 }
5196 let cmdDetails = ctlv.value;
5197 if (DEBUG) {
5198 this.context.debug("commandNumber = " + cmdDetails.commandNumber +
5199 " typeOfCommand = " + cmdDetails.typeOfCommand.toString(16) +
5200 " commandQualifier = " + cmdDetails.commandQualifier);
5201 }
5203 // STK_CMD_MORE_TIME need not to propagate event to chrome.
5204 if (cmdDetails.typeOfCommand == STK_CMD_MORE_TIME) {
5205 this.sendStkTerminalResponse({
5206 command: cmdDetails,
5207 resultCode: STK_RESULT_OK});
5208 return;
5209 }
5211 cmdDetails.rilMessageType = "stkcommand";
5212 cmdDetails.options =
5213 this.context.StkCommandParamsFactory.createParam(cmdDetails, ctlvs);
5214 this.sendChromeMessage(cmdDetails);
5215 },
5217 /**
5218 * Send messages to the main thread.
5219 */
5220 sendChromeMessage: function(message) {
5221 message.rilMessageClientId = this.context.clientId;
5222 postMessage(message);
5223 },
5225 /**
5226 * Handle incoming requests from the RIL. We find the method that
5227 * corresponds to the request type. Incidentally, the request type
5228 * _is_ the method name, so that's easy.
5229 */
5231 handleParcel: function(request_type, length, options) {
5232 let method = this[request_type];
5233 if (typeof method == "function") {
5234 if (DEBUG) this.context.debug("Handling parcel as " + method.name);
5235 method.call(this, length, options);
5236 }
5237 }
5238 };
5240 RilObject.prototype[REQUEST_GET_SIM_STATUS] = function REQUEST_GET_SIM_STATUS(length, options) {
5241 if (options.rilRequestError) {
5242 return;
5243 }
5245 let iccStatus = {};
5246 let Buf = this.context.Buf;
5247 iccStatus.cardState = Buf.readInt32(); // CARD_STATE_*
5248 iccStatus.universalPINState = Buf.readInt32(); // CARD_PINSTATE_*
5249 iccStatus.gsmUmtsSubscriptionAppIndex = Buf.readInt32();
5250 iccStatus.cdmaSubscriptionAppIndex = Buf.readInt32();
5251 if (!this.v5Legacy) {
5252 iccStatus.imsSubscriptionAppIndex = Buf.readInt32();
5253 }
5255 let apps_length = Buf.readInt32();
5256 if (apps_length > CARD_MAX_APPS) {
5257 apps_length = CARD_MAX_APPS;
5258 }
5260 iccStatus.apps = [];
5261 for (let i = 0 ; i < apps_length ; i++) {
5262 iccStatus.apps.push({
5263 app_type: Buf.readInt32(), // CARD_APPTYPE_*
5264 app_state: Buf.readInt32(), // CARD_APPSTATE_*
5265 perso_substate: Buf.readInt32(), // CARD_PERSOSUBSTATE_*
5266 aid: Buf.readString(),
5267 app_label: Buf.readString(),
5268 pin1_replaced: Buf.readInt32(),
5269 pin1: Buf.readInt32(),
5270 pin2: Buf.readInt32()
5271 });
5272 if (RILQUIRKS_SIM_APP_STATE_EXTRA_FIELDS) {
5273 Buf.readInt32();
5274 Buf.readInt32();
5275 Buf.readInt32();
5276 Buf.readInt32();
5277 }
5278 }
5280 if (DEBUG) this.context.debug("iccStatus: " + JSON.stringify(iccStatus));
5281 this._processICCStatus(iccStatus);
5282 };
5283 RilObject.prototype[REQUEST_ENTER_SIM_PIN] = function REQUEST_ENTER_SIM_PIN(length, options) {
5284 this._processEnterAndChangeICCResponses(length, options);
5285 };
5286 RilObject.prototype[REQUEST_ENTER_SIM_PUK] = function REQUEST_ENTER_SIM_PUK(length, options) {
5287 this._processEnterAndChangeICCResponses(length, options);
5288 };
5289 RilObject.prototype[REQUEST_ENTER_SIM_PIN2] = function REQUEST_ENTER_SIM_PIN2(length, options) {
5290 this._processEnterAndChangeICCResponses(length, options);
5291 };
5292 RilObject.prototype[REQUEST_ENTER_SIM_PUK2] = function REQUEST_ENTER_SIM_PUK(length, options) {
5293 this._processEnterAndChangeICCResponses(length, options);
5294 };
5295 RilObject.prototype[REQUEST_CHANGE_SIM_PIN] = function REQUEST_CHANGE_SIM_PIN(length, options) {
5296 this._processEnterAndChangeICCResponses(length, options);
5297 };
5298 RilObject.prototype[REQUEST_CHANGE_SIM_PIN2] = function REQUEST_CHANGE_SIM_PIN2(length, options) {
5299 this._processEnterAndChangeICCResponses(length, options);
5300 };
5301 RilObject.prototype[REQUEST_ENTER_NETWORK_DEPERSONALIZATION_CODE] =
5302 function REQUEST_ENTER_NETWORK_DEPERSONALIZATION_CODE(length, options) {
5303 this._processEnterAndChangeICCResponses(length, options);
5304 };
5305 RilObject.prototype[REQUEST_GET_CURRENT_CALLS] = function REQUEST_GET_CURRENT_CALLS(length, options) {
5306 if (options.rilRequestError) {
5307 return;
5308 }
5310 let Buf = this.context.Buf;
5311 let calls_length = 0;
5312 // The RIL won't even send us the length integer if there are no active calls.
5313 // So only read this integer if the parcel actually has it.
5314 if (length) {
5315 calls_length = Buf.readInt32();
5316 }
5317 if (!calls_length) {
5318 this._processCalls(null);
5319 return;
5320 }
5322 let calls = {};
5323 for (let i = 0; i < calls_length; i++) {
5324 let call = {};
5326 // Extra uint32 field to get correct callIndex and rest of call data for
5327 // call waiting feature.
5328 if (RILQUIRKS_EXTRA_UINT32_2ND_CALL && i > 0) {
5329 Buf.readInt32();
5330 }
5332 call.state = Buf.readInt32(); // CALL_STATE_*
5333 call.callIndex = Buf.readInt32(); // GSM index (1-based)
5334 call.toa = Buf.readInt32();
5335 call.isMpty = Boolean(Buf.readInt32());
5336 call.isMT = Boolean(Buf.readInt32());
5337 call.als = Buf.readInt32();
5338 call.isVoice = Boolean(Buf.readInt32());
5339 call.isVoicePrivacy = Boolean(Buf.readInt32());
5340 if (RILQUIRKS_CALLSTATE_EXTRA_UINT32) {
5341 Buf.readInt32();
5342 }
5343 call.number = Buf.readString(); //TODO munge with TOA
5344 call.numberPresentation = Buf.readInt32(); // CALL_PRESENTATION_*
5345 call.name = Buf.readString();
5346 call.namePresentation = Buf.readInt32();
5348 call.uusInfo = null;
5349 let uusInfoPresent = Buf.readInt32();
5350 if (uusInfoPresent == 1) {
5351 call.uusInfo = {
5352 type: Buf.readInt32(),
5353 dcs: Buf.readInt32(),
5354 userData: null //XXX TODO byte array?!?
5355 };
5356 }
5358 calls[call.callIndex] = call;
5359 }
5360 this._processCalls(calls);
5361 };
5362 RilObject.prototype[REQUEST_DIAL] = function REQUEST_DIAL(length, options) {
5363 // We already return a successful response before. Don't respond it again!
5364 if (options.rilRequestError) {
5365 this.getFailCauseCode((function(failCause) {
5366 this._removePendingOutgoingCall(failCause);
5367 }).bind(this));
5368 }
5369 };
5370 RilObject.prototype[REQUEST_DIAL_EMERGENCY_CALL] = RilObject.prototype[REQUEST_DIAL];
5371 RilObject.prototype[REQUEST_GET_IMSI] = function REQUEST_GET_IMSI(length, options) {
5372 if (options.rilRequestError) {
5373 return;
5374 }
5376 this.iccInfoPrivate.imsi = this.context.Buf.readString();
5377 if (DEBUG) {
5378 this.context.debug("IMSI: " + this.iccInfoPrivate.imsi);
5379 }
5381 options.rilMessageType = "iccimsi";
5382 options.imsi = this.iccInfoPrivate.imsi;
5383 this.sendChromeMessage(options);
5384 };
5385 RilObject.prototype[REQUEST_HANGUP] = function REQUEST_HANGUP(length, options) {
5386 if (options.rilRequestError) {
5387 return;
5388 }
5390 this.getCurrentCalls();
5391 };
5392 RilObject.prototype[REQUEST_HANGUP_WAITING_OR_BACKGROUND] = function REQUEST_HANGUP_WAITING_OR_BACKGROUND(length, options) {
5393 if (options.rilRequestError) {
5394 return;
5395 }
5397 this.getCurrentCalls();
5398 };
5399 RilObject.prototype[REQUEST_HANGUP_FOREGROUND_RESUME_BACKGROUND] = function REQUEST_HANGUP_FOREGROUND_RESUME_BACKGROUND(length, options) {
5400 if (options.rilRequestError) {
5401 return;
5402 }
5404 this.getCurrentCalls();
5405 };
5406 RilObject.prototype[REQUEST_SWITCH_WAITING_OR_HOLDING_AND_ACTIVE] = function REQUEST_SWITCH_WAITING_OR_HOLDING_AND_ACTIVE(length, options) {
5407 options.success = (options.rilRequestError === 0);
5408 if (!options.success) {
5409 options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError];
5410 this.sendChromeMessage(options);
5411 return;
5412 }
5414 this.sendChromeMessage(options);
5415 this.getCurrentCalls();
5416 };
5417 RilObject.prototype[REQUEST_CONFERENCE] = function REQUEST_CONFERENCE(length, options) {
5418 options.success = (options.rilRequestError === 0);
5419 if (!options.success) {
5420 this._hasConferenceRequest = false;
5421 options.errorName = "addError";
5422 options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError];
5423 this.sendChromeMessage(options);
5424 return;
5425 }
5427 this.sendChromeMessage(options);
5428 };
5429 RilObject.prototype[REQUEST_UDUB] = null;
5430 RilObject.prototype[REQUEST_LAST_CALL_FAIL_CAUSE] = function REQUEST_LAST_CALL_FAIL_CAUSE(length, options) {
5431 let Buf = this.context.Buf;
5432 let num = length ? Buf.readInt32() : 0;
5433 let failCause = num ? RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[Buf.readInt32()] : null;
5434 if (options.callback) {
5435 options.callback(failCause);
5436 }
5437 };
5438 RilObject.prototype[REQUEST_SIGNAL_STRENGTH] = function REQUEST_SIGNAL_STRENGTH(length, options) {
5439 this._receivedNetworkInfo(NETWORK_INFO_SIGNAL);
5441 if (options.rilRequestError) {
5442 return;
5443 }
5445 let Buf = this.context.Buf;
5446 let signal = {
5447 gsmSignalStrength: Buf.readInt32(),
5448 gsmBitErrorRate: Buf.readInt32(),
5449 cdmaDBM: Buf.readInt32(),
5450 cdmaECIO: Buf.readInt32(),
5451 evdoDBM: Buf.readInt32(),
5452 evdoECIO: Buf.readInt32(),
5453 evdoSNR: Buf.readInt32()
5454 };
5456 if (!this.v5Legacy) {
5457 signal.lteSignalStrength = Buf.readInt32();
5458 signal.lteRSRP = Buf.readInt32();
5459 signal.lteRSRQ = Buf.readInt32();
5460 signal.lteRSSNR = Buf.readInt32();
5461 signal.lteCQI = Buf.readInt32();
5462 }
5464 if (DEBUG) this.context.debug("signal strength: " + JSON.stringify(signal));
5466 this._processSignalStrength(signal);
5467 };
5468 RilObject.prototype[REQUEST_VOICE_REGISTRATION_STATE] = function REQUEST_VOICE_REGISTRATION_STATE(length, options) {
5469 this._receivedNetworkInfo(NETWORK_INFO_VOICE_REGISTRATION_STATE);
5471 if (options.rilRequestError) {
5472 return;
5473 }
5475 let state = this.context.Buf.readStringList();
5476 if (DEBUG) this.context.debug("voice registration state: " + state);
5478 this._processVoiceRegistrationState(state);
5480 if (this.cachedDialRequest &&
5481 (this.voiceRegistrationState.emergencyCallsOnly ||
5482 this.voiceRegistrationState.connected) &&
5483 this.voiceRegistrationState.radioTech != NETWORK_CREG_TECH_UNKNOWN) {
5484 // Radio is ready for making the cached emergency call.
5485 this.cachedDialRequest.callback();
5486 this.cachedDialRequest = null;
5487 }
5488 };
5489 RilObject.prototype[REQUEST_DATA_REGISTRATION_STATE] = function REQUEST_DATA_REGISTRATION_STATE(length, options) {
5490 this._receivedNetworkInfo(NETWORK_INFO_DATA_REGISTRATION_STATE);
5492 if (options.rilRequestError) {
5493 return;
5494 }
5496 let state = this.context.Buf.readStringList();
5497 this._processDataRegistrationState(state);
5498 };
5499 RilObject.prototype[REQUEST_OPERATOR] = function REQUEST_OPERATOR(length, options) {
5500 this._receivedNetworkInfo(NETWORK_INFO_OPERATOR);
5502 if (options.rilRequestError) {
5503 return;
5504 }
5506 let operatorData = this.context.Buf.readStringList();
5507 if (DEBUG) this.context.debug("Operator: " + operatorData);
5508 this._processOperator(operatorData);
5509 };
5510 RilObject.prototype[REQUEST_RADIO_POWER] = function REQUEST_RADIO_POWER(length, options) {
5511 if (options.rilMessageType == null) {
5512 // The request was made by ril_worker itself.
5513 if (options.rilRequestError) {
5514 if (this.cachedDialRequest && options.enabled) {
5515 // Turning on radio fails. Notify the error of making an emergency call.
5516 this.cachedDialRequest.onerror(GECKO_ERROR_RADIO_NOT_AVAILABLE);
5517 this.cachedDialRequest = null;
5518 }
5519 }
5520 return;
5521 }
5523 options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError];
5524 this.sendChromeMessage(options);
5525 };
5526 RilObject.prototype[REQUEST_DTMF] = null;
5527 RilObject.prototype[REQUEST_SEND_SMS] = function REQUEST_SEND_SMS(length, options) {
5528 this._processSmsSendResult(length, options);
5529 };
5530 RilObject.prototype[REQUEST_SEND_SMS_EXPECT_MORE] = null;
5532 RilObject.prototype.readSetupDataCall_v5 = function readSetupDataCall_v5(options) {
5533 if (!options) {
5534 options = {};
5535 }
5536 let [cid, ifname, addresses, dnses, gateways] = this.context.Buf.readStringList();
5537 options.cid = cid;
5538 options.ifname = ifname;
5539 options.addresses = addresses ? [addresses] : [];
5540 options.dnses = dnses ? [dnses] : [];
5541 options.gateways = gateways ? [gateways] : [];
5542 options.active = DATACALL_ACTIVE_UNKNOWN;
5543 options.state = GECKO_NETWORK_STATE_CONNECTING;
5544 return options;
5545 };
5547 RilObject.prototype[REQUEST_SETUP_DATA_CALL] = function REQUEST_SETUP_DATA_CALL(length, options) {
5548 if (options.rilRequestError) {
5549 // On Data Call generic errors, we shall notify caller
5550 this._sendDataCallError(options, options.rilRequestError);
5551 return;
5552 }
5554 if (this.v5Legacy) {
5555 // Populate the `options` object with the data call information. That way
5556 // we retain the APN and other info about how the data call was set up.
5557 this.readSetupDataCall_v5(options);
5558 this.currentDataCalls[options.cid] = options;
5559 options.rilMessageType = "datacallstatechange";
5560 this.sendChromeMessage(options);
5561 // Let's get the list of data calls to ensure we know whether it's active
5562 // or not.
5563 this.getDataCallList();
5564 return;
5565 }
5566 // Pass `options` along. That way we retain the APN and other info about
5567 // how the data call was set up.
5568 this[REQUEST_DATA_CALL_LIST](length, options);
5569 };
5570 RilObject.prototype[REQUEST_SIM_IO] = function REQUEST_SIM_IO(length, options) {
5571 let ICCIOHelper = this.context.ICCIOHelper;
5572 if (!length) {
5573 ICCIOHelper.processICCIOError(options);
5574 return;
5575 }
5577 // Don't need to read rilRequestError since we can know error status from
5578 // sw1 and sw2.
5579 let Buf = this.context.Buf;
5580 options.sw1 = Buf.readInt32();
5581 options.sw2 = Buf.readInt32();
5582 if (options.sw1 != ICC_STATUS_NORMAL_ENDING) {
5583 ICCIOHelper.processICCIOError(options);
5584 return;
5585 }
5586 ICCIOHelper.processICCIO(options);
5587 };
5588 RilObject.prototype[REQUEST_SEND_USSD] = function REQUEST_SEND_USSD(length, options) {
5589 if (DEBUG) {
5590 this.context.debug("REQUEST_SEND_USSD " + JSON.stringify(options));
5591 }
5592 options.success = (this._ussdSession = options.rilRequestError === 0);
5593 options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError];
5594 this.sendChromeMessage(options);
5595 };
5596 RilObject.prototype[REQUEST_CANCEL_USSD] = function REQUEST_CANCEL_USSD(length, options) {
5597 if (DEBUG) {
5598 this.context.debug("REQUEST_CANCEL_USSD" + JSON.stringify(options));
5599 }
5600 options.success = (options.rilRequestError === 0);
5601 this._ussdSession = !options.success;
5602 options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError];
5603 this.sendChromeMessage(options);
5604 };
5605 RilObject.prototype[REQUEST_GET_CLIR] = function REQUEST_GET_CLIR(length, options) {
5606 options.success = (options.rilRequestError === 0);
5607 if (!options.success) {
5608 options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError];
5609 this.sendChromeMessage(options);
5610 return;
5611 }
5613 let Buf = this.context.Buf;
5614 let bufLength = Buf.readInt32();
5615 if (!bufLength || bufLength < 2) {
5616 options.success = false;
5617 options.errorMsg = GECKO_ERROR_GENERIC_FAILURE;
5618 this.sendChromeMessage(options);
5619 return;
5620 }
5622 options.n = Buf.readInt32(); // Will be TS 27.007 +CLIR parameter 'n'.
5623 options.m = Buf.readInt32(); // Will be TS 27.007 +CLIR parameter 'm'.
5625 if (options.rilMessageType === "sendMMI") {
5626 // TS 27.007 +CLIR parameter 'm'.
5627 switch (options.m) {
5628 // CLIR not provisioned.
5629 case 0:
5630 options.statusMessage = MMI_SM_KS_SERVICE_NOT_PROVISIONED;
5631 break;
5632 // CLIR provisioned in permanent mode.
5633 case 1:
5634 options.statusMessage = MMI_SM_KS_CLIR_PERMANENT;
5635 break;
5636 // Unknown (e.g. no network, etc.).
5637 case 2:
5638 options.success = false;
5639 options.errorMsg = MMI_ERROR_KS_ERROR;
5640 break;
5641 // CLIR temporary mode presentation restricted.
5642 case 3:
5643 // TS 27.007 +CLIR parameter 'n'.
5644 switch (options.n) {
5645 // Default.
5646 case 0:
5647 // CLIR invocation.
5648 case 1:
5649 options.statusMessage = MMI_SM_KS_CLIR_DEFAULT_ON_NEXT_CALL_ON;
5650 break;
5651 // CLIR suppression.
5652 case 2:
5653 options.statusMessage = MMI_SM_KS_CLIR_DEFAULT_ON_NEXT_CALL_OFF;
5654 break;
5655 default:
5656 options.success = false;
5657 options.errorMsg = GECKO_ERROR_GENERIC_FAILURE;
5658 break;
5659 }
5660 break;
5661 // CLIR temporary mode presentation allowed.
5662 case 4:
5663 // TS 27.007 +CLIR parameter 'n'.
5664 switch (options.n) {
5665 // Default.
5666 case 0:
5667 // CLIR suppression.
5668 case 2:
5669 options.statusMessage = MMI_SM_KS_CLIR_DEFAULT_OFF_NEXT_CALL_OFF;
5670 break;
5671 // CLIR invocation.
5672 case 1:
5673 options.statusMessage = MMI_SM_KS_CLIR_DEFAULT_OFF_NEXT_CALL_ON;
5674 break;
5675 default:
5676 options.success = false;
5677 options.errorMsg = GECKO_ERROR_GENERIC_FAILURE;
5678 break;
5679 }
5680 break;
5681 default:
5682 options.success = false;
5683 options.errorMsg = GECKO_ERROR_GENERIC_FAILURE;
5684 break;
5685 }
5686 }
5688 this.sendChromeMessage(options);
5689 };
5690 RilObject.prototype[REQUEST_SET_CLIR] = function REQUEST_SET_CLIR(length, options) {
5691 if (options.rilMessageType == null) {
5692 // The request was made by ril_worker itself automatically. Don't report.
5693 return;
5694 }
5695 options.success = (options.rilRequestError === 0);
5696 if (!options.success) {
5697 options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError];
5698 } else if (options.rilMessageType === "sendMMI") {
5699 switch (options.procedure) {
5700 case MMI_PROCEDURE_ACTIVATION:
5701 options.statusMessage = MMI_SM_KS_SERVICE_ENABLED;
5702 break;
5703 case MMI_PROCEDURE_DEACTIVATION:
5704 options.statusMessage = MMI_SM_KS_SERVICE_DISABLED;
5705 break;
5706 }
5707 }
5708 this.sendChromeMessage(options);
5709 };
5711 RilObject.prototype[REQUEST_QUERY_CALL_FORWARD_STATUS] =
5712 function REQUEST_QUERY_CALL_FORWARD_STATUS(length, options) {
5713 options.success = (options.rilRequestError === 0);
5714 if (!options.success) {
5715 options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError];
5716 this.sendChromeMessage(options);
5717 return;
5718 }
5720 let Buf = this.context.Buf;
5721 let rulesLength = 0;
5722 if (length) {
5723 rulesLength = Buf.readInt32();
5724 }
5725 if (!rulesLength) {
5726 options.success = false;
5727 options.errorMsg = GECKO_ERROR_GENERIC_FAILURE;
5728 this.sendChromeMessage(options);
5729 return;
5730 }
5731 let rules = new Array(rulesLength);
5732 for (let i = 0; i < rulesLength; i++) {
5733 let rule = {};
5734 rule.active = Buf.readInt32() == 1; // CALL_FORWARD_STATUS_*
5735 rule.reason = Buf.readInt32(); // CALL_FORWARD_REASON_*
5736 rule.serviceClass = Buf.readInt32();
5737 rule.toa = Buf.readInt32();
5738 rule.number = Buf.readString();
5739 rule.timeSeconds = Buf.readInt32();
5740 rules[i] = rule;
5741 }
5742 options.rules = rules;
5743 if (options.rilMessageType === "sendMMI") {
5744 options.statusMessage = MMI_SM_KS_SERVICE_INTERROGATED;
5745 // MMI query call forwarding options request returns a set of rules that
5746 // will be exposed in the form of an array of nsIDOMMozMobileCFInfo
5747 // instances.
5748 options.additionalInformation = rules;
5749 }
5750 this.sendChromeMessage(options);
5751 };
5752 RilObject.prototype[REQUEST_SET_CALL_FORWARD] =
5753 function REQUEST_SET_CALL_FORWARD(length, options) {
5754 options.success = (options.rilRequestError === 0);
5755 if (!options.success) {
5756 options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError];
5757 } else if (options.rilMessageType === "sendMMI") {
5758 switch (options.action) {
5759 case CALL_FORWARD_ACTION_ENABLE:
5760 options.statusMessage = MMI_SM_KS_SERVICE_ENABLED;
5761 break;
5762 case CALL_FORWARD_ACTION_DISABLE:
5763 options.statusMessage = MMI_SM_KS_SERVICE_DISABLED;
5764 break;
5765 case CALL_FORWARD_ACTION_REGISTRATION:
5766 options.statusMessage = MMI_SM_KS_SERVICE_REGISTERED;
5767 break;
5768 case CALL_FORWARD_ACTION_ERASURE:
5769 options.statusMessage = MMI_SM_KS_SERVICE_ERASED;
5770 break;
5771 }
5772 }
5773 this.sendChromeMessage(options);
5774 };
5775 RilObject.prototype[REQUEST_QUERY_CALL_WAITING] =
5776 function REQUEST_QUERY_CALL_WAITING(length, options) {
5777 options.success = (options.rilRequestError === 0);
5778 if (!options.success) {
5779 options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError];
5780 this.sendChromeMessage(options);
5781 return;
5782 }
5784 if (options.callback) {
5785 options.callback.call(this, options);
5786 return;
5787 }
5789 let Buf = this.context.Buf;
5790 options.length = Buf.readInt32();
5791 options.enabled = ((Buf.readInt32() == 1) &&
5792 ((Buf.readInt32() & ICC_SERVICE_CLASS_VOICE) == 0x01));
5793 this.sendChromeMessage(options);
5794 };
5796 RilObject.prototype[REQUEST_SET_CALL_WAITING] = function REQUEST_SET_CALL_WAITING(length, options) {
5797 options.success = (options.rilRequestError === 0);
5798 if (!options.success) {
5799 options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError];
5800 this.sendChromeMessage(options);
5801 return;
5802 }
5804 if (options.callback) {
5805 options.callback.call(this, options);
5806 return;
5807 }
5809 this.sendChromeMessage(options);
5810 };
5811 RilObject.prototype[REQUEST_SMS_ACKNOWLEDGE] = null;
5812 RilObject.prototype[REQUEST_GET_IMEI] = function REQUEST_GET_IMEI(length, options) {
5813 this.IMEI = this.context.Buf.readString();
5814 let rilMessageType = options.rilMessageType;
5815 // So far we only send the IMEI back to chrome if it was requested via MMI.
5816 if (rilMessageType !== "sendMMI") {
5817 return;
5818 }
5820 options.success = (options.rilRequestError === 0);
5821 options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError];
5822 if ((!options.success || this.IMEI == null) && !options.errorMsg) {
5823 options.errorMsg = GECKO_ERROR_GENERIC_FAILURE;
5824 }
5825 options.statusMessage = this.IMEI;
5826 this.sendChromeMessage(options);
5827 };
5828 RilObject.prototype[REQUEST_GET_IMEISV] = function REQUEST_GET_IMEISV(length, options) {
5829 if (options.rilRequestError) {
5830 return;
5831 }
5833 this.IMEISV = this.context.Buf.readString();
5834 };
5835 RilObject.prototype[REQUEST_ANSWER] = null;
5836 RilObject.prototype[REQUEST_DEACTIVATE_DATA_CALL] = function REQUEST_DEACTIVATE_DATA_CALL(length, options) {
5837 if (options.rilRequestError) {
5838 return;
5839 }
5841 let datacall = this.currentDataCalls[options.cid];
5842 delete this.currentDataCalls[options.cid];
5843 datacall.state = GECKO_NETWORK_STATE_UNKNOWN;
5844 datacall.rilMessageType = "datacallstatechange";
5845 this.sendChromeMessage(datacall);
5846 };
5847 RilObject.prototype[REQUEST_QUERY_FACILITY_LOCK] = function REQUEST_QUERY_FACILITY_LOCK(length, options) {
5848 options.success = (options.rilRequestError === 0);
5849 if (!options.success) {
5850 options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError];
5851 }
5853 let services;
5854 if (length) {
5855 // Buf.readInt32List()[0] for Call Barring is a bit vector of services.
5856 services = this.context.Buf.readInt32List()[0];
5857 } else {
5858 options.success = false;
5859 options.errorMsg = GECKO_ERROR_GENERIC_FAILURE;
5860 this.sendChromeMessage(options);
5861 return;
5862 }
5864 options.enabled = services === 0 ? false : true;
5866 if (options.success && (options.rilMessageType === "sendMMI")) {
5867 if (!options.enabled) {
5868 options.statusMessage = MMI_SM_KS_SERVICE_DISABLED;
5869 } else {
5870 options.statusMessage = MMI_SM_KS_SERVICE_ENABLED_FOR;
5871 let serviceClass = [];
5872 for (let serviceClassMask = 1;
5873 serviceClassMask <= ICC_SERVICE_CLASS_MAX;
5874 serviceClassMask <<= 1) {
5875 if ((serviceClassMask & services) !== 0) {
5876 serviceClass.push(MMI_KS_SERVICE_CLASS_MAPPING[serviceClassMask]);
5877 }
5878 }
5880 options.additionalInformation = serviceClass;
5881 }
5882 }
5883 this.sendChromeMessage(options);
5884 };
5885 RilObject.prototype[REQUEST_SET_FACILITY_LOCK] = function REQUEST_SET_FACILITY_LOCK(length, options) {
5886 options.success = (options.rilRequestError === 0);
5887 if (!options.success) {
5888 options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError];
5889 }
5891 options.retryCount = length ? this.context.Buf.readInt32List()[0] : -1;
5893 if (options.success && (options.rilMessageType === "sendMMI")) {
5894 switch (options.procedure) {
5895 case MMI_PROCEDURE_ACTIVATION:
5896 options.statusMessage = MMI_SM_KS_SERVICE_ENABLED;
5897 break;
5898 case MMI_PROCEDURE_DEACTIVATION:
5899 options.statusMessage = MMI_SM_KS_SERVICE_DISABLED;
5900 break;
5901 }
5902 }
5903 this.sendChromeMessage(options);
5904 };
5905 RilObject.prototype[REQUEST_CHANGE_BARRING_PASSWORD] =
5906 function REQUEST_CHANGE_BARRING_PASSWORD(length, options) {
5907 if (options.rilRequestError) {
5908 options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError];
5909 }
5910 this.sendChromeMessage(options);
5911 };
5912 RilObject.prototype[REQUEST_SIM_OPEN_CHANNEL] = function REQUEST_SIM_OPEN_CHANNEL(length, options) {
5913 if (options.rilRequestError) {
5914 options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError];
5915 this.sendChromeMessage(options);
5916 return;
5917 }
5919 options.channel = this.context.Buf.readInt32();
5920 if (DEBUG) {
5921 this.context.debug("Setting channel number in options: " + options.channel);
5922 }
5923 this.sendChromeMessage(options);
5924 };
5925 RilObject.prototype[REQUEST_SIM_CLOSE_CHANNEL] = function REQUEST_SIM_CLOSE_CHANNEL(length, options) {
5926 if (options.rilRequestError) {
5927 options.error = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError];
5928 this.sendChromeMessage(options);
5929 return;
5930 }
5932 // No return value
5933 this.sendChromeMessage(options);
5934 };
5935 RilObject.prototype[REQUEST_SIM_ACCESS_CHANNEL] = function REQUEST_SIM_ACCESS_CHANNEL(length, options) {
5936 if (options.rilRequestError) {
5937 options.error = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError];
5938 this.sendChromeMessage(options);
5939 }
5941 let Buf = this.context.Buf;
5942 options.sw1 = Buf.readInt32();
5943 options.sw2 = Buf.readInt32();
5944 options.simResponse = Buf.readString();
5945 if (DEBUG) {
5946 this.context.debug("Setting return values for RIL[REQUEST_SIM_ACCESS_CHANNEL]: [" +
5947 options.sw1 + "," +
5948 options.sw2 + ", " +
5949 options.simResponse + "]");
5950 }
5951 this.sendChromeMessage(options);
5952 };
5953 RilObject.prototype[REQUEST_QUERY_NETWORK_SELECTION_MODE] = function REQUEST_QUERY_NETWORK_SELECTION_MODE(length, options) {
5954 this._receivedNetworkInfo(NETWORK_INFO_NETWORK_SELECTION_MODE);
5956 if (options.rilRequestError) {
5957 return;
5958 }
5960 let mode = this.context.Buf.readInt32List();
5961 let selectionMode;
5963 switch (mode[0]) {
5964 case NETWORK_SELECTION_MODE_AUTOMATIC:
5965 selectionMode = GECKO_NETWORK_SELECTION_AUTOMATIC;
5966 break;
5967 case NETWORK_SELECTION_MODE_MANUAL:
5968 selectionMode = GECKO_NETWORK_SELECTION_MANUAL;
5969 break;
5970 default:
5971 selectionMode = GECKO_NETWORK_SELECTION_UNKNOWN;
5972 break;
5973 }
5975 if (this.networkSelectionMode != selectionMode) {
5976 this.networkSelectionMode = options.mode = selectionMode;
5977 options.rilMessageType = "networkselectionmodechange";
5978 this._sendNetworkInfoMessage(NETWORK_INFO_NETWORK_SELECTION_MODE, options);
5979 }
5980 };
5981 RilObject.prototype[REQUEST_SET_NETWORK_SELECTION_AUTOMATIC] = function REQUEST_SET_NETWORK_SELECTION_AUTOMATIC(length, options) {
5982 if (options.rilRequestError) {
5983 options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError];
5984 }
5986 this.sendChromeMessage(options);
5987 };
5988 RilObject.prototype[REQUEST_SET_NETWORK_SELECTION_MANUAL] = function REQUEST_SET_NETWORK_SELECTION_MANUAL(length, options) {
5989 if (options.rilRequestError) {
5990 options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError];
5991 }
5993 this.sendChromeMessage(options);
5994 };
5995 RilObject.prototype[REQUEST_QUERY_AVAILABLE_NETWORKS] = function REQUEST_QUERY_AVAILABLE_NETWORKS(length, options) {
5996 if (options.rilRequestError) {
5997 options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError];
5998 } else {
5999 options.networks = this._processNetworks();
6000 }
6001 this.sendChromeMessage(options);
6002 };
6003 RilObject.prototype[REQUEST_DTMF_START] = null;
6004 RilObject.prototype[REQUEST_DTMF_STOP] = null;
6005 RilObject.prototype[REQUEST_BASEBAND_VERSION] = function REQUEST_BASEBAND_VERSION(length, options) {
6006 if (options.rilRequestError) {
6007 return;
6008 }
6010 this.basebandVersion = this.context.Buf.readString();
6011 if (DEBUG) this.context.debug("Baseband version: " + this.basebandVersion);
6012 };
6013 RilObject.prototype[REQUEST_SEPARATE_CONNECTION] = function REQUEST_SEPARATE_CONNECTION(length, options) {
6014 options.success = (options.rilRequestError === 0);
6015 if (!options.success) {
6016 options.errorName = "removeError";
6017 options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError];
6018 this.sendChromeMessage(options);
6019 return;
6020 }
6022 this.sendChromeMessage(options);
6023 };
6024 RilObject.prototype[REQUEST_SET_MUTE] = null;
6025 RilObject.prototype[REQUEST_GET_MUTE] = null;
6026 RilObject.prototype[REQUEST_QUERY_CLIP] = function REQUEST_QUERY_CLIP(length, options) {
6027 options.success = (options.rilRequestError === 0);
6028 if (!options.success) {
6029 options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError];
6030 this.sendChromeMessage(options);
6031 return;
6032 }
6034 let Buf = this.context.Buf;
6035 let bufLength = Buf.readInt32();
6036 if (!bufLength) {
6037 options.success = false;
6038 options.errorMsg = GECKO_ERROR_GENERIC_FAILURE;
6039 this.sendChromeMessage(options);
6040 return;
6041 }
6043 // options.provisioned informs about the called party receives the calling
6044 // party's address information:
6045 // 0 for CLIP not provisioned
6046 // 1 for CLIP provisioned
6047 // 2 for unknown
6048 options.provisioned = Buf.readInt32();
6049 if (options.rilMessageType === "sendMMI") {
6050 switch (options.provisioned) {
6051 case 0:
6052 options.statusMessage = MMI_SM_KS_SERVICE_DISABLED;
6053 break;
6054 case 1:
6055 options.statusMessage = MMI_SM_KS_SERVICE_ENABLED;
6056 break;
6057 default:
6058 options.success = false;
6059 options.errorMsg = MMI_ERROR_KS_ERROR;
6060 break;
6061 }
6062 }
6063 this.sendChromeMessage(options);
6064 };
6065 RilObject.prototype[REQUEST_LAST_DATA_CALL_FAIL_CAUSE] = null;
6067 /**
6068 * V3:
6069 * # address - A space-delimited list of addresses.
6070 *
6071 * V4:
6072 * # address - An address.
6073 *
6074 * V5:
6075 * # addresses - A space-delimited list of addresses.
6076 * # dnses - A space-delimited list of DNS server addresses.
6077 *
6078 * V6:
6079 * # addresses - A space-delimited list of addresses with optional "/" prefix
6080 * length.
6081 * # dnses - A space-delimited list of DNS server addresses.
6082 * # gateways - A space-delimited list of default gateway addresses.
6083 */
6084 RilObject.prototype.readDataCall_v5 = function(options) {
6085 if (!options) {
6086 options = {};
6087 }
6088 let Buf = this.context.Buf;
6089 options.cid = Buf.readInt32().toString();
6090 options.active = Buf.readInt32(); // DATACALL_ACTIVE_*
6091 options.type = Buf.readString();
6092 options.apn = Buf.readString();
6093 let addresses = Buf.readString();
6094 let dnses = Buf.readString();
6095 options.addresses = addresses ? addresses.split(" ") : [];
6096 options.dnses = dnses ? dnses.split(" ") : [];
6097 options.gateways = [];
6098 return options;
6099 };
6101 RilObject.prototype.readDataCall_v6 = function(options) {
6102 if (!options) {
6103 options = {};
6104 }
6105 let Buf = this.context.Buf;
6106 options.status = Buf.readInt32(); // DATACALL_FAIL_*
6107 options.suggestedRetryTime = Buf.readInt32();
6108 options.cid = Buf.readInt32().toString();
6109 options.active = Buf.readInt32(); // DATACALL_ACTIVE_*
6110 options.type = Buf.readString();
6111 options.ifname = Buf.readString();
6112 let addresses = Buf.readString();
6113 let dnses = Buf.readString();
6114 let gateways = Buf.readString();
6115 options.addresses = addresses ? addresses.split(" ") : [];
6116 options.dnses = dnses ? dnses.split(" ") : [];
6117 options.gateways = gateways ? gateways.split(" ") : [];
6118 return options;
6119 };
6121 RilObject.prototype[REQUEST_DATA_CALL_LIST] = function REQUEST_DATA_CALL_LIST(length, options) {
6122 if (options.rilRequestError) {
6123 return;
6124 }
6126 if (!length) {
6127 this._processDataCallList(null);
6128 return;
6129 }
6131 let Buf = this.context.Buf;
6132 let version = 0;
6133 if (!this.v5Legacy) {
6134 version = Buf.readInt32();
6135 }
6136 let num = Buf.readInt32();
6137 let datacalls = {};
6138 for (let i = 0; i < num; i++) {
6139 let datacall;
6140 if (version < 6) {
6141 datacall = this.readDataCall_v5();
6142 } else {
6143 datacall = this.readDataCall_v6();
6144 }
6145 datacalls[datacall.cid] = datacall;
6146 }
6148 let newDataCallOptions = null;
6149 if (options.rilRequestType == REQUEST_SETUP_DATA_CALL) {
6150 newDataCallOptions = options;
6151 }
6152 this._processDataCallList(datacalls, newDataCallOptions);
6153 };
6154 RilObject.prototype[REQUEST_RESET_RADIO] = null;
6155 RilObject.prototype[REQUEST_OEM_HOOK_RAW] = null;
6156 RilObject.prototype[REQUEST_OEM_HOOK_STRINGS] = null;
6157 RilObject.prototype[REQUEST_SCREEN_STATE] = null;
6158 RilObject.prototype[REQUEST_SET_SUPP_SVC_NOTIFICATION] = null;
6159 RilObject.prototype[REQUEST_WRITE_SMS_TO_SIM] = function REQUEST_WRITE_SMS_TO_SIM(length, options) {
6160 if (options.rilRequestError) {
6161 // `The MS shall return a "protocol error, unspecified" error message if
6162 // the short message cannot be stored in the (U)SIM, and there is other
6163 // message storage available at the MS` ~ 3GPP TS 23.038 section 4. Here
6164 // we assume we always have indexed db as another storage.
6165 this.acknowledgeGsmSms(false, PDU_FCS_PROTOCOL_ERROR);
6166 } else {
6167 this.acknowledgeGsmSms(true, PDU_FCS_OK);
6168 }
6169 };
6170 RilObject.prototype[REQUEST_DELETE_SMS_ON_SIM] = null;
6171 RilObject.prototype[REQUEST_SET_BAND_MODE] = null;
6172 RilObject.prototype[REQUEST_QUERY_AVAILABLE_BAND_MODE] = null;
6173 RilObject.prototype[REQUEST_STK_GET_PROFILE] = null;
6174 RilObject.prototype[REQUEST_STK_SET_PROFILE] = null;
6175 RilObject.prototype[REQUEST_STK_SEND_ENVELOPE_COMMAND] = null;
6176 RilObject.prototype[REQUEST_STK_SEND_TERMINAL_RESPONSE] = null;
6177 RilObject.prototype[REQUEST_STK_HANDLE_CALL_SETUP_REQUESTED_FROM_SIM] = null;
6178 RilObject.prototype[REQUEST_EXPLICIT_CALL_TRANSFER] = null;
6179 RilObject.prototype[REQUEST_SET_PREFERRED_NETWORK_TYPE] = function REQUEST_SET_PREFERRED_NETWORK_TYPE(length, options) {
6180 if (options.networkType == null) {
6181 // The request was made by ril_worker itself automatically. Don't report.
6182 return;
6183 }
6185 if (options.rilRequestError) {
6186 options.success = false;
6187 options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError];
6188 } else {
6189 options.success = true;
6190 }
6191 this.sendChromeMessage(options);
6192 };
6193 RilObject.prototype[REQUEST_GET_PREFERRED_NETWORK_TYPE] = function REQUEST_GET_PREFERRED_NETWORK_TYPE(length, options) {
6194 if (options.rilRequestError) {
6195 options.success = false;
6196 options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError];
6197 this.sendChromeMessage(options);
6198 return;
6199 }
6201 let results = this.context.Buf.readInt32List();
6202 options.networkType = this.preferredNetworkType = results[0];
6203 options.success = true;
6205 this.sendChromeMessage(options);
6206 };
6207 RilObject.prototype[REQUEST_GET_NEIGHBORING_CELL_IDS] = null;
6208 RilObject.prototype[REQUEST_SET_LOCATION_UPDATES] = null;
6209 RilObject.prototype[REQUEST_CDMA_SET_SUBSCRIPTION_SOURCE] = null;
6210 RilObject.prototype[REQUEST_CDMA_SET_ROAMING_PREFERENCE] = function REQUEST_CDMA_SET_ROAMING_PREFERENCE(length, options) {
6211 if (options.rilRequestError) {
6212 options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError];
6213 }
6214 this.sendChromeMessage(options);
6215 };
6216 RilObject.prototype[REQUEST_CDMA_QUERY_ROAMING_PREFERENCE] = function REQUEST_CDMA_QUERY_ROAMING_PREFERENCE(length, options) {
6217 if (options.rilRequestError) {
6218 options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError];
6219 } else {
6220 let mode = this.context.Buf.readInt32List();
6221 options.mode = CDMA_ROAMING_PREFERENCE_TO_GECKO[mode[0]];
6222 }
6223 this.sendChromeMessage(options);
6224 };
6225 RilObject.prototype[REQUEST_SET_TTY_MODE] = null;
6226 RilObject.prototype[REQUEST_QUERY_TTY_MODE] = null;
6227 RilObject.prototype[REQUEST_CDMA_SET_PREFERRED_VOICE_PRIVACY_MODE] = function REQUEST_CDMA_SET_PREFERRED_VOICE_PRIVACY_MODE(length, options) {
6228 if (options.rilRequestError) {
6229 options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError];
6230 this.sendChromeMessage(options);
6231 return;
6232 }
6234 this.sendChromeMessage(options);
6235 };
6236 RilObject.prototype[REQUEST_CDMA_QUERY_PREFERRED_VOICE_PRIVACY_MODE] = function REQUEST_CDMA_QUERY_PREFERRED_VOICE_PRIVACY_MODE(length, options) {
6237 if (options.rilRequestError) {
6238 options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError];
6239 this.sendChromeMessage(options);
6240 return;
6241 }
6243 let enabled = this.context.Buf.readInt32List();
6244 options.enabled = enabled[0] ? true : false;
6245 this.sendChromeMessage(options);
6246 };
6247 RilObject.prototype[REQUEST_CDMA_FLASH] = function REQUEST_CDMA_FLASH(length, options) {
6248 options.success = (options.rilRequestError === 0);
6249 if (!options.success) {
6250 if (options.rilMessageType === "conferenceCall") {
6251 options.errorName = "addError";
6252 } else if (options.rilMessageType === "separateCall") {
6253 options.errorName = "removeError";
6254 }
6255 options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError];
6256 }
6258 this.sendChromeMessage(options);
6259 };
6260 RilObject.prototype[REQUEST_CDMA_BURST_DTMF] = null;
6261 RilObject.prototype[REQUEST_CDMA_VALIDATE_AND_WRITE_AKEY] = null;
6262 RilObject.prototype[REQUEST_CDMA_SEND_SMS] = function REQUEST_CDMA_SEND_SMS(length, options) {
6263 this._processSmsSendResult(length, options);
6264 };
6265 RilObject.prototype[REQUEST_CDMA_SMS_ACKNOWLEDGE] = null;
6266 RilObject.prototype[REQUEST_GSM_GET_BROADCAST_SMS_CONFIG] = null;
6267 RilObject.prototype[REQUEST_GSM_SET_BROADCAST_SMS_CONFIG] = function REQUEST_GSM_SET_BROADCAST_SMS_CONFIG(length, options) {
6268 if (options.rilRequestError == ERROR_SUCCESS) {
6269 this.setSmsBroadcastActivation(true);
6270 }
6271 };
6272 RilObject.prototype[REQUEST_GSM_SMS_BROADCAST_ACTIVATION] = null;
6273 RilObject.prototype[REQUEST_CDMA_GET_BROADCAST_SMS_CONFIG] = null;
6274 RilObject.prototype[REQUEST_CDMA_SET_BROADCAST_SMS_CONFIG] = null;
6275 RilObject.prototype[REQUEST_CDMA_SMS_BROADCAST_ACTIVATION] = null;
6276 RilObject.prototype[REQUEST_CDMA_SUBSCRIPTION] = function REQUEST_CDMA_SUBSCRIPTION(length, options) {
6277 if (options.rilRequestError) {
6278 return;
6279 }
6281 let result = this.context.Buf.readStringList();
6283 this.iccInfo.mdn = result[0];
6284 // The result[1] is Home SID. (Already be handled in readCDMAHome())
6285 // The result[2] is Home NID. (Already be handled in readCDMAHome())
6286 // The result[3] is MIN.
6287 this.iccInfo.prlVersion = parseInt(result[4], 10);
6289 this.context.ICCUtilsHelper.handleICCInfoChange();
6290 };
6291 RilObject.prototype[REQUEST_CDMA_WRITE_SMS_TO_RUIM] = null;
6292 RilObject.prototype[REQUEST_CDMA_DELETE_SMS_ON_RUIM] = null;
6293 RilObject.prototype[REQUEST_DEVICE_IDENTITY] = function REQUEST_DEVICE_IDENTITY(length, options) {
6294 if (options.rilRequestError) {
6295 return;
6296 }
6298 let result = this.context.Buf.readStringList();
6300 // The result[0] is for IMEI. (Already be handled in REQUEST_GET_IMEI)
6301 // The result[1] is for IMEISV. (Already be handled in REQUEST_GET_IMEISV)
6302 // They are both ignored.
6303 this.ESN = result[2];
6304 this.MEID = result[3];
6305 };
6306 RilObject.prototype[REQUEST_EXIT_EMERGENCY_CALLBACK_MODE] = function REQUEST_EXIT_EMERGENCY_CALLBACK_MODE(length, options) {
6307 if (options.internal) {
6308 return;
6309 }
6311 options.success = (options.rilRequestError === 0);
6312 if (!options.success) {
6313 options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError];
6314 }
6315 this.sendChromeMessage(options);
6316 };
6317 RilObject.prototype[REQUEST_GET_SMSC_ADDRESS] = function REQUEST_GET_SMSC_ADDRESS(length, options) {
6318 this.SMSC = options.rilRequestError ? null : this.context.Buf.readString();
6320 if (!options.rilMessageType || options.rilMessageType !== "getSmscAddress") {
6321 return;
6322 }
6324 options.smscAddress = this.SMSC;
6325 options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError];
6326 this.sendChromeMessage(options);
6327 };
6328 RilObject.prototype[REQUEST_SET_SMSC_ADDRESS] = null;
6329 RilObject.prototype[REQUEST_REPORT_SMS_MEMORY_STATUS] = null;
6330 RilObject.prototype[REQUEST_REPORT_STK_SERVICE_IS_RUNNING] = null;
6331 RilObject.prototype[REQUEST_ACKNOWLEDGE_INCOMING_GSM_SMS_WITH_PDU] = null;
6332 RilObject.prototype[REQUEST_STK_SEND_ENVELOPE_WITH_STATUS] = function REQUEST_STK_SEND_ENVELOPE_WITH_STATUS(length, options) {
6333 if (options.rilRequestError) {
6334 this.acknowledgeGsmSms(false, PDU_FCS_UNSPECIFIED);
6335 return;
6336 }
6338 let Buf = this.context.Buf;
6339 let sw1 = Buf.readInt32();
6340 let sw2 = Buf.readInt32();
6341 if ((sw1 == ICC_STATUS_SAT_BUSY) && (sw2 === 0x00)) {
6342 this.acknowledgeGsmSms(false, PDU_FCS_USAT_BUSY);
6343 return;
6344 }
6346 let success = ((sw1 == ICC_STATUS_NORMAL_ENDING) && (sw2 === 0x00))
6347 || (sw1 == ICC_STATUS_NORMAL_ENDING_WITH_EXTRA);
6349 let messageStringLength = Buf.readInt32(); // In semi-octets
6350 let responsePduLen = messageStringLength / 2; // In octets
6351 if (!responsePduLen) {
6352 this.acknowledgeGsmSms(success, success ? PDU_FCS_OK
6353 : PDU_FCS_USIM_DATA_DOWNLOAD_ERROR);
6354 return;
6355 }
6357 this.acknowledgeIncomingGsmSmsWithPDU(success, responsePduLen, options);
6358 };
6359 RilObject.prototype[REQUEST_VOICE_RADIO_TECH] = function REQUEST_VOICE_RADIO_TECH(length, options) {
6360 if (options.rilRequestError) {
6361 if (DEBUG) {
6362 this.context.debug("Error when getting voice radio tech: " +
6363 options.rilRequestError);
6364 }
6365 return;
6366 }
6367 let radioTech = this.context.Buf.readInt32List();
6368 this._processRadioTech(radioTech[0]);
6369 };
6370 RilObject.prototype[REQUEST_GET_UNLOCK_RETRY_COUNT] = function REQUEST_GET_UNLOCK_RETRY_COUNT(length, options) {
6371 options.success = (options.rilRequestError === 0);
6372 if (!options.success) {
6373 options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError];
6374 }
6375 options.retryCount = length ? this.context.Buf.readInt32List()[0] : -1;
6376 this.sendChromeMessage(options);
6377 };
6378 RilObject.prototype[RIL_REQUEST_GPRS_ATTACH] = null;
6379 RilObject.prototype[RIL_REQUEST_GPRS_DETACH] = null;
6380 RilObject.prototype[UNSOLICITED_RESPONSE_RADIO_STATE_CHANGED] = function UNSOLICITED_RESPONSE_RADIO_STATE_CHANGED() {
6381 let radioState = this.context.Buf.readInt32();
6382 let newState;
6383 if (radioState == RADIO_STATE_UNAVAILABLE) {
6384 newState = GECKO_RADIOSTATE_UNAVAILABLE;
6385 } else if (radioState == RADIO_STATE_OFF) {
6386 newState = GECKO_RADIOSTATE_OFF;
6387 } else {
6388 newState = GECKO_RADIOSTATE_READY;
6389 }
6391 if (DEBUG) {
6392 this.context.debug("Radio state changed from '" + this.radioState +
6393 "' to '" + newState + "'");
6394 }
6395 if (this.radioState == newState) {
6396 return;
6397 }
6399 switch (radioState) {
6400 case RADIO_STATE_SIM_READY:
6401 case RADIO_STATE_SIM_NOT_READY:
6402 case RADIO_STATE_SIM_LOCKED_OR_ABSENT:
6403 this._isCdma = false;
6404 this._waitingRadioTech = false;
6405 break;
6406 case RADIO_STATE_RUIM_READY:
6407 case RADIO_STATE_RUIM_NOT_READY:
6408 case RADIO_STATE_RUIM_LOCKED_OR_ABSENT:
6409 case RADIO_STATE_NV_READY:
6410 case RADIO_STATE_NV_NOT_READY:
6411 this._isCdma = true;
6412 this._waitingRadioTech = false;
6413 break;
6414 case RADIO_STATE_ON: // RIL v7
6415 // This value is defined in RIL v7, we will retrieve radio tech by another
6416 // request. We leave _isCdma untouched, and it will be set once we get the
6417 // radio technology.
6418 this._waitingRadioTech = true;
6419 this.getVoiceRadioTechnology();
6420 break;
6421 }
6423 if ((this.radioState == GECKO_RADIOSTATE_UNAVAILABLE ||
6424 this.radioState == GECKO_RADIOSTATE_OFF) &&
6425 newState == GECKO_RADIOSTATE_READY) {
6426 // The radio became available, let's get its info.
6427 if (!this._waitingRadioTech) {
6428 if (this._isCdma) {
6429 this.getDeviceIdentity();
6430 } else {
6431 this.getIMEI();
6432 this.getIMEISV();
6433 }
6434 }
6435 this.getBasebandVersion();
6436 this.updateCellBroadcastConfig();
6437 this.setPreferredNetworkType();
6438 this.setCLIR();
6439 if (RILQUIRKS_DATA_REGISTRATION_ON_DEMAND && this._attachDataRegistration) {
6440 this.setDataRegistration({attach: true});
6441 }
6442 }
6444 this.radioState = newState;
6445 this.sendChromeMessage({
6446 rilMessageType: "radiostatechange",
6447 radioState: newState
6448 });
6450 // If the radio is up and on, so let's query the card state.
6451 // On older RILs only if the card is actually ready, though.
6452 // If _waitingRadioTech is set, we don't need to get icc status now.
6453 if (radioState == RADIO_STATE_UNAVAILABLE ||
6454 radioState == RADIO_STATE_OFF ||
6455 this._waitingRadioTech) {
6456 return;
6457 }
6458 this.getICCStatus();
6459 };
6460 RilObject.prototype[UNSOLICITED_RESPONSE_CALL_STATE_CHANGED] = function UNSOLICITED_RESPONSE_CALL_STATE_CHANGED() {
6461 this.getCurrentCalls();
6462 };
6463 RilObject.prototype[UNSOLICITED_RESPONSE_VOICE_NETWORK_STATE_CHANGED] = function UNSOLICITED_RESPONSE_VOICE_NETWORK_STATE_CHANGED() {
6464 if (DEBUG) {
6465 this.context.debug("Network state changed, re-requesting phone state and " +
6466 "ICC status");
6467 }
6468 this.getICCStatus();
6469 this.requestNetworkInfo();
6470 };
6471 RilObject.prototype[UNSOLICITED_RESPONSE_NEW_SMS] = function UNSOLICITED_RESPONSE_NEW_SMS(length) {
6472 let [message, result] = this.context.GsmPDUHelper.processReceivedSms(length);
6474 if (message) {
6475 result = this._processSmsMultipart(message);
6476 }
6478 if (result == PDU_FCS_RESERVED || result == MOZ_FCS_WAIT_FOR_EXPLICIT_ACK) {
6479 return;
6480 }
6482 // Not reserved FCS values, send ACK now.
6483 this.acknowledgeGsmSms(result == PDU_FCS_OK, result);
6484 };
6485 RilObject.prototype[UNSOLICITED_RESPONSE_NEW_SMS_STATUS_REPORT] = function UNSOLICITED_RESPONSE_NEW_SMS_STATUS_REPORT(length) {
6486 let result = this._processSmsStatusReport(length);
6487 this.acknowledgeGsmSms(result == PDU_FCS_OK, result);
6488 };
6489 RilObject.prototype[UNSOLICITED_RESPONSE_NEW_SMS_ON_SIM] = function UNSOLICITED_RESPONSE_NEW_SMS_ON_SIM(length) {
6490 let recordNumber = this.context.Buf.readInt32List()[0];
6492 this.context.SimRecordHelper.readSMS(
6493 recordNumber,
6494 function onsuccess(message) {
6495 if (message && message.simStatus === 3) { //New Unread SMS
6496 this._processSmsMultipart(message);
6497 }
6498 }.bind(this),
6499 function onerror(errorMsg) {
6500 if (DEBUG) {
6501 this.context.debug("Failed to Read NEW SMS on SIM #" + recordNumber +
6502 ", errorMsg: " + errorMsg);
6503 }
6504 });
6505 };
6506 RilObject.prototype[UNSOLICITED_ON_USSD] = function UNSOLICITED_ON_USSD() {
6507 let [typeCode, message] = this.context.Buf.readStringList();
6508 if (DEBUG) {
6509 this.context.debug("On USSD. Type Code: " + typeCode + " Message: " + message);
6510 }
6512 this._ussdSession = (typeCode != "0" && typeCode != "2");
6514 this.sendChromeMessage({rilMessageType: "USSDReceived",
6515 message: message,
6516 sessionEnded: !this._ussdSession});
6517 };
6518 RilObject.prototype[UNSOLICITED_NITZ_TIME_RECEIVED] = function UNSOLICITED_NITZ_TIME_RECEIVED() {
6519 let dateString = this.context.Buf.readString();
6521 // The data contained in the NITZ message is
6522 // in the form "yy/mm/dd,hh:mm:ss(+/-)tz,dt"
6523 // for example: 12/02/16,03:36:08-20,00,310410
6524 // See also bug 714352 - Listen for NITZ updates from rild.
6526 if (DEBUG) this.context.debug("DateTimeZone string " + dateString);
6528 let now = Date.now();
6530 let year = parseInt(dateString.substr(0, 2), 10);
6531 let month = parseInt(dateString.substr(3, 2), 10);
6532 let day = parseInt(dateString.substr(6, 2), 10);
6533 let hours = parseInt(dateString.substr(9, 2), 10);
6534 let minutes = parseInt(dateString.substr(12, 2), 10);
6535 let seconds = parseInt(dateString.substr(15, 2), 10);
6536 // Note that |tz| is in 15-min units.
6537 let tz = parseInt(dateString.substr(17, 3), 10);
6538 // Note that |dst| is in 1-hour units and is already applied in |tz|.
6539 let dst = parseInt(dateString.substr(21, 2), 10);
6541 let timeInMS = Date.UTC(year + PDU_TIMESTAMP_YEAR_OFFSET, month - 1, day,
6542 hours, minutes, seconds);
6544 if (isNaN(timeInMS)) {
6545 if (DEBUG) this.context.debug("NITZ failed to convert date");
6546 return;
6547 }
6549 this.sendChromeMessage({rilMessageType: "nitzTime",
6550 networkTimeInMS: timeInMS,
6551 networkTimeZoneInMinutes: -(tz * 15),
6552 networkDSTInMinutes: -(dst * 60),
6553 receiveTimeInMS: now});
6554 };
6556 RilObject.prototype[UNSOLICITED_SIGNAL_STRENGTH] = function UNSOLICITED_SIGNAL_STRENGTH(length) {
6557 this[REQUEST_SIGNAL_STRENGTH](length, {rilRequestError: ERROR_SUCCESS});
6558 };
6559 RilObject.prototype[UNSOLICITED_DATA_CALL_LIST_CHANGED] = function UNSOLICITED_DATA_CALL_LIST_CHANGED(length) {
6560 if (this.v5Legacy) {
6561 this.getDataCallList();
6562 return;
6563 }
6564 this[REQUEST_DATA_CALL_LIST](length, {rilRequestError: ERROR_SUCCESS});
6565 };
6566 RilObject.prototype[UNSOLICITED_SUPP_SVC_NOTIFICATION] = function UNSOLICITED_SUPP_SVC_NOTIFICATION(length) {
6567 let Buf = this.context.Buf;
6568 let info = {};
6569 info.notificationType = Buf.readInt32();
6570 info.code = Buf.readInt32();
6571 info.index = Buf.readInt32();
6572 info.type = Buf.readInt32();
6573 info.number = Buf.readString();
6575 this._processSuppSvcNotification(info);
6576 };
6578 RilObject.prototype[UNSOLICITED_STK_SESSION_END] = function UNSOLICITED_STK_SESSION_END() {
6579 this.sendChromeMessage({rilMessageType: "stksessionend"});
6580 };
6581 RilObject.prototype[UNSOLICITED_STK_PROACTIVE_COMMAND] = function UNSOLICITED_STK_PROACTIVE_COMMAND() {
6582 this.processStkProactiveCommand();
6583 };
6584 RilObject.prototype[UNSOLICITED_STK_EVENT_NOTIFY] = function UNSOLICITED_STK_EVENT_NOTIFY() {
6585 this.processStkProactiveCommand();
6586 };
6587 RilObject.prototype[UNSOLICITED_STK_CALL_SETUP] = null;
6588 RilObject.prototype[UNSOLICITED_SIM_SMS_STORAGE_FULL] = null;
6589 RilObject.prototype[UNSOLICITED_SIM_REFRESH] = null;
6590 RilObject.prototype[UNSOLICITED_CALL_RING] = function UNSOLICITED_CALL_RING() {
6591 let Buf = this.context.Buf;
6592 let info = {rilMessageType: "callRing"};
6593 let isCDMA = false; //XXX TODO hard-code this for now
6594 if (isCDMA) {
6595 info.isPresent = Buf.readInt32();
6596 info.signalType = Buf.readInt32();
6597 info.alertPitch = Buf.readInt32();
6598 info.signal = Buf.readInt32();
6599 }
6600 // At this point we don't know much other than the fact there's an incoming
6601 // call, but that's enough to bring up the Phone app already. We'll know
6602 // details once we get a call state changed notification and can then
6603 // dispatch DOM events etc.
6604 this.sendChromeMessage(info);
6605 };
6606 RilObject.prototype[UNSOLICITED_RESPONSE_SIM_STATUS_CHANGED] = function UNSOLICITED_RESPONSE_SIM_STATUS_CHANGED() {
6607 this.getICCStatus();
6608 };
6609 RilObject.prototype[UNSOLICITED_RESPONSE_CDMA_NEW_SMS] = function UNSOLICITED_RESPONSE_CDMA_NEW_SMS(length) {
6610 let [message, result] = this.context.CdmaPDUHelper.processReceivedSms(length);
6612 if (message) {
6613 if (message.teleservice === PDU_CDMA_MSG_TELESERIVCIE_ID_WAP) {
6614 result = this._processCdmaSmsWapPush(message);
6615 } else if (message.subMsgType === PDU_CDMA_MSG_TYPE_DELIVER_ACK) {
6616 result = this._processCdmaSmsStatusReport(message);
6617 } else {
6618 result = this._processSmsMultipart(message);
6619 }
6620 }
6622 if (result == PDU_FCS_RESERVED || result == MOZ_FCS_WAIT_FOR_EXPLICIT_ACK) {
6623 return;
6624 }
6626 // Not reserved FCS values, send ACK now.
6627 this.acknowledgeCdmaSms(result == PDU_FCS_OK, result);
6628 };
6629 RilObject.prototype[UNSOLICITED_RESPONSE_NEW_BROADCAST_SMS] = function UNSOLICITED_RESPONSE_NEW_BROADCAST_SMS(length) {
6630 let message;
6631 try {
6632 message =
6633 this.context.GsmPDUHelper.readCbMessage(this.context.Buf.readInt32());
6634 } catch (e) {
6635 if (DEBUG) {
6636 this.context.debug("Failed to parse Cell Broadcast message: " +
6637 JSON.stringify(e));
6638 }
6639 return;
6640 }
6642 message = this._processReceivedSmsCbPage(message);
6643 if (!message) {
6644 return;
6645 }
6647 message.rilMessageType = "cellbroadcast-received";
6648 this.sendChromeMessage(message);
6649 };
6650 RilObject.prototype[UNSOLICITED_CDMA_RUIM_SMS_STORAGE_FULL] = null;
6651 RilObject.prototype[UNSOLICITED_RESTRICTED_STATE_CHANGED] = null;
6652 RilObject.prototype[UNSOLICITED_ENTER_EMERGENCY_CALLBACK_MODE] = function UNSOLICITED_ENTER_EMERGENCY_CALLBACK_MODE() {
6653 this._handleChangedEmergencyCbMode(true);
6654 };
6655 RilObject.prototype[UNSOLICITED_CDMA_CALL_WAITING] = function UNSOLICITED_CDMA_CALL_WAITING(length) {
6656 let Buf = this.context.Buf;
6657 let call = {};
6658 call.number = Buf.readString();
6659 call.numberPresentation = Buf.readInt32();
6660 call.name = Buf.readString();
6661 call.namePresentation = Buf.readInt32();
6662 call.isPresent = Buf.readInt32();
6663 call.signalType = Buf.readInt32();
6664 call.alertPitch = Buf.readInt32();
6665 call.signal = Buf.readInt32();
6666 this.sendChromeMessage({rilMessageType: "cdmaCallWaiting",
6667 number: call.number});
6668 };
6669 RilObject.prototype[UNSOLICITED_CDMA_OTA_PROVISION_STATUS] = function UNSOLICITED_CDMA_OTA_PROVISION_STATUS() {
6670 let status = this.context.Buf.readInt32List()[0];
6671 this.sendChromeMessage({rilMessageType: "otastatuschange",
6672 status: status});
6673 };
6674 RilObject.prototype[UNSOLICITED_CDMA_INFO_REC] = function UNSOLICITED_CDMA_INFO_REC(length) {
6675 let record = this.context.CdmaPDUHelper.decodeInformationRecord();
6676 record.rilMessageType = "cdma-info-rec-received";
6677 this.sendChromeMessage(record);
6678 };
6679 RilObject.prototype[UNSOLICITED_OEM_HOOK_RAW] = null;
6680 RilObject.prototype[UNSOLICITED_RINGBACK_TONE] = null;
6681 RilObject.prototype[UNSOLICITED_RESEND_INCALL_MUTE] = null;
6682 RilObject.prototype[UNSOLICITED_CDMA_SUBSCRIPTION_SOURCE_CHANGED] = null;
6683 RilObject.prototype[UNSOLICITED_CDMA_PRL_CHANGED] = function UNSOLICITED_CDMA_PRL_CHANGED(length) {
6684 let version = this.context.Buf.readInt32List()[0];
6685 if (version !== this.iccInfo.prlVersion) {
6686 this.iccInfo.prlVersion = version;
6687 this.context.ICCUtilsHelper.handleICCInfoChange();
6688 }
6689 };
6690 RilObject.prototype[UNSOLICITED_EXIT_EMERGENCY_CALLBACK_MODE] = function UNSOLICITED_EXIT_EMERGENCY_CALLBACK_MODE() {
6691 this._handleChangedEmergencyCbMode(false);
6692 };
6693 RilObject.prototype[UNSOLICITED_RIL_CONNECTED] = function UNSOLICITED_RIL_CONNECTED(length) {
6694 // Prevent response id collision between UNSOLICITED_RIL_CONNECTED and
6695 // UNSOLICITED_VOICE_RADIO_TECH_CHANGED for Akami on gingerbread branch.
6696 if (!length) {
6697 return;
6698 }
6700 let version = this.context.Buf.readInt32List()[0];
6701 this.v5Legacy = (version < 5);
6702 if (DEBUG) {
6703 this.context.debug("Detected RIL version " + version);
6704 this.context.debug("this.v5Legacy is " + this.v5Legacy);
6705 }
6707 this.initRILState();
6708 // Always ensure that we are not in emergency callback mode when init.
6709 this.exitEmergencyCbMode();
6710 // Reset radio in the case that b2g restart (or crash).
6711 this.setRadioEnabled({enabled: false});
6712 };
6714 /**
6715 * This object exposes the functionality to parse and serialize PDU strings
6716 *
6717 * A PDU is a string containing a series of hexadecimally encoded octets
6718 * or nibble-swapped binary-coded decimals (BCDs). It contains not only the
6719 * message text but information about the sender, the SMS service center,
6720 * timestamp, etc.
6721 */
6722 function GsmPDUHelperObject(aContext) {
6723 this.context = aContext;
6724 }
6725 GsmPDUHelperObject.prototype = {
6726 context: null,
6728 /**
6729 * Read one character (2 bytes) from a RIL string and decode as hex.
6730 *
6731 * @return the nibble as a number.
6732 */
6733 readHexNibble: function() {
6734 let nibble = this.context.Buf.readUint16();
6735 if (nibble >= 48 && nibble <= 57) {
6736 nibble -= 48; // ASCII '0'..'9'
6737 } else if (nibble >= 65 && nibble <= 70) {
6738 nibble -= 55; // ASCII 'A'..'F'
6739 } else if (nibble >= 97 && nibble <= 102) {
6740 nibble -= 87; // ASCII 'a'..'f'
6741 } else {
6742 throw "Found invalid nibble during PDU parsing: " +
6743 String.fromCharCode(nibble);
6744 }
6745 return nibble;
6746 },
6748 /**
6749 * Encode a nibble as one hex character in a RIL string (2 bytes).
6750 *
6751 * @param nibble
6752 * The nibble to encode (represented as a number)
6753 */
6754 writeHexNibble: function(nibble) {
6755 nibble &= 0x0f;
6756 if (nibble < 10) {
6757 nibble += 48; // ASCII '0'
6758 } else {
6759 nibble += 55; // ASCII 'A'
6760 }
6761 this.context.Buf.writeUint16(nibble);
6762 },
6764 /**
6765 * Read a hex-encoded octet (two nibbles).
6766 *
6767 * @return the octet as a number.
6768 */
6769 readHexOctet: function() {
6770 return (this.readHexNibble() << 4) | this.readHexNibble();
6771 },
6773 /**
6774 * Write an octet as two hex-encoded nibbles.
6775 *
6776 * @param octet
6777 * The octet (represented as a number) to encode.
6778 */
6779 writeHexOctet: function(octet) {
6780 this.writeHexNibble(octet >> 4);
6781 this.writeHexNibble(octet);
6782 },
6784 /**
6785 * Read an array of hex-encoded octets.
6786 */
6787 readHexOctetArray: function(length) {
6788 let array = new Uint8Array(length);
6789 for (let i = 0; i < length; i++) {
6790 array[i] = this.readHexOctet();
6791 }
6792 return array;
6793 },
6795 /**
6796 * Convert an octet (number) to a BCD number.
6797 *
6798 * Any nibbles that are not in the BCD range count as 0.
6799 *
6800 * @param octet
6801 * The octet (a number, as returned by getOctet())
6802 *
6803 * @return the corresponding BCD number.
6804 */
6805 octetToBCD: function(octet) {
6806 return ((octet & 0xf0) <= 0x90) * ((octet >> 4) & 0x0f) +
6807 ((octet & 0x0f) <= 0x09) * (octet & 0x0f) * 10;
6808 },
6810 /**
6811 * Convert a BCD number to an octet (number)
6812 *
6813 * Only take two digits with absolute value.
6814 *
6815 * @param bcd
6816 *
6817 * @return the corresponding octet.
6818 */
6819 BCDToOctet: function(bcd) {
6820 bcd = Math.abs(bcd);
6821 return ((bcd % 10) << 4) + (Math.floor(bcd / 10) % 10);
6822 },
6824 /**
6825 * Convert a semi-octet (number) to a GSM BCD char, or return empty string
6826 * if invalid semiOctet and supressException is set to true.
6827 *
6828 * @param semiOctet
6829 * Nibble to be converted to.
6830 * @param [optional] supressException
6831 * Supress exception if invalid semiOctet and supressException is set
6832 * to true.
6833 *
6834 * @return GSM BCD char, or empty string.
6835 */
6836 bcdChars: "0123456789*#,;",
6837 semiOctetToBcdChar: function(semiOctet, supressException) {
6838 if (semiOctet >= 14) {
6839 if (supressException) {
6840 return "";
6841 } else {
6842 throw new RangeError();
6843 }
6844 }
6846 return this.bcdChars.charAt(semiOctet);
6847 },
6849 /**
6850 * Read a *swapped nibble* binary coded decimal (BCD)
6851 *
6852 * @param pairs
6853 * Number of nibble *pairs* to read.
6854 *
6855 * @return the decimal as a number.
6856 */
6857 readSwappedNibbleBcdNum: function(pairs) {
6858 let number = 0;
6859 for (let i = 0; i < pairs; i++) {
6860 let octet = this.readHexOctet();
6861 // Ignore 'ff' octets as they're often used as filler.
6862 if (octet == 0xff) {
6863 continue;
6864 }
6865 // If the first nibble is an "F" , only the second nibble is to be taken
6866 // into account.
6867 if ((octet & 0xf0) == 0xf0) {
6868 number *= 10;
6869 number += octet & 0x0f;
6870 continue;
6871 }
6872 number *= 100;
6873 number += this.octetToBCD(octet);
6874 }
6875 return number;
6876 },
6878 /**
6879 * Read a *swapped nibble* binary coded string (BCD)
6880 *
6881 * @param pairs
6882 * Number of nibble *pairs* to read.
6883 * @param [optional] supressException
6884 * Supress exception if invalid semiOctet and supressException is set
6885 * to true.
6886 *
6887 * @return The BCD string.
6888 */
6889 readSwappedNibbleBcdString: function(pairs, supressException) {
6890 let str = "";
6891 for (let i = 0; i < pairs; i++) {
6892 let nibbleH = this.readHexNibble();
6893 let nibbleL = this.readHexNibble();
6894 if (nibbleL == 0x0F) {
6895 break;
6896 }
6898 str += this.semiOctetToBcdChar(nibbleL, supressException);
6899 if (nibbleH != 0x0F) {
6900 str += this.semiOctetToBcdChar(nibbleH, supressException);
6901 }
6902 }
6904 return str;
6905 },
6907 /**
6908 * Write numerical data as swapped nibble BCD.
6909 *
6910 * @param data
6911 * Data to write (as a string or a number)
6912 */
6913 writeSwappedNibbleBCD: function(data) {
6914 data = data.toString();
6915 if (data.length % 2) {
6916 data += "F";
6917 }
6918 let Buf = this.context.Buf;
6919 for (let i = 0; i < data.length; i += 2) {
6920 Buf.writeUint16(data.charCodeAt(i + 1));
6921 Buf.writeUint16(data.charCodeAt(i));
6922 }
6923 },
6925 /**
6926 * Write numerical data as swapped nibble BCD.
6927 * If the number of digit of data is even, add '0' at the beginning.
6928 *
6929 * @param data
6930 * Data to write (as a string or a number)
6931 */
6932 writeSwappedNibbleBCDNum: function(data) {
6933 data = data.toString();
6934 if (data.length % 2) {
6935 data = "0" + data;
6936 }
6937 let Buf = this.context.Buf;
6938 for (let i = 0; i < data.length; i += 2) {
6939 Buf.writeUint16(data.charCodeAt(i + 1));
6940 Buf.writeUint16(data.charCodeAt(i));
6941 }
6942 },
6944 /**
6945 * Read user data, convert to septets, look up relevant characters in a
6946 * 7-bit alphabet, and construct string.
6947 *
6948 * @param length
6949 * Number of septets to read (*not* octets)
6950 * @param paddingBits
6951 * Number of padding bits in the first byte of user data.
6952 * @param langIndex
6953 * Table index used for normal 7-bit encoded character lookup.
6954 * @param langShiftIndex
6955 * Table index used for escaped 7-bit encoded character lookup.
6956 *
6957 * @return a string.
6958 */
6959 readSeptetsToString: function(length, paddingBits, langIndex, langShiftIndex) {
6960 let ret = "";
6961 let byteLength = Math.ceil((length * 7 + paddingBits) / 8);
6963 /**
6964 * |<- last byte in header ->|
6965 * |<- incompleteBits ->|<- last header septet->|
6966 * +===7===|===6===|===5===|===4===|===3===|===2===|===1===|===0===|
6967 *
6968 * |<- 1st byte in user data ->|
6969 * |<- data septet 1 ->|<-paddingBits->|
6970 * +===7===|===6===|===5===|===4===|===3===|===2===|===1===|===0===|
6971 *
6972 * |<- 2nd byte in user data ->|
6973 * |<- data spetet 2 ->|<-ds1->|
6974 * +===7===|===6===|===5===|===4===|===3===|===2===|===1===|===0===|
6975 */
6976 let data = 0;
6977 let dataBits = 0;
6978 if (paddingBits) {
6979 data = this.readHexOctet() >> paddingBits;
6980 dataBits = 8 - paddingBits;
6981 --byteLength;
6982 }
6984 let escapeFound = false;
6985 const langTable = PDU_NL_LOCKING_SHIFT_TABLES[langIndex];
6986 const langShiftTable = PDU_NL_SINGLE_SHIFT_TABLES[langShiftIndex];
6987 do {
6988 // Read as much as fits in 32bit word
6989 let bytesToRead = Math.min(byteLength, dataBits ? 3 : 4);
6990 for (let i = 0; i < bytesToRead; i++) {
6991 data |= this.readHexOctet() << dataBits;
6992 dataBits += 8;
6993 --byteLength;
6994 }
6996 // Consume available full septets
6997 for (; dataBits >= 7; dataBits -= 7) {
6998 let septet = data & 0x7F;
6999 data >>>= 7;
7001 if (escapeFound) {
7002 escapeFound = false;
7003 if (septet == PDU_NL_EXTENDED_ESCAPE) {
7004 // According to 3GPP TS 23.038, section 6.2.1.1, NOTE 1, "On
7005 // receipt of this code, a receiving entity shall display a space
7006 // until another extensiion table is defined."
7007 ret += " ";
7008 } else if (septet == PDU_NL_RESERVED_CONTROL) {
7009 // According to 3GPP TS 23.038 B.2, "This code represents a control
7010 // character and therefore must not be used for language specific
7011 // characters."
7012 ret += " ";
7013 } else {
7014 ret += langShiftTable[septet];
7015 }
7016 } else if (septet == PDU_NL_EXTENDED_ESCAPE) {
7017 escapeFound = true;
7019 // <escape> is not an effective character
7020 --length;
7021 } else {
7022 ret += langTable[septet];
7023 }
7024 }
7025 } while (byteLength);
7027 if (ret.length != length) {
7028 /**
7029 * If num of effective characters does not equal to the length of read
7030 * string, cut the tail off. This happens when the last octet of user
7031 * data has following layout:
7032 *
7033 * |<- penultimate octet in user data ->|
7034 * |<- data septet N ->|<- dsN-1 ->|
7035 * +===7===|===6===|===5===|===4===|===3===|===2===|===1===|===0===|
7036 *
7037 * |<- last octet in user data ->|
7038 * |<- fill bits ->|<-dsN->|
7039 * +===7===|===6===|===5===|===4===|===3===|===2===|===1===|===0===|
7040 *
7041 * The fill bits in the last octet may happen to form a full septet and
7042 * be appended at the end of result string.
7043 */
7044 ret = ret.slice(0, length);
7045 }
7046 return ret;
7047 },
7049 writeStringAsSeptets: function(message, paddingBits, langIndex, langShiftIndex) {
7050 const langTable = PDU_NL_LOCKING_SHIFT_TABLES[langIndex];
7051 const langShiftTable = PDU_NL_SINGLE_SHIFT_TABLES[langShiftIndex];
7053 let dataBits = paddingBits;
7054 let data = 0;
7055 for (let i = 0; i < message.length; i++) {
7056 let c = message.charAt(i);
7057 let septet = langTable.indexOf(c);
7058 if (septet == PDU_NL_EXTENDED_ESCAPE) {
7059 continue;
7060 }
7062 if (septet >= 0) {
7063 data |= septet << dataBits;
7064 dataBits += 7;
7065 } else {
7066 septet = langShiftTable.indexOf(c);
7067 if (septet == -1) {
7068 throw new Error("'" + c + "' is not in 7 bit alphabet "
7069 + langIndex + ":" + langShiftIndex + "!");
7070 }
7072 if (septet == PDU_NL_RESERVED_CONTROL) {
7073 continue;
7074 }
7076 data |= PDU_NL_EXTENDED_ESCAPE << dataBits;
7077 dataBits += 7;
7078 data |= septet << dataBits;
7079 dataBits += 7;
7080 }
7082 for (; dataBits >= 8; dataBits -= 8) {
7083 this.writeHexOctet(data & 0xFF);
7084 data >>>= 8;
7085 }
7086 }
7088 if (dataBits !== 0) {
7089 this.writeHexOctet(data & 0xFF);
7090 }
7091 },
7093 /**
7094 * Read user data and decode as a UCS2 string.
7095 *
7096 * @param numOctets
7097 * Number of octets to be read as UCS2 string.
7098 *
7099 * @return a string.
7100 */
7101 readUCS2String: function(numOctets) {
7102 let str = "";
7103 let length = numOctets / 2;
7104 for (let i = 0; i < length; ++i) {
7105 let code = (this.readHexOctet() << 8) | this.readHexOctet();
7106 str += String.fromCharCode(code);
7107 }
7109 if (DEBUG) this.context.debug("Read UCS2 string: " + str);
7111 return str;
7112 },
7114 /**
7115 * Write user data as a UCS2 string.
7116 *
7117 * @param message
7118 * Message string to encode as UCS2 in hex-encoded octets.
7119 */
7120 writeUCS2String: function(message) {
7121 for (let i = 0; i < message.length; ++i) {
7122 let code = message.charCodeAt(i);
7123 this.writeHexOctet((code >> 8) & 0xFF);
7124 this.writeHexOctet(code & 0xFF);
7125 }
7126 },
7128 /**
7129 * Read 1 + UDHL octets and construct user data header.
7130 *
7131 * @param msg
7132 * message object for output.
7133 *
7134 * @see 3GPP TS 23.040 9.2.3.24
7135 */
7136 readUserDataHeader: function(msg) {
7137 /**
7138 * A header object with properties contained in received message.
7139 * The properties set include:
7140 *
7141 * length: totoal length of the header, default 0.
7142 * langIndex: used locking shift table index, default
7143 * PDU_NL_IDENTIFIER_DEFAULT.
7144 * langShiftIndex: used locking shift table index, default
7145 * PDU_NL_IDENTIFIER_DEFAULT.
7146 *
7147 */
7148 let header = {
7149 length: 0,
7150 langIndex: PDU_NL_IDENTIFIER_DEFAULT,
7151 langShiftIndex: PDU_NL_IDENTIFIER_DEFAULT
7152 };
7154 header.length = this.readHexOctet();
7155 if (DEBUG) this.context.debug("Read UDH length: " + header.length);
7157 let dataAvailable = header.length;
7158 while (dataAvailable >= 2) {
7159 let id = this.readHexOctet();
7160 let length = this.readHexOctet();
7161 if (DEBUG) this.context.debug("Read UDH id: " + id + ", length: " + length);
7163 dataAvailable -= 2;
7165 switch (id) {
7166 case PDU_IEI_CONCATENATED_SHORT_MESSAGES_8BIT: {
7167 let ref = this.readHexOctet();
7168 let max = this.readHexOctet();
7169 let seq = this.readHexOctet();
7170 dataAvailable -= 3;
7171 if (max && seq && (seq <= max)) {
7172 header.segmentRef = ref;
7173 header.segmentMaxSeq = max;
7174 header.segmentSeq = seq;
7175 }
7176 break;
7177 }
7178 case PDU_IEI_APPLICATION_PORT_ADDRESSING_SCHEME_8BIT: {
7179 let dstp = this.readHexOctet();
7180 let orip = this.readHexOctet();
7181 dataAvailable -= 2;
7182 if ((dstp < PDU_APA_RESERVED_8BIT_PORTS)
7183 || (orip < PDU_APA_RESERVED_8BIT_PORTS)) {
7184 // 3GPP TS 23.040 clause 9.2.3.24.3: "A receiving entity shall
7185 // ignore any information element where the value of the
7186 // Information-Element-Data is Reserved or not supported"
7187 break;
7188 }
7189 header.destinationPort = dstp;
7190 header.originatorPort = orip;
7191 break;
7192 }
7193 case PDU_IEI_APPLICATION_PORT_ADDRESSING_SCHEME_16BIT: {
7194 let dstp = (this.readHexOctet() << 8) | this.readHexOctet();
7195 let orip = (this.readHexOctet() << 8) | this.readHexOctet();
7196 dataAvailable -= 4;
7197 // 3GPP TS 23.040 clause 9.2.3.24.4: "A receiving entity shall
7198 // ignore any information element where the value of the
7199 // Information-Element-Data is Reserved or not supported"
7200 if ((dstp < PDU_APA_VALID_16BIT_PORTS)
7201 && (orip < PDU_APA_VALID_16BIT_PORTS)) {
7202 header.destinationPort = dstp;
7203 header.originatorPort = orip;
7204 }
7205 break;
7206 }
7207 case PDU_IEI_CONCATENATED_SHORT_MESSAGES_16BIT: {
7208 let ref = (this.readHexOctet() << 8) | this.readHexOctet();
7209 let max = this.readHexOctet();
7210 let seq = this.readHexOctet();
7211 dataAvailable -= 4;
7212 if (max && seq && (seq <= max)) {
7213 header.segmentRef = ref;
7214 header.segmentMaxSeq = max;
7215 header.segmentSeq = seq;
7216 }
7217 break;
7218 }
7219 case PDU_IEI_NATIONAL_LANGUAGE_SINGLE_SHIFT:
7220 let langShiftIndex = this.readHexOctet();
7221 --dataAvailable;
7222 if (langShiftIndex < PDU_NL_SINGLE_SHIFT_TABLES.length) {
7223 header.langShiftIndex = langShiftIndex;
7224 }
7225 break;
7226 case PDU_IEI_NATIONAL_LANGUAGE_LOCKING_SHIFT:
7227 let langIndex = this.readHexOctet();
7228 --dataAvailable;
7229 if (langIndex < PDU_NL_LOCKING_SHIFT_TABLES.length) {
7230 header.langIndex = langIndex;
7231 }
7232 break;
7233 case PDU_IEI_SPECIAL_SMS_MESSAGE_INDICATION:
7234 let msgInd = this.readHexOctet() & 0xFF;
7235 let msgCount = this.readHexOctet();
7236 dataAvailable -= 2;
7239 /*
7240 * TS 23.040 V6.8.1 Sec 9.2.3.24.2
7241 * bits 1 0 : basic message indication type
7242 * bits 4 3 2 : extended message indication type
7243 * bits 6 5 : Profile id
7244 * bit 7 : storage type
7245 */
7246 let storeType = msgInd & PDU_MWI_STORE_TYPE_BIT;
7247 let mwi = msg.mwi;
7248 if (!mwi) {
7249 mwi = msg.mwi = {};
7250 }
7252 if (storeType == PDU_MWI_STORE_TYPE_STORE) {
7253 // Store message because TP_UDH indicates so, note this may override
7254 // the setting in DCS, but that is expected
7255 mwi.discard = false;
7256 } else if (mwi.discard === undefined) {
7257 // storeType == PDU_MWI_STORE_TYPE_DISCARD
7258 // only override mwi.discard here if it hasn't already been set
7259 mwi.discard = true;
7260 }
7262 mwi.msgCount = msgCount & 0xFF;
7263 mwi.active = mwi.msgCount > 0;
7265 if (DEBUG) {
7266 this.context.debug("MWI in TP_UDH received: " + JSON.stringify(mwi));
7267 }
7269 break;
7270 default:
7271 if (DEBUG) {
7272 this.context.debug("readUserDataHeader: unsupported IEI(" + id +
7273 "), " + length + " bytes.");
7274 }
7276 // Read out unsupported data
7277 if (length) {
7278 let octets;
7279 if (DEBUG) octets = new Uint8Array(length);
7281 for (let i = 0; i < length; i++) {
7282 let octet = this.readHexOctet();
7283 if (DEBUG) octets[i] = octet;
7284 }
7285 dataAvailable -= length;
7287 if (DEBUG) {
7288 this.context.debug("readUserDataHeader: " + Array.slice(octets));
7289 }
7290 }
7291 break;
7292 }
7293 }
7295 if (dataAvailable !== 0) {
7296 throw new Error("Illegal user data header found!");
7297 }
7299 msg.header = header;
7300 },
7302 /**
7303 * Write out user data header.
7304 *
7305 * @param options
7306 * Options containing information for user data header write-out. The
7307 * `userDataHeaderLength` property must be correctly pre-calculated.
7308 */
7309 writeUserDataHeader: function(options) {
7310 this.writeHexOctet(options.userDataHeaderLength);
7312 if (options.segmentMaxSeq > 1) {
7313 if (options.segmentRef16Bit) {
7314 this.writeHexOctet(PDU_IEI_CONCATENATED_SHORT_MESSAGES_16BIT);
7315 this.writeHexOctet(4);
7316 this.writeHexOctet((options.segmentRef >> 8) & 0xFF);
7317 } else {
7318 this.writeHexOctet(PDU_IEI_CONCATENATED_SHORT_MESSAGES_8BIT);
7319 this.writeHexOctet(3);
7320 }
7321 this.writeHexOctet(options.segmentRef & 0xFF);
7322 this.writeHexOctet(options.segmentMaxSeq & 0xFF);
7323 this.writeHexOctet(options.segmentSeq & 0xFF);
7324 }
7326 if (options.dcs == PDU_DCS_MSG_CODING_7BITS_ALPHABET) {
7327 if (options.langIndex != PDU_NL_IDENTIFIER_DEFAULT) {
7328 this.writeHexOctet(PDU_IEI_NATIONAL_LANGUAGE_LOCKING_SHIFT);
7329 this.writeHexOctet(1);
7330 this.writeHexOctet(options.langIndex);
7331 }
7333 if (options.langShiftIndex != PDU_NL_IDENTIFIER_DEFAULT) {
7334 this.writeHexOctet(PDU_IEI_NATIONAL_LANGUAGE_SINGLE_SHIFT);
7335 this.writeHexOctet(1);
7336 this.writeHexOctet(options.langShiftIndex);
7337 }
7338 }
7339 },
7341 /**
7342 * Read SM-TL Address.
7343 *
7344 * @param len
7345 * Length of useful semi-octets within the Address-Value field. For
7346 * example, the lenth of "12345" should be 5, and 4 for "1234".
7347 *
7348 * @see 3GPP TS 23.040 9.1.2.5
7349 */
7350 readAddress: function(len) {
7351 // Address Length
7352 if (!len || (len < 0)) {
7353 if (DEBUG) {
7354 this.context.debug("PDU error: invalid sender address length: " + len);
7355 }
7356 return null;
7357 }
7358 if (len % 2 == 1) {
7359 len += 1;
7360 }
7361 if (DEBUG) this.context.debug("PDU: Going to read address: " + len);
7363 // Type-of-Address
7364 let toa = this.readHexOctet();
7365 let addr = "";
7367 if ((toa & 0xF0) == PDU_TOA_ALPHANUMERIC) {
7368 addr = this.readSeptetsToString(Math.floor(len * 4 / 7), 0,
7369 PDU_NL_IDENTIFIER_DEFAULT , PDU_NL_IDENTIFIER_DEFAULT );
7370 return addr;
7371 }
7372 addr = this.readSwappedNibbleBcdString(len / 2);
7373 if (addr.length <= 0) {
7374 if (DEBUG) this.context.debug("PDU error: no number provided");
7375 return null;
7376 }
7377 if ((toa & 0xF0) == (PDU_TOA_INTERNATIONAL)) {
7378 addr = '+' + addr;
7379 }
7381 return addr;
7382 },
7384 /**
7385 * Read TP-Protocol-Indicator(TP-PID).
7386 *
7387 * @param msg
7388 * message object for output.
7389 *
7390 * @see 3GPP TS 23.040 9.2.3.9
7391 */
7392 readProtocolIndicator: function(msg) {
7393 // `The MS shall interpret reserved, obsolete, or unsupported values as the
7394 // value 00000000 but shall store them exactly as received.`
7395 msg.pid = this.readHexOctet();
7397 msg.epid = msg.pid;
7398 switch (msg.epid & 0xC0) {
7399 case 0x40:
7400 // Bit 7..0 = 01xxxxxx
7401 switch (msg.epid) {
7402 case PDU_PID_SHORT_MESSAGE_TYPE_0:
7403 case PDU_PID_ANSI_136_R_DATA:
7404 case PDU_PID_USIM_DATA_DOWNLOAD:
7405 return;
7406 }
7407 break;
7408 }
7410 msg.epid = PDU_PID_DEFAULT;
7411 },
7413 /**
7414 * Read TP-Data-Coding-Scheme(TP-DCS)
7415 *
7416 * @param msg
7417 * message object for output.
7418 *
7419 * @see 3GPP TS 23.040 9.2.3.10, 3GPP TS 23.038 4.
7420 */
7421 readDataCodingScheme: function(msg) {
7422 let dcs = this.readHexOctet();
7423 if (DEBUG) this.context.debug("PDU: read SMS dcs: " + dcs);
7425 // No message class by default.
7426 let messageClass = PDU_DCS_MSG_CLASS_NORMAL;
7427 // 7 bit is the default fallback encoding.
7428 let encoding = PDU_DCS_MSG_CODING_7BITS_ALPHABET;
7429 switch (dcs & PDU_DCS_CODING_GROUP_BITS) {
7430 case 0x40: // bits 7..4 = 01xx
7431 case 0x50:
7432 case 0x60:
7433 case 0x70:
7434 // Bit 5..0 are coded exactly the same as Group 00xx
7435 case 0x00: // bits 7..4 = 00xx
7436 case 0x10:
7437 case 0x20:
7438 case 0x30:
7439 if (dcs & 0x10) {
7440 messageClass = dcs & PDU_DCS_MSG_CLASS_BITS;
7441 }
7442 switch (dcs & 0x0C) {
7443 case 0x4:
7444 encoding = PDU_DCS_MSG_CODING_8BITS_ALPHABET;
7445 break;
7446 case 0x8:
7447 encoding = PDU_DCS_MSG_CODING_16BITS_ALPHABET;
7448 break;
7449 }
7450 break;
7452 case 0xE0: // bits 7..4 = 1110
7453 encoding = PDU_DCS_MSG_CODING_16BITS_ALPHABET;
7454 // Bit 3..0 are coded exactly the same as Message Waiting Indication
7455 // Group 1101.
7456 // Fall through.
7457 case 0xC0: // bits 7..4 = 1100
7458 case 0xD0: // bits 7..4 = 1101
7459 // Indiciates voicemail indicator set or clear
7460 let active = (dcs & PDU_DCS_MWI_ACTIVE_BITS) == PDU_DCS_MWI_ACTIVE_VALUE;
7462 // If TP-UDH is present, these values will be overwritten
7463 switch (dcs & PDU_DCS_MWI_TYPE_BITS) {
7464 case PDU_DCS_MWI_TYPE_VOICEMAIL:
7465 let mwi = msg.mwi;
7466 if (!mwi) {
7467 mwi = msg.mwi = {};
7468 }
7470 mwi.active = active;
7471 mwi.discard = (dcs & PDU_DCS_CODING_GROUP_BITS) == 0xC0;
7472 mwi.msgCount = active ? GECKO_VOICEMAIL_MESSAGE_COUNT_UNKNOWN : 0;
7474 if (DEBUG) {
7475 this.context.debug("MWI in DCS received for voicemail: " +
7476 JSON.stringify(mwi));
7477 }
7478 break;
7479 case PDU_DCS_MWI_TYPE_FAX:
7480 if (DEBUG) this.context.debug("MWI in DCS received for fax");
7481 break;
7482 case PDU_DCS_MWI_TYPE_EMAIL:
7483 if (DEBUG) this.context.debug("MWI in DCS received for email");
7484 break;
7485 default:
7486 if (DEBUG) this.context.debug("MWI in DCS received for \"other\"");
7487 break;
7488 }
7489 break;
7491 case 0xF0: // bits 7..4 = 1111
7492 if (dcs & 0x04) {
7493 encoding = PDU_DCS_MSG_CODING_8BITS_ALPHABET;
7494 }
7495 messageClass = dcs & PDU_DCS_MSG_CLASS_BITS;
7496 break;
7498 default:
7499 // Falling back to default encoding.
7500 break;
7501 }
7503 msg.dcs = dcs;
7504 msg.encoding = encoding;
7505 msg.messageClass = GECKO_SMS_MESSAGE_CLASSES[messageClass];
7507 if (DEBUG) this.context.debug("PDU: message encoding is " + encoding + " bit.");
7508 },
7510 /**
7511 * Read GSM TP-Service-Centre-Time-Stamp(TP-SCTS).
7512 *
7513 * @see 3GPP TS 23.040 9.2.3.11
7514 */
7515 readTimestamp: function() {
7516 let year = this.readSwappedNibbleBcdNum(1) + PDU_TIMESTAMP_YEAR_OFFSET;
7517 let month = this.readSwappedNibbleBcdNum(1) - 1;
7518 let day = this.readSwappedNibbleBcdNum(1);
7519 let hour = this.readSwappedNibbleBcdNum(1);
7520 let minute = this.readSwappedNibbleBcdNum(1);
7521 let second = this.readSwappedNibbleBcdNum(1);
7522 let timestamp = Date.UTC(year, month, day, hour, minute, second);
7524 // If the most significant bit of the least significant nibble is 1,
7525 // the timezone offset is negative (fourth bit from the right => 0x08):
7526 // localtime = UTC + tzOffset
7527 // therefore
7528 // UTC = localtime - tzOffset
7529 let tzOctet = this.readHexOctet();
7530 let tzOffset = this.octetToBCD(tzOctet & ~0x08) * 15 * 60 * 1000;
7531 tzOffset = (tzOctet & 0x08) ? -tzOffset : tzOffset;
7532 timestamp -= tzOffset;
7534 return timestamp;
7535 },
7537 /**
7538 * Write GSM TP-Service-Centre-Time-Stamp(TP-SCTS).
7539 *
7540 * @see 3GPP TS 23.040 9.2.3.11
7541 */
7542 writeTimestamp: function(date) {
7543 this.writeSwappedNibbleBCDNum(date.getFullYear() - PDU_TIMESTAMP_YEAR_OFFSET);
7545 // The value returned by getMonth() is an integer between 0 and 11.
7546 // 0 is corresponds to January, 1 to February, and so on.
7547 this.writeSwappedNibbleBCDNum(date.getMonth() + 1);
7548 this.writeSwappedNibbleBCDNum(date.getDate());
7549 this.writeSwappedNibbleBCDNum(date.getHours());
7550 this.writeSwappedNibbleBCDNum(date.getMinutes());
7551 this.writeSwappedNibbleBCDNum(date.getSeconds());
7553 // the value returned by getTimezoneOffset() is the difference,
7554 // in minutes, between UTC and local time.
7555 // For example, if your time zone is UTC+10 (Australian Eastern Standard Time),
7556 // -600 will be returned.
7557 // In TS 23.040 9.2.3.11, the Time Zone field of TP-SCTS indicates
7558 // the different between the local time and GMT.
7559 // And expressed in quarters of an hours. (so need to divid by 15)
7560 let zone = date.getTimezoneOffset() / 15;
7561 let octet = this.BCDToOctet(zone);
7563 // the bit3 of the Time Zone field represents the algebraic sign.
7564 // (0: positive, 1: negative).
7565 // For example, if the time zone is -0800 GMT,
7566 // 480 will be returned by getTimezoneOffset().
7567 // In this case, need to mark sign bit as 1. => 0x08
7568 if (zone > 0) {
7569 octet = octet | 0x08;
7570 }
7571 this.writeHexOctet(octet);
7572 },
7574 /**
7575 * User data can be 7 bit (default alphabet) data, 8 bit data, or 16 bit
7576 * (UCS2) data.
7577 *
7578 * @param msg
7579 * message object for output.
7580 * @param length
7581 * length of user data to read in octets.
7582 */
7583 readUserData: function(msg, length) {
7584 if (DEBUG) {
7585 this.context.debug("Reading " + length + " bytes of user data.");
7586 }
7588 let paddingBits = 0;
7589 if (msg.udhi) {
7590 this.readUserDataHeader(msg);
7592 if (msg.encoding == PDU_DCS_MSG_CODING_7BITS_ALPHABET) {
7593 let headerBits = (msg.header.length + 1) * 8;
7594 let headerSeptets = Math.ceil(headerBits / 7);
7596 length -= headerSeptets;
7597 paddingBits = headerSeptets * 7 - headerBits;
7598 } else {
7599 length -= (msg.header.length + 1);
7600 }
7601 }
7603 if (DEBUG) {
7604 this.context.debug("After header, " + length + " septets left of user data");
7605 }
7607 msg.body = null;
7608 msg.data = null;
7609 switch (msg.encoding) {
7610 case PDU_DCS_MSG_CODING_7BITS_ALPHABET:
7611 // 7 bit encoding allows 140 octets, which means 160 characters
7612 // ((140x8) / 7 = 160 chars)
7613 if (length > PDU_MAX_USER_DATA_7BIT) {
7614 if (DEBUG) {
7615 this.context.debug("PDU error: user data is too long: " + length);
7616 }
7617 break;
7618 }
7620 let langIndex = msg.udhi ? msg.header.langIndex : PDU_NL_IDENTIFIER_DEFAULT;
7621 let langShiftIndex = msg.udhi ? msg.header.langShiftIndex : PDU_NL_IDENTIFIER_DEFAULT;
7622 msg.body = this.readSeptetsToString(length, paddingBits, langIndex,
7623 langShiftIndex);
7624 break;
7625 case PDU_DCS_MSG_CODING_8BITS_ALPHABET:
7626 msg.data = this.readHexOctetArray(length);
7627 break;
7628 case PDU_DCS_MSG_CODING_16BITS_ALPHABET:
7629 msg.body = this.readUCS2String(length);
7630 break;
7631 }
7632 },
7634 /**
7635 * Read extra parameters if TP-PI is set.
7636 *
7637 * @param msg
7638 * message object for output.
7639 */
7640 readExtraParams: function(msg) {
7641 // Because each PDU octet is converted to two UCS2 char2, we should always
7642 // get even messageStringLength in this#_processReceivedSms(). So, we'll
7643 // always need two delimitors at the end.
7644 if (this.context.Buf.getReadAvailable() <= 4) {
7645 return;
7646 }
7648 // TP-Parameter-Indicator
7649 let pi;
7650 do {
7651 // `The most significant bit in octet 1 and any other TP-PI octets which
7652 // may be added later is reserved as an extension bit which when set to a
7653 // 1 shall indicate that another TP-PI octet follows immediately
7654 // afterwards.` ~ 3GPP TS 23.040 9.2.3.27
7655 pi = this.readHexOctet();
7656 } while (pi & PDU_PI_EXTENSION);
7658 // `If the TP-UDL bit is set to "1" but the TP-DCS bit is set to "0" then
7659 // the receiving entity shall for TP-DCS assume a value of 0x00, i.e. the
7660 // 7bit default alphabet.` ~ 3GPP 23.040 9.2.3.27
7661 msg.dcs = 0;
7662 msg.encoding = PDU_DCS_MSG_CODING_7BITS_ALPHABET;
7664 // TP-Protocol-Identifier
7665 if (pi & PDU_PI_PROTOCOL_IDENTIFIER) {
7666 this.readProtocolIndicator(msg);
7667 }
7668 // TP-Data-Coding-Scheme
7669 if (pi & PDU_PI_DATA_CODING_SCHEME) {
7670 this.readDataCodingScheme(msg);
7671 }
7672 // TP-User-Data-Length
7673 if (pi & PDU_PI_USER_DATA_LENGTH) {
7674 let userDataLength = this.readHexOctet();
7675 this.readUserData(msg, userDataLength);
7676 }
7677 },
7679 /**
7680 * Read and decode a PDU-encoded message from the stream.
7681 *
7682 * TODO: add some basic sanity checks like:
7683 * - do we have the minimum number of chars available
7684 */
7685 readMessage: function() {
7686 // An empty message object. This gets filled below and then returned.
7687 let msg = {
7688 // D:DELIVER, DR:DELIVER-REPORT, S:SUBMIT, SR:SUBMIT-REPORT,
7689 // ST:STATUS-REPORT, C:COMMAND
7690 // M:Mandatory, O:Optional, X:Unavailable
7691 // D DR S SR ST C
7692 SMSC: null, // M M M M M M
7693 mti: null, // M M M M M M
7694 udhi: null, // M M O M M M
7695 sender: null, // M X X X X X
7696 recipient: null, // X X M X M M
7697 pid: null, // M O M O O M
7698 epid: null, // M O M O O M
7699 dcs: null, // M O M O O X
7700 mwi: null, // O O O O O O
7701 replace: false, // O O O O O O
7702 header: null, // M M O M M M
7703 body: null, // M O M O O O
7704 data: null, // M O M O O O
7705 sentTimestamp: null, // M X X X X X
7706 status: null, // X X X X M X
7707 scts: null, // X X X M M X
7708 dt: null, // X X X X M X
7709 };
7711 // SMSC info
7712 let smscLength = this.readHexOctet();
7713 if (smscLength > 0) {
7714 let smscTypeOfAddress = this.readHexOctet();
7715 // Subtract the type-of-address octet we just read from the length.
7716 msg.SMSC = this.readSwappedNibbleBcdString(smscLength - 1);
7717 if ((smscTypeOfAddress >> 4) == (PDU_TOA_INTERNATIONAL >> 4)) {
7718 msg.SMSC = '+' + msg.SMSC;
7719 }
7720 }
7722 // First octet of this SMS-DELIVER or SMS-SUBMIT message
7723 let firstOctet = this.readHexOctet();
7724 // Message Type Indicator
7725 msg.mti = firstOctet & 0x03;
7726 // User data header indicator
7727 msg.udhi = firstOctet & PDU_UDHI;
7729 switch (msg.mti) {
7730 case PDU_MTI_SMS_RESERVED:
7731 // `If an MS receives a TPDU with a "Reserved" value in the TP-MTI it
7732 // shall process the message as if it were an "SMS-DELIVER" but store
7733 // the message exactly as received.` ~ 3GPP TS 23.040 9.2.3.1
7734 case PDU_MTI_SMS_DELIVER:
7735 return this.readDeliverMessage(msg);
7736 case PDU_MTI_SMS_STATUS_REPORT:
7737 return this.readStatusReportMessage(msg);
7738 default:
7739 return null;
7740 }
7741 },
7743 /**
7744 * Helper for processing received SMS parcel data.
7745 *
7746 * @param length
7747 * Length of SMS string in the incoming parcel.
7748 *
7749 * @return Message parsed or null for invalid message.
7750 */
7751 processReceivedSms: function(length) {
7752 if (!length) {
7753 if (DEBUG) this.context.debug("Received empty SMS!");
7754 return [null, PDU_FCS_UNSPECIFIED];
7755 }
7757 let Buf = this.context.Buf;
7759 // An SMS is a string, but we won't read it as such, so let's read the
7760 // string length and then defer to PDU parsing helper.
7761 let messageStringLength = Buf.readInt32();
7762 if (DEBUG) this.context.debug("Got new SMS, length " + messageStringLength);
7763 let message = this.readMessage();
7764 if (DEBUG) this.context.debug("Got new SMS: " + JSON.stringify(message));
7766 // Read string delimiters. See Buf.readString().
7767 Buf.readStringDelimiter(length);
7769 // Determine result
7770 if (!message) {
7771 return [null, PDU_FCS_UNSPECIFIED];
7772 }
7774 if (message.epid == PDU_PID_SHORT_MESSAGE_TYPE_0) {
7775 // `A short message type 0 indicates that the ME must acknowledge receipt
7776 // of the short message but shall discard its contents.` ~ 3GPP TS 23.040
7777 // 9.2.3.9
7778 return [null, PDU_FCS_OK];
7779 }
7781 if (message.messageClass == GECKO_SMS_MESSAGE_CLASSES[PDU_DCS_MSG_CLASS_2]) {
7782 let RIL = this.context.RIL;
7783 switch (message.epid) {
7784 case PDU_PID_ANSI_136_R_DATA:
7785 case PDU_PID_USIM_DATA_DOWNLOAD:
7786 let ICCUtilsHelper = this.context.ICCUtilsHelper;
7787 if (ICCUtilsHelper.isICCServiceAvailable("DATA_DOWNLOAD_SMS_PP")) {
7788 // `If the service "data download via SMS Point-to-Point" is
7789 // allocated and activated in the (U)SIM Service Table, ... then the
7790 // ME shall pass the message transparently to the UICC using the
7791 // ENVELOPE (SMS-PP DOWNLOAD).` ~ 3GPP TS 31.111 7.1.1.1
7792 RIL.dataDownloadViaSMSPP(message);
7794 // `the ME shall not display the message, or alert the user of a
7795 // short message waiting.` ~ 3GPP TS 31.111 7.1.1.1
7796 return [null, PDU_FCS_RESERVED];
7797 }
7799 // If the service "data download via SMS-PP" is not available in the
7800 // (U)SIM Service Table, ..., then the ME shall store the message in
7801 // EFsms in accordance with TS 31.102` ~ 3GPP TS 31.111 7.1.1.1
7803 // Fall through.
7804 default:
7805 RIL.writeSmsToSIM(message);
7806 break;
7807 }
7808 }
7810 // TODO: Bug 739143: B2G SMS: Support SMS Storage Full event
7811 if ((message.messageClass != GECKO_SMS_MESSAGE_CLASSES[PDU_DCS_MSG_CLASS_0]) && !true) {
7812 // `When a mobile terminated message is class 0..., the MS shall display
7813 // the message immediately and send a ACK to the SC ..., irrespective of
7814 // whether there is memory available in the (U)SIM or ME.` ~ 3GPP 23.038
7815 // clause 4.
7817 if (message.messageClass == GECKO_SMS_MESSAGE_CLASSES[PDU_DCS_MSG_CLASS_2]) {
7818 // `If all the short message storage at the MS is already in use, the
7819 // MS shall return "memory capacity exceeded".` ~ 3GPP 23.038 clause 4.
7820 return [null, PDU_FCS_MEMORY_CAPACITY_EXCEEDED];
7821 }
7823 return [null, PDU_FCS_UNSPECIFIED];
7824 }
7826 return [message, PDU_FCS_OK];
7827 },
7829 /**
7830 * Read and decode a SMS-DELIVER PDU.
7831 *
7832 * @param msg
7833 * message object for output.
7834 */
7835 readDeliverMessage: function(msg) {
7836 // - Sender Address info -
7837 let senderAddressLength = this.readHexOctet();
7838 msg.sender = this.readAddress(senderAddressLength);
7839 // - TP-Protocolo-Identifier -
7840 this.readProtocolIndicator(msg);
7841 // - TP-Data-Coding-Scheme -
7842 this.readDataCodingScheme(msg);
7843 // - TP-Service-Center-Time-Stamp -
7844 msg.sentTimestamp = this.readTimestamp();
7845 // - TP-User-Data-Length -
7846 let userDataLength = this.readHexOctet();
7848 // - TP-User-Data -
7849 if (userDataLength > 0) {
7850 this.readUserData(msg, userDataLength);
7851 }
7853 return msg;
7854 },
7856 /**
7857 * Read and decode a SMS-STATUS-REPORT PDU.
7858 *
7859 * @param msg
7860 * message object for output.
7861 */
7862 readStatusReportMessage: function(msg) {
7863 // TP-Message-Reference
7864 msg.messageRef = this.readHexOctet();
7865 // TP-Recipient-Address
7866 let recipientAddressLength = this.readHexOctet();
7867 msg.recipient = this.readAddress(recipientAddressLength);
7868 // TP-Service-Centre-Time-Stamp
7869 msg.scts = this.readTimestamp();
7870 // TP-Discharge-Time
7871 msg.dt = this.readTimestamp();
7872 // TP-Status
7873 msg.status = this.readHexOctet();
7875 this.readExtraParams(msg);
7877 return msg;
7878 },
7880 /**
7881 * Serialize a SMS-SUBMIT PDU message and write it to the output stream.
7882 *
7883 * This method expects that a data coding scheme has been chosen already
7884 * and that the length of the user data payload in that encoding is known,
7885 * too. Both go hand in hand together anyway.
7886 *
7887 * @param address
7888 * String containing the address (number) of the SMS receiver
7889 * @param userData
7890 * String containing the message to be sent as user data
7891 * @param dcs
7892 * Data coding scheme. One of the PDU_DCS_MSG_CODING_*BITS_ALPHABET
7893 * constants.
7894 * @param userDataHeaderLength
7895 * Length of embedded user data header, in bytes. The whole header
7896 * size will be userDataHeaderLength + 1; 0 for no header.
7897 * @param encodedBodyLength
7898 * Length of the user data when encoded with the given DCS. For UCS2,
7899 * in bytes; for 7-bit, in septets.
7900 * @param langIndex
7901 * Table index used for normal 7-bit encoded character lookup.
7902 * @param langShiftIndex
7903 * Table index used for escaped 7-bit encoded character lookup.
7904 * @param requestStatusReport
7905 * Request status report.
7906 */
7907 writeMessage: function(options) {
7908 if (DEBUG) {
7909 this.context.debug("writeMessage: " + JSON.stringify(options));
7910 }
7911 let Buf = this.context.Buf;
7912 let address = options.number;
7913 let body = options.body;
7914 let dcs = options.dcs;
7915 let userDataHeaderLength = options.userDataHeaderLength;
7916 let encodedBodyLength = options.encodedBodyLength;
7917 let langIndex = options.langIndex;
7918 let langShiftIndex = options.langShiftIndex;
7920 // SMS-SUBMIT Format:
7921 //
7922 // PDU Type - 1 octet
7923 // Message Reference - 1 octet
7924 // DA - Destination Address - 2 to 12 octets
7925 // PID - Protocol Identifier - 1 octet
7926 // DCS - Data Coding Scheme - 1 octet
7927 // VP - Validity Period - 0, 1 or 7 octets
7928 // UDL - User Data Length - 1 octet
7929 // UD - User Data - 140 octets
7931 let addressFormat = PDU_TOA_ISDN; // 81
7932 if (address[0] == '+') {
7933 addressFormat = PDU_TOA_INTERNATIONAL | PDU_TOA_ISDN; // 91
7934 address = address.substring(1);
7935 }
7936 //TODO validity is unsupported for now
7937 let validity = 0;
7939 let headerOctets = (userDataHeaderLength ? userDataHeaderLength + 1 : 0);
7940 let paddingBits;
7941 let userDataLengthInSeptets;
7942 let userDataLengthInOctets;
7943 if (dcs == PDU_DCS_MSG_CODING_7BITS_ALPHABET) {
7944 let headerSeptets = Math.ceil(headerOctets * 8 / 7);
7945 userDataLengthInSeptets = headerSeptets + encodedBodyLength;
7946 userDataLengthInOctets = Math.ceil(userDataLengthInSeptets * 7 / 8);
7947 paddingBits = headerSeptets * 7 - headerOctets * 8;
7948 } else {
7949 userDataLengthInOctets = headerOctets + encodedBodyLength;
7950 paddingBits = 0;
7951 }
7953 let pduOctetLength = 4 + // PDU Type, Message Ref, address length + format
7954 Math.ceil(address.length / 2) +
7955 3 + // PID, DCS, UDL
7956 userDataLengthInOctets;
7957 if (validity) {
7958 //TODO: add more to pduOctetLength
7959 }
7961 // Start the string. Since octets are represented in hex, we will need
7962 // twice as many characters as octets.
7963 Buf.writeInt32(pduOctetLength * 2);
7965 // - PDU-TYPE-
7967 // +--------+----------+---------+---------+--------+---------+
7968 // | RP (1) | UDHI (1) | SRR (1) | VPF (2) | RD (1) | MTI (2) |
7969 // +--------+----------+---------+---------+--------+---------+
7970 // RP: 0 Reply path parameter is not set
7971 // 1 Reply path parameter is set
7972 // UDHI: 0 The UD Field contains only the short message
7973 // 1 The beginning of the UD field contains a header in addition
7974 // of the short message
7975 // SRR: 0 A status report is not requested
7976 // 1 A status report is requested
7977 // VPF: bit4 bit3
7978 // 0 0 VP field is not present
7979 // 0 1 Reserved
7980 // 1 0 VP field present an integer represented (relative)
7981 // 1 1 VP field present a semi-octet represented (absolute)
7982 // RD: Instruct the SMSC to accept(0) or reject(1) an SMS-SUBMIT
7983 // for a short message still held in the SMSC which has the same
7984 // MR and DA as a previously submitted short message from the
7985 // same OA
7986 // MTI: bit1 bit0 Message Type
7987 // 0 0 SMS-DELIVER (SMSC ==> MS)
7988 // 0 1 SMS-SUBMIT (MS ==> SMSC)
7990 // PDU type. MTI is set to SMS-SUBMIT
7991 let firstOctet = PDU_MTI_SMS_SUBMIT;
7993 // Status-Report-Request
7994 if (options.requestStatusReport) {
7995 firstOctet |= PDU_SRI_SRR;
7996 }
7998 // Validity period
7999 if (validity) {
8000 //TODO: not supported yet, OR with one of PDU_VPF_*
8001 }
8002 // User data header indicator
8003 if (headerOctets) {
8004 firstOctet |= PDU_UDHI;
8005 }
8006 this.writeHexOctet(firstOctet);
8008 // Message reference 00
8009 this.writeHexOctet(0x00);
8011 // - Destination Address -
8012 this.writeHexOctet(address.length);
8013 this.writeHexOctet(addressFormat);
8014 this.writeSwappedNibbleBCD(address);
8016 // - Protocol Identifier -
8017 this.writeHexOctet(0x00);
8019 // - Data coding scheme -
8020 // For now it assumes bits 7..4 = 1111 except for the 16 bits use case
8021 this.writeHexOctet(dcs);
8023 // - Validity Period -
8024 if (validity) {
8025 this.writeHexOctet(validity);
8026 }
8028 // - User Data -
8029 if (dcs == PDU_DCS_MSG_CODING_7BITS_ALPHABET) {
8030 this.writeHexOctet(userDataLengthInSeptets);
8031 } else {
8032 this.writeHexOctet(userDataLengthInOctets);
8033 }
8035 if (headerOctets) {
8036 this.writeUserDataHeader(options);
8037 }
8039 switch (dcs) {
8040 case PDU_DCS_MSG_CODING_7BITS_ALPHABET:
8041 this.writeStringAsSeptets(body, paddingBits, langIndex, langShiftIndex);
8042 break;
8043 case PDU_DCS_MSG_CODING_8BITS_ALPHABET:
8044 // Unsupported.
8045 break;
8046 case PDU_DCS_MSG_CODING_16BITS_ALPHABET:
8047 this.writeUCS2String(body);
8048 break;
8049 }
8051 // End of the string. The string length is always even by definition, so
8052 // we write two \0 delimiters.
8053 Buf.writeUint16(0);
8054 Buf.writeUint16(0);
8055 },
8057 /**
8058 * Read GSM CBS message serial number.
8059 *
8060 * @param msg
8061 * message object for output.
8062 *
8063 * @see 3GPP TS 23.041 section 9.4.1.2.1
8064 */
8065 readCbSerialNumber: function(msg) {
8066 let Buf = this.context.Buf;
8067 msg.serial = Buf.readUint8() << 8 | Buf.readUint8();
8068 msg.geographicalScope = (msg.serial >>> 14) & 0x03;
8069 msg.messageCode = (msg.serial >>> 4) & 0x03FF;
8070 msg.updateNumber = msg.serial & 0x0F;
8071 },
8073 /**
8074 * Read GSM CBS message message identifier.
8075 *
8076 * @param msg
8077 * message object for output.
8078 *
8079 * @see 3GPP TS 23.041 section 9.4.1.2.2
8080 */
8081 readCbMessageIdentifier: function(msg) {
8082 let Buf = this.context.Buf;
8083 msg.messageId = Buf.readUint8() << 8 | Buf.readUint8();
8085 if ((msg.format != CB_FORMAT_ETWS)
8086 && (msg.messageId >= CB_GSM_MESSAGEID_ETWS_BEGIN)
8087 && (msg.messageId <= CB_GSM_MESSAGEID_ETWS_END)) {
8088 // `In the case of transmitting CBS message for ETWS, a part of
8089 // Message Code can be used to command mobile terminals to activate
8090 // emergency user alert and message popup in order to alert the users.`
8091 msg.etws = {
8092 emergencyUserAlert: msg.messageCode & 0x0200 ? true : false,
8093 popup: msg.messageCode & 0x0100 ? true : false
8094 };
8096 let warningType = msg.messageId - CB_GSM_MESSAGEID_ETWS_BEGIN;
8097 if (warningType < CB_ETWS_WARNING_TYPE_NAMES.length) {
8098 msg.etws.warningType = warningType;
8099 }
8100 }
8101 },
8103 /**
8104 * Read CBS Data Coding Scheme.
8105 *
8106 * @param msg
8107 * message object for output.
8108 *
8109 * @see 3GPP TS 23.038 section 5.
8110 */
8111 readCbDataCodingScheme: function(msg) {
8112 let dcs = this.context.Buf.readUint8();
8113 if (DEBUG) this.context.debug("PDU: read CBS dcs: " + dcs);
8115 let language = null, hasLanguageIndicator = false;
8116 // `Any reserved codings shall be assumed to be the GSM 7bit default
8117 // alphabet.`
8118 let encoding = PDU_DCS_MSG_CODING_7BITS_ALPHABET;
8119 let messageClass = PDU_DCS_MSG_CLASS_NORMAL;
8121 switch (dcs & PDU_DCS_CODING_GROUP_BITS) {
8122 case 0x00: // 0000
8123 language = CB_DCS_LANG_GROUP_1[dcs & 0x0F];
8124 break;
8126 case 0x10: // 0001
8127 switch (dcs & 0x0F) {
8128 case 0x00:
8129 hasLanguageIndicator = true;
8130 break;
8131 case 0x01:
8132 encoding = PDU_DCS_MSG_CODING_16BITS_ALPHABET;
8133 hasLanguageIndicator = true;
8134 break;
8135 }
8136 break;
8138 case 0x20: // 0010
8139 language = CB_DCS_LANG_GROUP_2[dcs & 0x0F];
8140 break;
8142 case 0x40: // 01xx
8143 case 0x50:
8144 //case 0x60: Text Compression, not supported
8145 //case 0x70: Text Compression, not supported
8146 case 0x90: // 1001
8147 encoding = (dcs & 0x0C);
8148 if (encoding == 0x0C) {
8149 encoding = PDU_DCS_MSG_CODING_7BITS_ALPHABET;
8150 }
8151 messageClass = (dcs & PDU_DCS_MSG_CLASS_BITS);
8152 break;
8154 case 0xF0:
8155 encoding = (dcs & 0x04) ? PDU_DCS_MSG_CODING_8BITS_ALPHABET
8156 : PDU_DCS_MSG_CODING_7BITS_ALPHABET;
8157 switch(dcs & PDU_DCS_MSG_CLASS_BITS) {
8158 case 0x01: messageClass = PDU_DCS_MSG_CLASS_USER_1; break;
8159 case 0x02: messageClass = PDU_DCS_MSG_CLASS_USER_2; break;
8160 case 0x03: messageClass = PDU_DCS_MSG_CLASS_3; break;
8161 }
8162 break;
8164 case 0x30: // 0011 (Reserved)
8165 case 0x80: // 1000 (Reserved)
8166 case 0xA0: // 1010..1100 (Reserved)
8167 case 0xB0:
8168 case 0xC0:
8169 break;
8171 default:
8172 throw new Error("Unsupported CBS data coding scheme: " + dcs);
8173 }
8175 msg.dcs = dcs;
8176 msg.encoding = encoding;
8177 msg.language = language;
8178 msg.messageClass = GECKO_SMS_MESSAGE_CLASSES[messageClass];
8179 msg.hasLanguageIndicator = hasLanguageIndicator;
8180 },
8182 /**
8183 * Read GSM CBS message page parameter.
8184 *
8185 * @param msg
8186 * message object for output.
8187 *
8188 * @see 3GPP TS 23.041 section 9.4.1.2.4
8189 */
8190 readCbPageParameter: function(msg) {
8191 let octet = this.context.Buf.readUint8();
8192 msg.pageIndex = (octet >>> 4) & 0x0F;
8193 msg.numPages = octet & 0x0F;
8194 if (!msg.pageIndex || !msg.numPages) {
8195 // `If a mobile receives the code 0000 in either the first field or the
8196 // second field then it shall treat the CBS message exactly the same as a
8197 // CBS message with page parameter 0001 0001 (i.e. a single page message).`
8198 msg.pageIndex = msg.numPages = 1;
8199 }
8200 },
8202 /**
8203 * Read ETWS Primary Notification message warning type.
8204 *
8205 * @param msg
8206 * message object for output.
8207 *
8208 * @see 3GPP TS 23.041 section 9.3.24
8209 */
8210 readCbWarningType: function(msg) {
8211 let Buf = this.context.Buf;
8212 let word = Buf.readUint8() << 8 | Buf.readUint8();
8213 msg.etws = {
8214 warningType: (word >>> 9) & 0x7F,
8215 popup: word & 0x80 ? true : false,
8216 emergencyUserAlert: word & 0x100 ? true : false
8217 };
8218 },
8220 /**
8221 * Read CBS-Message-Information-Page
8222 *
8223 * @param msg
8224 * message object for output.
8225 * @param length
8226 * length of cell broadcast data to read in octets.
8227 *
8228 * @see 3GPP TS 23.041 section 9.3.19
8229 */
8230 readGsmCbData: function(msg, length) {
8231 let Buf = this.context.Buf;
8232 let bufAdapter = {
8233 context: this.context,
8234 readHexOctet: function() {
8235 return Buf.readUint8();
8236 }
8237 };
8239 msg.body = null;
8240 msg.data = null;
8241 switch (msg.encoding) {
8242 case PDU_DCS_MSG_CODING_7BITS_ALPHABET:
8243 msg.body = this.readSeptetsToString.call(bufAdapter,
8244 (length * 8 / 7), 0,
8245 PDU_NL_IDENTIFIER_DEFAULT,
8246 PDU_NL_IDENTIFIER_DEFAULT);
8247 if (msg.hasLanguageIndicator) {
8248 msg.language = msg.body.substring(0, 2);
8249 msg.body = msg.body.substring(3);
8250 }
8251 break;
8253 case PDU_DCS_MSG_CODING_8BITS_ALPHABET:
8254 msg.data = Buf.readUint8Array(length);
8255 break;
8257 case PDU_DCS_MSG_CODING_16BITS_ALPHABET:
8258 if (msg.hasLanguageIndicator) {
8259 msg.language = this.readSeptetsToString.call(bufAdapter, 2, 0,
8260 PDU_NL_IDENTIFIER_DEFAULT,
8261 PDU_NL_IDENTIFIER_DEFAULT);
8262 length -= 2;
8263 }
8264 msg.body = this.readUCS2String.call(bufAdapter, length);
8265 break;
8266 }
8267 },
8269 /**
8270 * Read Cell GSM/ETWS/UMTS Broadcast Message.
8271 *
8272 * @param pduLength
8273 * total length of the incoming PDU in octets.
8274 */
8275 readCbMessage: function(pduLength) {
8276 // Validity GSM ETWS UMTS
8277 let msg = {
8278 // Internally used in ril_worker:
8279 serial: null, // O O O
8280 updateNumber: null, // O O O
8281 format: null, // O O O
8282 dcs: 0x0F, // O X O
8283 encoding: PDU_DCS_MSG_CODING_7BITS_ALPHABET, // O X O
8284 hasLanguageIndicator: false, // O X O
8285 data: null, // O X O
8286 body: null, // O X O
8287 pageIndex: 1, // O X X
8288 numPages: 1, // O X X
8290 // DOM attributes:
8291 geographicalScope: null, // O O O
8292 messageCode: null, // O O O
8293 messageId: null, // O O O
8294 language: null, // O X O
8295 fullBody: null, // O X O
8296 fullData: null, // O X O
8297 messageClass: GECKO_SMS_MESSAGE_CLASSES[PDU_DCS_MSG_CLASS_NORMAL], // O x O
8298 etws: null // ? O ?
8299 /*{
8300 warningType: null, // X O X
8301 popup: false, // X O X
8302 emergencyUserAlert: false, // X O X
8303 }*/
8304 };
8306 if (pduLength <= CB_MESSAGE_SIZE_ETWS) {
8307 msg.format = CB_FORMAT_ETWS;
8308 return this.readEtwsCbMessage(msg);
8309 }
8311 if (pduLength <= CB_MESSAGE_SIZE_GSM) {
8312 msg.format = CB_FORMAT_GSM;
8313 return this.readGsmCbMessage(msg, pduLength);
8314 }
8316 return null;
8317 },
8319 /**
8320 * Read GSM Cell Broadcast Message.
8321 *
8322 * @param msg
8323 * message object for output.
8324 * @param pduLength
8325 * total length of the incomint PDU in octets.
8326 *
8327 * @see 3GPP TS 23.041 clause 9.4.1.2
8328 */
8329 readGsmCbMessage: function(msg, pduLength) {
8330 this.readCbSerialNumber(msg);
8331 this.readCbMessageIdentifier(msg);
8332 this.readCbDataCodingScheme(msg);
8333 this.readCbPageParameter(msg);
8335 // GSM CB message header takes 6 octets.
8336 this.readGsmCbData(msg, pduLength - 6);
8338 return msg;
8339 },
8341 /**
8342 * Read ETWS Primary Notification Message.
8343 *
8344 * @param msg
8345 * message object for output.
8346 *
8347 * @see 3GPP TS 23.041 clause 9.4.1.3
8348 */
8349 readEtwsCbMessage: function(msg) {
8350 this.readCbSerialNumber(msg);
8351 this.readCbMessageIdentifier(msg);
8352 this.readCbWarningType(msg);
8354 // Octet 7..56 is Warning Security Information. However, according to
8355 // section 9.4.1.3.6, `The UE shall ignore this parameter.` So we just skip
8356 // processing it here.
8358 return msg;
8359 },
8361 /**
8362 * Read network name.
8363 *
8364 * @param len Length of the information element.
8365 * @return
8366 * {
8367 * networkName: network name.
8368 * shouldIncludeCi: Should Country's initials included in text string.
8369 * }
8370 * @see TS 24.008 clause 10.5.3.5a.
8371 */
8372 readNetworkName: function(len) {
8373 // According to TS 24.008 Sec. 10.5.3.5a, the first octet is:
8374 // bit 8: must be 1.
8375 // bit 5-7: Text encoding.
8376 // 000 - GSM default alphabet.
8377 // 001 - UCS2 (16 bit).
8378 // else - reserved.
8379 // bit 4: MS should add the letters for Country's Initials and a space
8380 // to the text string if this bit is true.
8381 // bit 1-3: number of spare bits in last octet.
8383 let codingInfo = this.readHexOctet();
8384 if (!(codingInfo & 0x80)) {
8385 return null;
8386 }
8388 let textEncoding = (codingInfo & 0x70) >> 4;
8389 let shouldIncludeCountryInitials = !!(codingInfo & 0x08);
8390 let spareBits = codingInfo & 0x07;
8391 let resultString;
8393 switch (textEncoding) {
8394 case 0:
8395 // GSM Default alphabet.
8396 resultString = this.readSeptetsToString(
8397 ((len - 1) * 8 - spareBits) / 7, 0,
8398 PDU_NL_IDENTIFIER_DEFAULT,
8399 PDU_NL_IDENTIFIER_DEFAULT);
8400 break;
8401 case 1:
8402 // UCS2 encoded.
8403 resultString = this.readUCS2String(len - 1);
8404 break;
8405 default:
8406 // Not an available text coding.
8407 return null;
8408 }
8410 // TODO - Bug 820286: According to shouldIncludeCountryInitials, add
8411 // country initials to the resulting string.
8412 return resultString;
8413 }
8414 };
8416 /**
8417 * Provide buffer with bitwise read/write function so make encoding/decoding easier.
8418 */
8419 function BitBufferHelperObject(/* unused */aContext) {
8420 this.readBuffer = [];
8421 this.writeBuffer = [];
8422 }
8423 BitBufferHelperObject.prototype = {
8424 readCache: 0,
8425 readCacheSize: 0,
8426 readBuffer: null,
8427 readIndex: 0,
8428 writeCache: 0,
8429 writeCacheSize: 0,
8430 writeBuffer: null,
8432 // Max length is 32 because we use integer as read/write cache.
8433 // All read/write functions are implemented based on bitwise operation.
8434 readBits: function(length) {
8435 if (length <= 0 || length > 32) {
8436 return null;
8437 }
8439 if (length > this.readCacheSize) {
8440 let bytesToRead = Math.ceil((length - this.readCacheSize) / 8);
8441 for(let i = 0; i < bytesToRead; i++) {
8442 this.readCache = (this.readCache << 8) | (this.readBuffer[this.readIndex++] & 0xFF);
8443 this.readCacheSize += 8;
8444 }
8445 }
8447 let bitOffset = (this.readCacheSize - length),
8448 resultMask = (1 << length) - 1,
8449 result = 0;
8451 result = (this.readCache >> bitOffset) & resultMask;
8452 this.readCacheSize -= length;
8454 return result;
8455 },
8457 backwardReadPilot: function(length) {
8458 if (length <= 0) {
8459 return;
8460 }
8462 // Zero-based position.
8463 let bitIndexToRead = this.readIndex * 8 - this.readCacheSize - length;
8465 if (bitIndexToRead < 0) {
8466 return;
8467 }
8469 // Update readIndex, readCache, readCacheSize accordingly.
8470 let readBits = bitIndexToRead % 8;
8471 this.readIndex = Math.floor(bitIndexToRead / 8) + ((readBits) ? 1 : 0);
8472 this.readCache = (readBits) ? this.readBuffer[this.readIndex - 1] : 0;
8473 this.readCacheSize = (readBits) ? (8 - readBits) : 0;
8474 },
8476 writeBits: function(value, length) {
8477 if (length <= 0 || length > 32) {
8478 return;
8479 }
8481 let totalLength = length + this.writeCacheSize;
8483 // 8-byte cache not full
8484 if (totalLength < 8) {
8485 let valueMask = (1 << length) - 1;
8486 this.writeCache = (this.writeCache << length) | (value & valueMask);
8487 this.writeCacheSize += length;
8488 return;
8489 }
8491 // Deal with unaligned part
8492 if (this.writeCacheSize) {
8493 let mergeLength = 8 - this.writeCacheSize,
8494 valueMask = (1 << mergeLength) - 1;
8496 this.writeCache = (this.writeCache << mergeLength) | ((value >> (length - mergeLength)) & valueMask);
8497 this.writeBuffer.push(this.writeCache & 0xFF);
8498 length -= mergeLength;
8499 }
8501 // Aligned part, just copy
8502 while (length >= 8) {
8503 length -= 8;
8504 this.writeBuffer.push((value >> length) & 0xFF);
8505 }
8507 // Rest part is saved into cache
8508 this.writeCacheSize = length;
8509 this.writeCache = value & ((1 << length) - 1);
8511 return;
8512 },
8514 // Drop what still in read cache and goto next 8-byte alignment.
8515 // There might be a better naming.
8516 nextOctetAlign: function() {
8517 this.readCache = 0;
8518 this.readCacheSize = 0;
8519 },
8521 // Flush current write cache to Buf with padding 0s.
8522 // There might be a better naming.
8523 flushWithPadding: function() {
8524 if (this.writeCacheSize) {
8525 this.writeBuffer.push(this.writeCache << (8 - this.writeCacheSize));
8526 }
8527 this.writeCache = 0;
8528 this.writeCacheSize = 0;
8529 },
8531 startWrite: function(dataBuffer) {
8532 this.writeBuffer = dataBuffer;
8533 this.writeCache = 0;
8534 this.writeCacheSize = 0;
8535 },
8537 startRead: function(dataBuffer) {
8538 this.readBuffer = dataBuffer;
8539 this.readCache = 0;
8540 this.readCacheSize = 0;
8541 this.readIndex = 0;
8542 },
8544 getWriteBufferSize: function() {
8545 return this.writeBuffer.length;
8546 },
8548 overwriteWriteBuffer: function(position, data) {
8549 let writeLength = data.length;
8550 if (writeLength + position >= this.writeBuffer.length) {
8551 writeLength = this.writeBuffer.length - position;
8552 }
8553 for (let i = 0; i < writeLength; i++) {
8554 this.writeBuffer[i + position] = data[i];
8555 }
8556 }
8557 };
8559 /**
8560 * Helper for CDMA PDU
8561 *
8562 * Currently, some function are shared with GsmPDUHelper, they should be
8563 * moved from GsmPDUHelper to a common object shared among GsmPDUHelper and
8564 * CdmaPDUHelper.
8565 */
8566 function CdmaPDUHelperObject(aContext) {
8567 this.context = aContext;
8568 }
8569 CdmaPDUHelperObject.prototype = {
8570 context: null,
8572 // 1..........C
8573 // Only "1234567890*#" is defined in C.S0005-D v2.0
8574 dtmfChars: ".1234567890*#...",
8576 /**
8577 * Entry point for SMS encoding, the options object is made compatible
8578 * with existing writeMessage() of GsmPDUHelper, but less key is used.
8579 *
8580 * Current used key in options:
8581 * @param number
8582 * String containing the address (number) of the SMS receiver
8583 * @param body
8584 * String containing the message to be sent, segmented part
8585 * @param dcs
8586 * Data coding scheme. One of the PDU_DCS_MSG_CODING_*BITS_ALPHABET
8587 * constants.
8588 * @param encodedBodyLength
8589 * Length of the user data when encoded with the given DCS. For UCS2,
8590 * in bytes; for 7-bit, in septets.
8591 * @param requestStatusReport
8592 * Request status report.
8593 * @param segmentRef
8594 * Reference number of concatenated SMS message
8595 * @param segmentMaxSeq
8596 * Total number of concatenated SMS message
8597 * @param segmentSeq
8598 * Sequence number of concatenated SMS message
8599 */
8600 writeMessage: function(options) {
8601 if (DEBUG) {
8602 this.context.debug("cdma_writeMessage: " + JSON.stringify(options));
8603 }
8605 // Get encoding
8606 options.encoding = this.gsmDcsToCdmaEncoding(options.dcs);
8608 // Common Header
8609 if (options.segmentMaxSeq > 1) {
8610 this.writeInt(PDU_CDMA_MSG_TELESERIVCIE_ID_WEMT);
8611 } else {
8612 this.writeInt(PDU_CDMA_MSG_TELESERIVCIE_ID_SMS);
8613 }
8615 this.writeInt(0);
8616 this.writeInt(PDU_CDMA_MSG_CATEGORY_UNSPEC);
8618 // Just fill out address info in byte, rild will encap them for us
8619 let addrInfo = this.encodeAddr(options.number);
8620 this.writeByte(addrInfo.digitMode);
8621 this.writeByte(addrInfo.numberMode);
8622 this.writeByte(addrInfo.numberType);
8623 this.writeByte(addrInfo.numberPlan);
8624 this.writeByte(addrInfo.address.length);
8625 for (let i = 0; i < addrInfo.address.length; i++) {
8626 this.writeByte(addrInfo.address[i]);
8627 }
8629 // Subaddress, not supported
8630 this.writeByte(0); // Subaddress : Type
8631 this.writeByte(0); // Subaddress : Odd
8632 this.writeByte(0); // Subaddress : length
8634 // User Data
8635 let encodeResult = this.encodeUserData(options);
8636 this.writeByte(encodeResult.length);
8637 for (let i = 0; i < encodeResult.length; i++) {
8638 this.writeByte(encodeResult[i]);
8639 }
8641 encodeResult = null;
8642 },
8644 /**
8645 * Data writters
8646 */
8647 writeInt: function(value) {
8648 this.context.Buf.writeInt32(value);
8649 },
8651 writeByte: function(value) {
8652 this.context.Buf.writeInt32(value & 0xFF);
8653 },
8655 /**
8656 * Transform GSM DCS to CDMA encoding.
8657 */
8658 gsmDcsToCdmaEncoding: function(encoding) {
8659 switch (encoding) {
8660 case PDU_DCS_MSG_CODING_7BITS_ALPHABET:
8661 return PDU_CDMA_MSG_CODING_7BITS_ASCII;
8662 case PDU_DCS_MSG_CODING_8BITS_ALPHABET:
8663 return PDU_CDMA_MSG_CODING_OCTET;
8664 case PDU_DCS_MSG_CODING_16BITS_ALPHABET:
8665 return PDU_CDMA_MSG_CODING_UNICODE;
8666 }
8667 throw new Error("gsmDcsToCdmaEncoding(): Invalid GSM SMS DCS value: " + encoding);
8668 },
8670 /**
8671 * Encode address into CDMA address format, as a byte array.
8672 *
8673 * @see 3GGP2 C.S0015-B 2.0, 3.4.3.3 Address Parameters
8674 *
8675 * @param address
8676 * String of address to be encoded
8677 */
8678 encodeAddr: function(address) {
8679 let result = {};
8681 result.numberType = PDU_CDMA_MSG_ADDR_NUMBER_TYPE_UNKNOWN;
8682 result.numberPlan = PDU_CDMA_MSG_ADDR_NUMBER_TYPE_UNKNOWN;
8684 if (address[0] === '+') {
8685 address = address.substring(1);
8686 }
8688 // Try encode with DTMF first
8689 result.digitMode = PDU_CDMA_MSG_ADDR_DIGIT_MODE_DTMF;
8690 result.numberMode = PDU_CDMA_MSG_ADDR_NUMBER_MODE_ANSI;
8692 result.address = [];
8693 for (let i = 0; i < address.length; i++) {
8694 let addrDigit = this.dtmfChars.indexOf(address.charAt(i));
8695 if (addrDigit < 0) {
8696 result.digitMode = PDU_CDMA_MSG_ADDR_DIGIT_MODE_ASCII;
8697 result.numberMode = PDU_CDMA_MSG_ADDR_NUMBER_MODE_ASCII;
8698 result.address = [];
8699 break;
8700 }
8701 result.address.push(addrDigit);
8702 }
8704 // Address can't be encoded with DTMF, then use 7-bit ASCII
8705 if (result.digitMode !== PDU_CDMA_MSG_ADDR_DIGIT_MODE_DTMF) {
8706 if (address.indexOf("@") !== -1) {
8707 result.numberType = PDU_CDMA_MSG_ADDR_NUMBER_TYPE_NATIONAL;
8708 }
8710 for (let i = 0; i < address.length; i++) {
8711 result.address.push(address.charCodeAt(i) & 0x7F);
8712 }
8713 }
8715 return result;
8716 },
8718 /**
8719 * Encode SMS contents in options into CDMA userData field.
8720 * Corresponding and required subparameters will be added automatically.
8721 *
8722 * @see 3GGP2 C.S0015-B 2.0, 3.4.3.7 Bearer Data
8723 * 4.5 Bearer Data Parameters
8724 *
8725 * Current used key in options:
8726 * @param body
8727 * String containing the message to be sent, segmented part
8728 * @param encoding
8729 * Encoding method of CDMA, can be transformed from GSM DCS by function
8730 * cdmaPduHelp.gsmDcsToCdmaEncoding()
8731 * @param encodedBodyLength
8732 * Length of the user data when encoded with the given DCS. For UCS2,
8733 * in bytes; for 7-bit, in septets.
8734 * @param requestStatusReport
8735 * Request status report.
8736 * @param segmentRef
8737 * Reference number of concatenated SMS message
8738 * @param segmentMaxSeq
8739 * Total number of concatenated SMS message
8740 * @param segmentSeq
8741 * Sequence number of concatenated SMS message
8742 */
8743 encodeUserData: function(options) {
8744 let userDataBuffer = [];
8745 this.context.BitBufferHelper.startWrite(userDataBuffer);
8747 // Message Identifier
8748 this.encodeUserDataMsgId(options);
8750 // User Data
8751 this.encodeUserDataMsg(options);
8753 // Reply Option
8754 this.encodeUserDataReplyOption(options);
8756 return userDataBuffer;
8757 },
8759 /**
8760 * User data subparameter encoder : Message Identifier
8761 *
8762 * @see 3GGP2 C.S0015-B 2.0, 4.5.1 Message Identifier
8763 */
8764 encodeUserDataMsgId: function(options) {
8765 let BitBufferHelper = this.context.BitBufferHelper;
8766 BitBufferHelper.writeBits(PDU_CDMA_MSG_USERDATA_MSG_ID, 8);
8767 BitBufferHelper.writeBits(3, 8);
8768 BitBufferHelper.writeBits(PDU_CDMA_MSG_TYPE_SUBMIT, 4);
8769 BitBufferHelper.writeBits(1, 16); // TODO: How to get message ID?
8770 if (options.segmentMaxSeq > 1) {
8771 BitBufferHelper.writeBits(1, 1);
8772 } else {
8773 BitBufferHelper.writeBits(0, 1);
8774 }
8776 BitBufferHelper.flushWithPadding();
8777 },
8779 /**
8780 * User data subparameter encoder : User Data
8781 *
8782 * @see 3GGP2 C.S0015-B 2.0, 4.5.2 User Data
8783 */
8784 encodeUserDataMsg: function(options) {
8785 let BitBufferHelper = this.context.BitBufferHelper;
8786 BitBufferHelper.writeBits(PDU_CDMA_MSG_USERDATA_BODY, 8);
8787 // Reserve space for length
8788 BitBufferHelper.writeBits(0, 8);
8789 let lengthPosition = BitBufferHelper.getWriteBufferSize();
8791 BitBufferHelper.writeBits(options.encoding, 5);
8793 // Add user data header for message segement
8794 let msgBody = options.body,
8795 msgBodySize = (options.encoding === PDU_CDMA_MSG_CODING_7BITS_ASCII ?
8796 options.encodedBodyLength :
8797 msgBody.length);
8798 if (options.segmentMaxSeq > 1) {
8799 if (options.encoding === PDU_CDMA_MSG_CODING_7BITS_ASCII) {
8800 BitBufferHelper.writeBits(msgBodySize + 7, 8); // Required length for user data header, in septet(7-bit)
8802 BitBufferHelper.writeBits(5, 8); // total header length 5 bytes
8803 BitBufferHelper.writeBits(PDU_IEI_CONCATENATED_SHORT_MESSAGES_8BIT, 8); // header id 0
8804 BitBufferHelper.writeBits(3, 8); // length of element for id 0 is 3
8805 BitBufferHelper.writeBits(options.segmentRef & 0xFF, 8); // Segement reference
8806 BitBufferHelper.writeBits(options.segmentMaxSeq & 0xFF, 8); // Max segment
8807 BitBufferHelper.writeBits(options.segmentSeq & 0xFF, 8); // Current segment
8808 BitBufferHelper.writeBits(0, 1); // Padding to make header data septet(7-bit) aligned
8809 } else {
8810 if (options.encoding === PDU_CDMA_MSG_CODING_UNICODE) {
8811 BitBufferHelper.writeBits(msgBodySize + 3, 8); // Required length for user data header, in 16-bit
8812 } else {
8813 BitBufferHelper.writeBits(msgBodySize + 6, 8); // Required length for user data header, in octet(8-bit)
8814 }
8816 BitBufferHelper.writeBits(5, 8); // total header length 5 bytes
8817 BitBufferHelper.writeBits(PDU_IEI_CONCATENATED_SHORT_MESSAGES_8BIT, 8); // header id 0
8818 BitBufferHelper.writeBits(3, 8); // length of element for id 0 is 3
8819 BitBufferHelper.writeBits(options.segmentRef & 0xFF, 8); // Segement reference
8820 BitBufferHelper.writeBits(options.segmentMaxSeq & 0xFF, 8); // Max segment
8821 BitBufferHelper.writeBits(options.segmentSeq & 0xFF, 8); // Current segment
8822 }
8823 } else {
8824 BitBufferHelper.writeBits(msgBodySize, 8);
8825 }
8827 // Encode message based on encoding method
8828 const langTable = PDU_NL_LOCKING_SHIFT_TABLES[PDU_NL_IDENTIFIER_DEFAULT];
8829 const langShiftTable = PDU_NL_SINGLE_SHIFT_TABLES[PDU_NL_IDENTIFIER_DEFAULT];
8830 for (let i = 0; i < msgBody.length; i++) {
8831 switch (options.encoding) {
8832 case PDU_CDMA_MSG_CODING_OCTET: {
8833 let msgDigit = msgBody.charCodeAt(i);
8834 BitBufferHelper.writeBits(msgDigit, 8);
8835 break;
8836 }
8837 case PDU_CDMA_MSG_CODING_7BITS_ASCII: {
8838 let msgDigit = msgBody.charCodeAt(i),
8839 msgDigitChar = msgBody.charAt(i);
8841 if (msgDigit >= 32) {
8842 BitBufferHelper.writeBits(msgDigit, 7);
8843 } else {
8844 msgDigit = langTable.indexOf(msgDigitChar);
8846 if (msgDigit === PDU_NL_EXTENDED_ESCAPE) {
8847 break;
8848 }
8849 if (msgDigit >= 0) {
8850 BitBufferHelper.writeBits(msgDigit, 7);
8851 } else {
8852 msgDigit = langShiftTable.indexOf(msgDigitChar);
8853 if (msgDigit == -1) {
8854 throw new Error("'" + msgDigitChar + "' is not in 7 bit alphabet "
8855 + langIndex + ":" + langShiftIndex + "!");
8856 }
8858 if (msgDigit === PDU_NL_RESERVED_CONTROL) {
8859 break;
8860 }
8862 BitBufferHelper.writeBits(PDU_NL_EXTENDED_ESCAPE, 7);
8863 BitBufferHelper.writeBits(msgDigit, 7);
8864 }
8865 }
8866 break;
8867 }
8868 case PDU_CDMA_MSG_CODING_UNICODE: {
8869 let msgDigit = msgBody.charCodeAt(i);
8870 BitBufferHelper.writeBits(msgDigit, 16);
8871 break;
8872 }
8873 }
8874 }
8875 BitBufferHelper.flushWithPadding();
8877 // Fill length
8878 let currentPosition = BitBufferHelper.getWriteBufferSize();
8879 BitBufferHelper.overwriteWriteBuffer(lengthPosition - 1, [currentPosition - lengthPosition]);
8880 },
8882 /**
8883 * User data subparameter encoder : Reply Option
8884 *
8885 * @see 3GGP2 C.S0015-B 2.0, 4.5.11 Reply Option
8886 */
8887 encodeUserDataReplyOption: function(options) {
8888 if (options.requestStatusReport) {
8889 let BitBufferHelper = this.context.BitBufferHelper;
8890 BitBufferHelper.writeBits(PDU_CDMA_MSG_USERDATA_REPLY_OPTION, 8);
8891 BitBufferHelper.writeBits(1, 8);
8892 BitBufferHelper.writeBits(0, 1); // USER_ACK_REQ
8893 BitBufferHelper.writeBits(1, 1); // DAK_REQ
8894 BitBufferHelper.flushWithPadding();
8895 }
8896 },
8898 /**
8899 * Entry point for SMS decoding, the returned object is made compatible
8900 * with existing readMessage() of GsmPDUHelper
8901 */
8902 readMessage: function() {
8903 let message = {};
8905 // Teleservice Identifier
8906 message.teleservice = this.readInt();
8908 // Message Type
8909 let isServicePresent = this.readByte();
8910 if (isServicePresent) {
8911 message.messageType = PDU_CDMA_MSG_TYPE_BROADCAST;
8912 } else {
8913 if (message.teleservice) {
8914 message.messageType = PDU_CDMA_MSG_TYPE_P2P;
8915 } else {
8916 message.messageType = PDU_CDMA_MSG_TYPE_ACK;
8917 }
8918 }
8920 // Service Category
8921 message.service = this.readInt();
8923 // Originated Address
8924 let addrInfo = {};
8925 addrInfo.digitMode = (this.readInt() & 0x01);
8926 addrInfo.numberMode = (this.readInt() & 0x01);
8927 addrInfo.numberType = (this.readInt() & 0x01);
8928 addrInfo.numberPlan = (this.readInt() & 0x01);
8929 addrInfo.addrLength = this.readByte();
8930 addrInfo.address = [];
8931 for (let i = 0; i < addrInfo.addrLength; i++) {
8932 addrInfo.address.push(this.readByte());
8933 }
8934 message.sender = this.decodeAddr(addrInfo);
8936 // Originated Subaddress
8937 addrInfo.Type = (this.readInt() & 0x07);
8938 addrInfo.Odd = (this.readByte() & 0x01);
8939 addrInfo.addrLength = this.readByte();
8940 for (let i = 0; i < addrInfo.addrLength; i++) {
8941 let addrDigit = this.readByte();
8942 message.sender += String.fromCharCode(addrDigit);
8943 }
8945 // Bearer Data
8946 this.decodeUserData(message);
8948 // Bearer Data Sub-Parameter: User Data
8949 let userData = message[PDU_CDMA_MSG_USERDATA_BODY];
8950 [message.header, message.body, message.encoding, message.data] =
8951 (userData) ? [userData.header, userData.body, userData.encoding, userData.data]
8952 : [null, null, null, null];
8954 // Bearer Data Sub-Parameter: Message Status
8955 // Success Delivery (0) if both Message Status and User Data are absent.
8956 // Message Status absent (-1) if only User Data is available.
8957 let msgStatus = message[PDU_CDMA_MSG_USER_DATA_MSG_STATUS];
8958 [message.errorClass, message.msgStatus] =
8959 (msgStatus) ? [msgStatus.errorClass, msgStatus.msgStatus]
8960 : ((message.body) ? [-1, -1] : [0, 0]);
8962 // Transform message to GSM msg
8963 let msg = {
8964 SMSC: "",
8965 mti: 0,
8966 udhi: 0,
8967 sender: message.sender,
8968 recipient: null,
8969 pid: PDU_PID_DEFAULT,
8970 epid: PDU_PID_DEFAULT,
8971 dcs: 0,
8972 mwi: null,
8973 replace: false,
8974 header: message.header,
8975 body: message.body,
8976 data: message.data,
8977 sentTimestamp: message[PDU_CDMA_MSG_USERDATA_TIMESTAMP],
8978 language: message[PDU_CDMA_LANGUAGE_INDICATOR],
8979 status: null,
8980 scts: null,
8981 dt: null,
8982 encoding: message.encoding,
8983 messageClass: GECKO_SMS_MESSAGE_CLASSES[PDU_DCS_MSG_CLASS_NORMAL],
8984 messageType: message.messageType,
8985 serviceCategory: message.service,
8986 subMsgType: message[PDU_CDMA_MSG_USERDATA_MSG_ID].msgType,
8987 msgId: message[PDU_CDMA_MSG_USERDATA_MSG_ID].msgId,
8988 errorClass: message.errorClass,
8989 msgStatus: message.msgStatus,
8990 teleservice: message.teleservice
8991 };
8993 return msg;
8994 },
8996 /**
8997 * Helper for processing received SMS parcel data.
8998 *
8999 * @param length
9000 * Length of SMS string in the incoming parcel.
9001 *
9002 * @return Message parsed or null for invalid message.
9003 */
9004 processReceivedSms: function(length) {
9005 if (!length) {
9006 if (DEBUG) this.context.debug("Received empty SMS!");
9007 return [null, PDU_FCS_UNSPECIFIED];
9008 }
9010 let message = this.readMessage();
9011 if (DEBUG) this.context.debug("Got new SMS: " + JSON.stringify(message));
9013 // Determine result
9014 if (!message) {
9015 return [null, PDU_FCS_UNSPECIFIED];
9016 }
9018 return [message, PDU_FCS_OK];
9019 },
9021 /**
9022 * Data readers
9023 */
9024 readInt: function() {
9025 return this.context.Buf.readInt32();
9026 },
9028 readByte: function() {
9029 return (this.context.Buf.readInt32() & 0xFF);
9030 },
9032 /**
9033 * Decode CDMA address data into address string
9034 *
9035 * @see 3GGP2 C.S0015-B 2.0, 3.4.3.3 Address Parameters
9036 *
9037 * Required key in addrInfo
9038 * @param addrLength
9039 * Length of address
9040 * @param digitMode
9041 * Address encoding method
9042 * @param address
9043 * Array of encoded address data
9044 */
9045 decodeAddr: function(addrInfo) {
9046 let result = "";
9047 for (let i = 0; i < addrInfo.addrLength; i++) {
9048 if (addrInfo.digitMode === PDU_CDMA_MSG_ADDR_DIGIT_MODE_DTMF) {
9049 result += this.dtmfChars.charAt(addrInfo.address[i]);
9050 } else {
9051 result += String.fromCharCode(addrInfo.address[i]);
9052 }
9053 }
9054 return result;
9055 },
9057 /**
9058 * Read userData in parcel buffer and decode into message object.
9059 * Each subparameter will be stored in corresponding key.
9060 *
9061 * @see 3GGP2 C.S0015-B 2.0, 3.4.3.7 Bearer Data
9062 * 4.5 Bearer Data Parameters
9063 */
9064 decodeUserData: function(message) {
9065 let userDataLength = this.readInt();
9067 while (userDataLength > 0) {
9068 let id = this.readByte(),
9069 length = this.readByte(),
9070 userDataBuffer = [];
9072 for (let i = 0; i < length; i++) {
9073 userDataBuffer.push(this.readByte());
9074 }
9076 this.context.BitBufferHelper.startRead(userDataBuffer);
9078 switch (id) {
9079 case PDU_CDMA_MSG_USERDATA_MSG_ID:
9080 message[id] = this.decodeUserDataMsgId();
9081 break;
9082 case PDU_CDMA_MSG_USERDATA_BODY:
9083 message[id] = this.decodeUserDataMsg(message[PDU_CDMA_MSG_USERDATA_MSG_ID].userHeader);
9084 break;
9085 case PDU_CDMA_MSG_USERDATA_TIMESTAMP:
9086 message[id] = this.decodeUserDataTimestamp();
9087 break;
9088 case PDU_CDMA_MSG_USERDATA_REPLY_OPTION:
9089 message[id] = this.decodeUserDataReplyOption();
9090 break;
9091 case PDU_CDMA_LANGUAGE_INDICATOR:
9092 message[id] = this.decodeLanguageIndicator();
9093 break;
9094 case PDU_CDMA_MSG_USERDATA_CALLBACK_NUMBER:
9095 message[id] = this.decodeUserDataCallbackNumber();
9096 break;
9097 case PDU_CDMA_MSG_USER_DATA_MSG_STATUS:
9098 message[id] = this.decodeUserDataMsgStatus();
9099 break;
9100 }
9102 userDataLength -= (length + 2);
9103 userDataBuffer = [];
9104 }
9105 },
9107 /**
9108 * User data subparameter decoder: Message Identifier
9109 *
9110 * @see 3GGP2 C.S0015-B 2.0, 4.5.1 Message Identifier
9111 */
9112 decodeUserDataMsgId: function() {
9113 let result = {};
9114 let BitBufferHelper = this.context.BitBufferHelper;
9115 result.msgType = BitBufferHelper.readBits(4);
9116 result.msgId = BitBufferHelper.readBits(16);
9117 result.userHeader = BitBufferHelper.readBits(1);
9119 return result;
9120 },
9122 /**
9123 * Decode user data header, we only care about segment information
9124 * on CDMA.
9125 *
9126 * This function is mostly copied from gsmPduHelper.readUserDataHeader() but
9127 * change the read function, because CDMA user header decoding is't byte-wise
9128 * aligned.
9129 */
9130 decodeUserDataHeader: function(encoding) {
9131 let BitBufferHelper = this.context.BitBufferHelper;
9132 let header = {},
9133 headerSize = BitBufferHelper.readBits(8),
9134 userDataHeaderSize = headerSize + 1,
9135 headerPaddingBits = 0;
9137 // Calculate header size
9138 if (encoding === PDU_DCS_MSG_CODING_7BITS_ALPHABET) {
9139 // Length is in 7-bit
9140 header.length = Math.ceil(userDataHeaderSize * 8 / 7);
9141 // Calulate padding length
9142 headerPaddingBits = (header.length * 7) - (userDataHeaderSize * 8);
9143 } else if (encoding === PDU_DCS_MSG_CODING_8BITS_ALPHABET) {
9144 header.length = userDataHeaderSize;
9145 } else {
9146 header.length = userDataHeaderSize / 2;
9147 }
9149 while (headerSize) {
9150 let identifier = BitBufferHelper.readBits(8),
9151 length = BitBufferHelper.readBits(8);
9153 headerSize -= (2 + length);
9155 switch (identifier) {
9156 case PDU_IEI_CONCATENATED_SHORT_MESSAGES_8BIT: {
9157 let ref = BitBufferHelper.readBits(8),
9158 max = BitBufferHelper.readBits(8),
9159 seq = BitBufferHelper.readBits(8);
9160 if (max && seq && (seq <= max)) {
9161 header.segmentRef = ref;
9162 header.segmentMaxSeq = max;
9163 header.segmentSeq = seq;
9164 }
9165 break;
9166 }
9167 case PDU_IEI_APPLICATION_PORT_ADDRESSING_SCHEME_8BIT: {
9168 let dstp = BitBufferHelper.readBits(8),
9169 orip = BitBufferHelper.readBits(8);
9170 if ((dstp < PDU_APA_RESERVED_8BIT_PORTS)
9171 || (orip < PDU_APA_RESERVED_8BIT_PORTS)) {
9172 // 3GPP TS 23.040 clause 9.2.3.24.3: "A receiving entity shall
9173 // ignore any information element where the value of the
9174 // Information-Element-Data is Reserved or not supported"
9175 break;
9176 }
9177 header.destinationPort = dstp;
9178 header.originatorPort = orip;
9179 break;
9180 }
9181 case PDU_IEI_APPLICATION_PORT_ADDRESSING_SCHEME_16BIT: {
9182 let dstp = (BitBufferHelper.readBits(8) << 8) | BitBufferHelper.readBits(8),
9183 orip = (BitBufferHelper.readBits(8) << 8) | BitBufferHelper.readBits(8);
9184 // 3GPP TS 23.040 clause 9.2.3.24.4: "A receiving entity shall
9185 // ignore any information element where the value of the
9186 // Information-Element-Data is Reserved or not supported"
9187 if ((dstp < PDU_APA_VALID_16BIT_PORTS)
9188 && (orip < PDU_APA_VALID_16BIT_PORTS)) {
9189 header.destinationPort = dstp;
9190 header.originatorPort = orip;
9191 }
9192 break;
9193 }
9194 case PDU_IEI_CONCATENATED_SHORT_MESSAGES_16BIT: {
9195 let ref = (BitBufferHelper.readBits(8) << 8) | BitBufferHelper.readBits(8),
9196 max = BitBufferHelper.readBits(8),
9197 seq = BitBufferHelper.readBits(8);
9198 if (max && seq && (seq <= max)) {
9199 header.segmentRef = ref;
9200 header.segmentMaxSeq = max;
9201 header.segmentSeq = seq;
9202 }
9203 break;
9204 }
9205 case PDU_IEI_NATIONAL_LANGUAGE_SINGLE_SHIFT: {
9206 let langShiftIndex = BitBufferHelper.readBits(8);
9207 if (langShiftIndex < PDU_NL_SINGLE_SHIFT_TABLES.length) {
9208 header.langShiftIndex = langShiftIndex;
9209 }
9210 break;
9211 }
9212 case PDU_IEI_NATIONAL_LANGUAGE_LOCKING_SHIFT: {
9213 let langIndex = BitBufferHelper.readBits(8);
9214 if (langIndex < PDU_NL_LOCKING_SHIFT_TABLES.length) {
9215 header.langIndex = langIndex;
9216 }
9217 break;
9218 }
9219 case PDU_IEI_SPECIAL_SMS_MESSAGE_INDICATION: {
9220 let msgInd = BitBufferHelper.readBits(8) & 0xFF,
9221 msgCount = BitBufferHelper.readBits(8);
9222 /*
9223 * TS 23.040 V6.8.1 Sec 9.2.3.24.2
9224 * bits 1 0 : basic message indication type
9225 * bits 4 3 2 : extended message indication type
9226 * bits 6 5 : Profile id
9227 * bit 7 : storage type
9228 */
9229 let storeType = msgInd & PDU_MWI_STORE_TYPE_BIT;
9230 header.mwi = {};
9231 mwi = header.mwi;
9233 if (storeType == PDU_MWI_STORE_TYPE_STORE) {
9234 // Store message because TP_UDH indicates so, note this may override
9235 // the setting in DCS, but that is expected
9236 mwi.discard = false;
9237 } else if (mwi.discard === undefined) {
9238 // storeType == PDU_MWI_STORE_TYPE_DISCARD
9239 // only override mwi.discard here if it hasn't already been set
9240 mwi.discard = true;
9241 }
9243 mwi.msgCount = msgCount & 0xFF;
9244 mwi.active = mwi.msgCount > 0;
9246 if (DEBUG) {
9247 this.context.debug("MWI in TP_UDH received: " + JSON.stringify(mwi));
9248 }
9249 break;
9250 }
9251 default:
9252 // Drop unsupported id
9253 for (let i = 0; i < length; i++) {
9254 BitBufferHelper.readBits(8);
9255 }
9256 }
9257 }
9259 // Consume padding bits
9260 if (headerPaddingBits) {
9261 BitBufferHelper.readBits(headerPaddingBits);
9262 }
9264 return header;
9265 },
9267 getCdmaMsgEncoding: function(encoding) {
9268 // Determine encoding method
9269 switch (encoding) {
9270 case PDU_CDMA_MSG_CODING_7BITS_ASCII:
9271 case PDU_CDMA_MSG_CODING_IA5:
9272 case PDU_CDMA_MSG_CODING_7BITS_GSM:
9273 return PDU_DCS_MSG_CODING_7BITS_ALPHABET;
9274 case PDU_CDMA_MSG_CODING_OCTET:
9275 case PDU_CDMA_MSG_CODING_IS_91:
9276 case PDU_CDMA_MSG_CODING_LATIN_HEBREW:
9277 case PDU_CDMA_MSG_CODING_LATIN:
9278 return PDU_DCS_MSG_CODING_8BITS_ALPHABET;
9279 case PDU_CDMA_MSG_CODING_UNICODE:
9280 case PDU_CDMA_MSG_CODING_SHIFT_JIS:
9281 case PDU_CDMA_MSG_CODING_KOREAN:
9282 return PDU_DCS_MSG_CODING_16BITS_ALPHABET;
9283 }
9284 return null;
9285 },
9287 decodeCdmaPDUMsg: function(encoding, msgType, msgBodySize) {
9288 const langTable = PDU_NL_LOCKING_SHIFT_TABLES[PDU_NL_IDENTIFIER_DEFAULT];
9289 const langShiftTable = PDU_NL_SINGLE_SHIFT_TABLES[PDU_NL_IDENTIFIER_DEFAULT];
9290 let BitBufferHelper = this.context.BitBufferHelper;
9291 let result = "";
9292 let msgDigit;
9293 switch (encoding) {
9294 case PDU_CDMA_MSG_CODING_OCTET: // TODO : Require Test
9295 while(msgBodySize > 0) {
9296 msgDigit = String.fromCharCode(BitBufferHelper.readBits(8));
9297 result += msgDigit;
9298 msgBodySize--;
9299 }
9300 break;
9301 case PDU_CDMA_MSG_CODING_IS_91: // TODO : Require Test
9302 // Referenced from android code
9303 switch (msgType) {
9304 case PDU_CDMA_MSG_CODING_IS_91_TYPE_SMS:
9305 case PDU_CDMA_MSG_CODING_IS_91_TYPE_SMS_FULL:
9306 case PDU_CDMA_MSG_CODING_IS_91_TYPE_VOICEMAIL_STATUS:
9307 while(msgBodySize > 0) {
9308 msgDigit = String.fromCharCode(BitBufferHelper.readBits(6) + 0x20);
9309 result += msgDigit;
9310 msgBodySize--;
9311 }
9312 break;
9313 case PDU_CDMA_MSG_CODING_IS_91_TYPE_CLI:
9314 let addrInfo = {};
9315 addrInfo.digitMode = PDU_CDMA_MSG_ADDR_DIGIT_MODE_DTMF;
9316 addrInfo.numberMode = PDU_CDMA_MSG_ADDR_NUMBER_MODE_ANSI;
9317 addrInfo.numberType = PDU_CDMA_MSG_ADDR_NUMBER_TYPE_UNKNOWN;
9318 addrInfo.numberPlan = PDU_CDMA_MSG_ADDR_NUMBER_PLAN_UNKNOWN;
9319 addrInfo.addrLength = msgBodySize;
9320 addrInfo.address = [];
9321 for (let i = 0; i < addrInfo.addrLength; i++) {
9322 addrInfo.address.push(BitBufferHelper.readBits(4));
9323 }
9324 result = this.decodeAddr(addrInfo);
9325 break;
9326 }
9327 // Fall through.
9328 case PDU_CDMA_MSG_CODING_7BITS_ASCII:
9329 case PDU_CDMA_MSG_CODING_IA5: // TODO : Require Test
9330 while(msgBodySize > 0) {
9331 msgDigit = BitBufferHelper.readBits(7);
9332 if (msgDigit >= 32) {
9333 msgDigit = String.fromCharCode(msgDigit);
9334 } else {
9335 if (msgDigit !== PDU_NL_EXTENDED_ESCAPE) {
9336 msgDigit = langTable[msgDigit];
9337 } else {
9338 msgDigit = BitBufferHelper.readBits(7);
9339 msgBodySize--;
9340 msgDigit = langShiftTable[msgDigit];
9341 }
9342 }
9343 result += msgDigit;
9344 msgBodySize--;
9345 }
9346 break;
9347 case PDU_CDMA_MSG_CODING_UNICODE:
9348 while(msgBodySize > 0) {
9349 msgDigit = String.fromCharCode(BitBufferHelper.readBits(16));
9350 result += msgDigit;
9351 msgBodySize--;
9352 }
9353 break;
9354 case PDU_CDMA_MSG_CODING_7BITS_GSM: // TODO : Require Test
9355 while(msgBodySize > 0) {
9356 msgDigit = BitBufferHelper.readBits(7);
9357 if (msgDigit !== PDU_NL_EXTENDED_ESCAPE) {
9358 msgDigit = langTable[msgDigit];
9359 } else {
9360 msgDigit = BitBufferHelper.readBits(7);
9361 msgBodySize--;
9362 msgDigit = langShiftTable[msgDigit];
9363 }
9364 result += msgDigit;
9365 msgBodySize--;
9366 }
9367 break;
9368 case PDU_CDMA_MSG_CODING_LATIN: // TODO : Require Test
9369 // Reference : http://en.wikipedia.org/wiki/ISO/IEC_8859-1
9370 while(msgBodySize > 0) {
9371 msgDigit = String.fromCharCode(BitBufferHelper.readBits(8));
9372 result += msgDigit;
9373 msgBodySize--;
9374 }
9375 break;
9376 case PDU_CDMA_MSG_CODING_LATIN_HEBREW: // TODO : Require Test
9377 // Reference : http://en.wikipedia.org/wiki/ISO/IEC_8859-8
9378 while(msgBodySize > 0) {
9379 msgDigit = BitBufferHelper.readBits(8);
9380 if (msgDigit === 0xDF) {
9381 msgDigit = String.fromCharCode(0x2017);
9382 } else if (msgDigit === 0xFD) {
9383 msgDigit = String.fromCharCode(0x200E);
9384 } else if (msgDigit === 0xFE) {
9385 msgDigit = String.fromCharCode(0x200F);
9386 } else if (msgDigit >= 0xE0 && msgDigit <= 0xFA) {
9387 msgDigit = String.fromCharCode(0x4F0 + msgDigit);
9388 } else {
9389 msgDigit = String.fromCharCode(msgDigit);
9390 }
9391 result += msgDigit;
9392 msgBodySize--;
9393 }
9394 break;
9395 case PDU_CDMA_MSG_CODING_SHIFT_JIS:
9396 // Reference : http://msdn.microsoft.com/en-US/goglobal/cc305152.aspx
9397 // http://demo.icu-project.org/icu-bin/convexp?conv=Shift_JIS
9398 let shift_jis_message = [];
9400 while (msgBodySize > 0) {
9401 shift_jis_message.push(BitBufferHelper.readBits(8));
9402 msgBodySize--;
9403 }
9405 let decoder = new TextDecoder("shift_jis");
9406 result = decoder.decode(new Uint8Array(shift_jis_message));
9407 break;
9408 case PDU_CDMA_MSG_CODING_KOREAN:
9409 case PDU_CDMA_MSG_CODING_GSM_DCS:
9410 // Fall through.
9411 default:
9412 break;
9413 }
9414 return result;
9415 },
9417 /**
9418 * User data subparameter decoder : User Data
9419 *
9420 * @see 3GGP2 C.S0015-B 2.0, 4.5.2 User Data
9421 */
9422 decodeUserDataMsg: function(hasUserHeader) {
9423 let BitBufferHelper = this.context.BitBufferHelper;
9424 let result = {},
9425 encoding = BitBufferHelper.readBits(5),
9426 msgType;
9428 if (encoding === PDU_CDMA_MSG_CODING_IS_91) {
9429 msgType = BitBufferHelper.readBits(8);
9430 }
9431 result.encoding = this.getCdmaMsgEncoding(encoding);
9433 let msgBodySize = BitBufferHelper.readBits(8);
9435 // For segmented SMS, a user header is included before sms content
9436 if (hasUserHeader) {
9437 result.header = this.decodeUserDataHeader(result.encoding);
9438 // header size is included in body size, they are decoded
9439 msgBodySize -= result.header.length;
9440 }
9442 // Store original payload if enconding is OCTET for further handling of WAP Push, etc.
9443 if (encoding === PDU_CDMA_MSG_CODING_OCTET && msgBodySize > 0) {
9444 result.data = new Uint8Array(msgBodySize);
9445 for (let i = 0; i < msgBodySize; i++) {
9446 result.data[i] = BitBufferHelper.readBits(8);
9447 }
9448 BitBufferHelper.backwardReadPilot(8 * msgBodySize);
9449 }
9451 // Decode sms content
9452 result.body = this.decodeCdmaPDUMsg(encoding, msgType, msgBodySize);
9454 return result;
9455 },
9457 decodeBcd: function(value) {
9458 return ((value >> 4) & 0xF) * 10 + (value & 0x0F);
9459 },
9461 /**
9462 * User data subparameter decoder : Time Stamp
9463 *
9464 * @see 3GGP2 C.S0015-B 2.0, 4.5.4 Message Center Time Stamp
9465 */
9466 decodeUserDataTimestamp: function() {
9467 let BitBufferHelper = this.context.BitBufferHelper;
9468 let year = this.decodeBcd(BitBufferHelper.readBits(8)),
9469 month = this.decodeBcd(BitBufferHelper.readBits(8)) - 1,
9470 date = this.decodeBcd(BitBufferHelper.readBits(8)),
9471 hour = this.decodeBcd(BitBufferHelper.readBits(8)),
9472 min = this.decodeBcd(BitBufferHelper.readBits(8)),
9473 sec = this.decodeBcd(BitBufferHelper.readBits(8));
9475 if (year >= 96 && year <= 99) {
9476 year += 1900;
9477 } else {
9478 year += 2000;
9479 }
9481 let result = (new Date(year, month, date, hour, min, sec, 0)).valueOf();
9483 return result;
9484 },
9486 /**
9487 * User data subparameter decoder : Reply Option
9488 *
9489 * @see 3GGP2 C.S0015-B 2.0, 4.5.11 Reply Option
9490 */
9491 decodeUserDataReplyOption: function() {
9492 let replyAction = this.context.BitBufferHelper.readBits(4),
9493 result = { userAck: (replyAction & 0x8) ? true : false,
9494 deliverAck: (replyAction & 0x4) ? true : false,
9495 readAck: (replyAction & 0x2) ? true : false,
9496 report: (replyAction & 0x1) ? true : false
9497 };
9499 return result;
9500 },
9502 /**
9503 * User data subparameter decoder : Language Indicator
9504 *
9505 * @see 3GGP2 C.S0015-B 2.0, 4.5.14 Language Indicator
9506 */
9507 decodeLanguageIndicator: function() {
9508 let language = this.context.BitBufferHelper.readBits(8);
9509 let result = CB_CDMA_LANG_GROUP[language];
9510 return result;
9511 },
9513 /**
9514 * User data subparameter decoder : Call-Back Number
9515 *
9516 * @see 3GGP2 C.S0015-B 2.0, 4.5.15 Call-Back Number
9517 */
9518 decodeUserDataCallbackNumber: function() {
9519 let BitBufferHelper = this.context.BitBufferHelper;
9520 let digitMode = BitBufferHelper.readBits(1);
9521 if (digitMode) {
9522 let numberType = BitBufferHelper.readBits(3),
9523 numberPlan = BitBufferHelper.readBits(4);
9524 }
9525 let numberFields = BitBufferHelper.readBits(8),
9526 result = "";
9527 for (let i = 0; i < numberFields; i++) {
9528 if (digitMode === PDU_CDMA_MSG_ADDR_DIGIT_MODE_DTMF) {
9529 let addrDigit = BitBufferHelper.readBits(4);
9530 result += this.dtmfChars.charAt(addrDigit);
9531 } else {
9532 let addrDigit = BitBufferHelper.readBits(8);
9533 result += String.fromCharCode(addrDigit);
9534 }
9535 }
9537 return result;
9538 },
9540 /**
9541 * User data subparameter decoder : Message Status
9542 *
9543 * @see 3GGP2 C.S0015-B 2.0, 4.5.21 Message Status
9544 */
9545 decodeUserDataMsgStatus: function() {
9546 let BitBufferHelper = this.context.BitBufferHelper;
9547 let result = {
9548 errorClass: BitBufferHelper.readBits(2),
9549 msgStatus: BitBufferHelper.readBits(6)
9550 };
9552 return result;
9553 },
9555 /**
9556 * Decode information record parcel.
9557 */
9558 decodeInformationRecord: function() {
9559 let Buf = this.context.Buf;
9560 let record = {};
9561 let numOfRecords = Buf.readInt32();
9563 let type;
9564 for (let i = 0; i < numOfRecords; i++) {
9565 type = Buf.readInt32();
9567 switch (type) {
9568 /*
9569 * Every type is encaped by ril, except extended display
9570 */
9571 case PDU_CDMA_INFO_REC_TYPE_DISPLAY:
9572 record.display = Buf.readString();
9573 break;
9574 case PDU_CDMA_INFO_REC_TYPE_CALLED_PARTY_NUMBER:
9575 record.calledNumber = {};
9576 record.calledNumber.number = Buf.readString();
9577 record.calledNumber.type = Buf.readInt32();
9578 record.calledNumber.plan = Buf.readInt32();
9579 record.calledNumber.pi = Buf.readInt32();
9580 record.calledNumber.si = Buf.readInt32();
9581 break;
9582 case PDU_CDMA_INFO_REC_TYPE_CALLING_PARTY_NUMBER:
9583 record.callingNumber = {};
9584 record.callingNumber.number = Buf.readString();
9585 record.callingNumber.type = Buf.readInt32();
9586 record.callingNumber.plan = Buf.readInt32();
9587 record.callingNumber.pi = Buf.readInt32();
9588 record.callingNumber.si = Buf.readInt32();
9589 break;
9590 case PDU_CDMA_INFO_REC_TYPE_CONNECTED_NUMBER:
9591 record.connectedNumber = {};
9592 record.connectedNumber.number = Buf.readString();
9593 record.connectedNumber.type = Buf.readInt32();
9594 record.connectedNumber.plan = Buf.readInt32();
9595 record.connectedNumber.pi = Buf.readInt32();
9596 record.connectedNumber.si = Buf.readInt32();
9597 break;
9598 case PDU_CDMA_INFO_REC_TYPE_SIGNAL:
9599 record.signal = {};
9600 record.signal.present = Buf.readInt32();
9601 record.signal.type = Buf.readInt32();
9602 record.signal.alertPitch = Buf.readInt32();
9603 record.signal.signal = Buf.readInt32();
9604 break;
9605 case PDU_CDMA_INFO_REC_TYPE_REDIRECTING_NUMBER:
9606 record.redirect = {};
9607 record.redirect.number = Buf.readString();
9608 record.redirect.type = Buf.readInt32();
9609 record.redirect.plan = Buf.readInt32();
9610 record.redirect.pi = Buf.readInt32();
9611 record.redirect.si = Buf.readInt32();
9612 record.redirect.reason = Buf.readInt32();
9613 break;
9614 case PDU_CDMA_INFO_REC_TYPE_LINE_CONTROL:
9615 record.lineControl = {};
9616 record.lineControl.polarityIncluded = Buf.readInt32();
9617 record.lineControl.toggle = Buf.readInt32();
9618 record.lineControl.recerse = Buf.readInt32();
9619 record.lineControl.powerDenial = Buf.readInt32();
9620 break;
9621 case PDU_CDMA_INFO_REC_TYPE_EXTENDED_DISPLAY:
9622 let length = Buf.readInt32();
9623 /*
9624 * Extended display is still in format defined in
9625 * C.S0005-F v1.0, 3.7.5.16
9626 */
9627 record.extendedDisplay = {};
9629 let headerByte = Buf.readInt32();
9630 length--;
9631 // Based on spec, headerByte must be 0x80 now
9632 record.extendedDisplay.indicator = (headerByte >> 7);
9633 record.extendedDisplay.type = (headerByte & 0x7F);
9634 record.extendedDisplay.records = [];
9636 while (length > 0) {
9637 let display = {};
9639 display.tag = Buf.readInt32();
9640 length--;
9641 if (display.tag !== INFO_REC_EXTENDED_DISPLAY_BLANK &&
9642 display.tag !== INFO_REC_EXTENDED_DISPLAY_SKIP) {
9643 display.content = Buf.readString();
9644 length -= (display.content.length + 1);
9645 }
9647 record.extendedDisplay.records.push(display);
9648 }
9649 break;
9650 case PDU_CDMA_INFO_REC_TYPE_T53_CLIR:
9651 record.cause = Buf.readInt32();
9652 break;
9653 case PDU_CDMA_INFO_REC_TYPE_T53_AUDIO_CONTROL:
9654 record.audioControl = {};
9655 record.audioControl.upLink = Buf.readInt32();
9656 record.audioControl.downLink = Buf.readInt32();
9657 break;
9658 case PDU_CDMA_INFO_REC_TYPE_T53_RELEASE:
9659 // Fall through
9660 default:
9661 throw new Error("UNSOLICITED_CDMA_INFO_REC(), Unsupported information record type " + record.type + "\n");
9662 }
9663 }
9665 return record;
9666 }
9667 };
9669 /**
9670 * Helper for processing ICC PDUs.
9671 */
9672 function ICCPDUHelperObject(aContext) {
9673 this.context = aContext;
9674 }
9675 ICCPDUHelperObject.prototype = {
9676 context: null,
9678 /**
9679 * Read GSM 8-bit unpacked octets,
9680 * which are default 7-bit alphabets with bit 8 set to 0.
9681 *
9682 * @param numOctets
9683 * Number of octets to be read.
9684 */
9685 read8BitUnpackedToString: function(numOctets) {
9686 let GsmPDUHelper = this.context.GsmPDUHelper;
9688 let ret = "";
9689 let escapeFound = false;
9690 let i;
9691 const langTable = PDU_NL_LOCKING_SHIFT_TABLES[PDU_NL_IDENTIFIER_DEFAULT];
9692 const langShiftTable = PDU_NL_SINGLE_SHIFT_TABLES[PDU_NL_IDENTIFIER_DEFAULT];
9694 for(i = 0; i < numOctets; i++) {
9695 let octet = GsmPDUHelper.readHexOctet();
9696 if (octet == 0xff) {
9697 i++;
9698 break;
9699 }
9701 if (escapeFound) {
9702 escapeFound = false;
9703 if (octet == PDU_NL_EXTENDED_ESCAPE) {
9704 // According to 3GPP TS 23.038, section 6.2.1.1, NOTE 1, "On
9705 // receipt of this code, a receiving entity shall display a space
9706 // until another extensiion table is defined."
9707 ret += " ";
9708 } else if (octet == PDU_NL_RESERVED_CONTROL) {
9709 // According to 3GPP TS 23.038 B.2, "This code represents a control
9710 // character and therefore must not be used for language specific
9711 // characters."
9712 ret += " ";
9713 } else {
9714 ret += langShiftTable[octet];
9715 }
9716 } else if (octet == PDU_NL_EXTENDED_ESCAPE) {
9717 escapeFound = true;
9718 } else {
9719 ret += langTable[octet];
9720 }
9721 }
9723 let Buf = this.context.Buf;
9724 Buf.seekIncoming((numOctets - i) * Buf.PDU_HEX_OCTET_SIZE);
9725 return ret;
9726 },
9728 /**
9729 * Write GSM 8-bit unpacked octets.
9730 *
9731 * @param numOctets Number of total octets to be writen, including trailing
9732 * 0xff.
9733 * @param str String to be written. Could be null.
9734 */
9735 writeStringTo8BitUnpacked: function(numOctets, str) {
9736 const langTable = PDU_NL_LOCKING_SHIFT_TABLES[PDU_NL_IDENTIFIER_DEFAULT];
9737 const langShiftTable = PDU_NL_SINGLE_SHIFT_TABLES[PDU_NL_IDENTIFIER_DEFAULT];
9739 let GsmPDUHelper = this.context.GsmPDUHelper;
9741 // If the character is GSM extended alphabet, two octets will be written.
9742 // So we need to keep track of number of octets to be written.
9743 let i, j;
9744 let len = str ? str.length : 0;
9745 for (i = 0, j = 0; i < len && j < numOctets; i++) {
9746 let c = str.charAt(i);
9747 let octet = langTable.indexOf(c);
9749 if (octet == -1) {
9750 // Make sure we still have enough space to write two octets.
9751 if (j + 2 > numOctets) {
9752 break;
9753 }
9755 octet = langShiftTable.indexOf(c);
9756 if (octet == -1) {
9757 // Fallback to ASCII space.
9758 octet = langTable.indexOf(' ');
9759 }
9760 GsmPDUHelper.writeHexOctet(PDU_NL_EXTENDED_ESCAPE);
9761 j++;
9762 }
9763 GsmPDUHelper.writeHexOctet(octet);
9764 j++;
9765 }
9767 // trailing 0xff
9768 while (j++ < numOctets) {
9769 GsmPDUHelper.writeHexOctet(0xff);
9770 }
9771 },
9773 /**
9774 * Read UCS2 String on UICC.
9775 *
9776 * @see TS 101.221, Annex A.
9777 * @param scheme
9778 * Coding scheme for UCS2 on UICC. One of 0x80, 0x81 or 0x82.
9779 * @param numOctets
9780 * Number of octets to be read as UCS2 string.
9781 */
9782 readICCUCS2String: function(scheme, numOctets) {
9783 let Buf = this.context.Buf;
9784 let GsmPDUHelper = this.context.GsmPDUHelper;
9786 let str = "";
9787 switch (scheme) {
9788 /**
9789 * +------+---------+---------+---------+---------+------+------+
9790 * | 0x80 | Ch1_msb | Ch1_lsb | Ch2_msb | Ch2_lsb | 0xff | 0xff |
9791 * +------+---------+---------+---------+---------+------+------+
9792 */
9793 case 0x80:
9794 let isOdd = numOctets % 2;
9795 let i;
9796 for (i = 0; i < numOctets - isOdd; i += 2) {
9797 let code = (GsmPDUHelper.readHexOctet() << 8) | GsmPDUHelper.readHexOctet();
9798 if (code == 0xffff) {
9799 i += 2;
9800 break;
9801 }
9802 str += String.fromCharCode(code);
9803 }
9805 // Skip trailing 0xff
9806 Buf.seekIncoming((numOctets - i) * Buf.PDU_HEX_OCTET_SIZE);
9807 break;
9808 case 0x81: // Fall through
9809 case 0x82:
9810 /**
9811 * +------+-----+--------+-----+-----+-----+--------+------+
9812 * | 0x81 | len | offset | Ch1 | Ch2 | ... | Ch_len | 0xff |
9813 * +------+-----+--------+-----+-----+-----+--------+------+
9814 *
9815 * len : The length of characters.
9816 * offset : 0hhh hhhh h000 0000
9817 * Ch_n: bit 8 = 0
9818 * GSM default alphabets
9819 * bit 8 = 1
9820 * UCS2 character whose char code is (Ch_n & 0x7f) + offset
9821 *
9822 * +------+-----+------------+------------+-----+-----+-----+--------+
9823 * | 0x82 | len | offset_msb | offset_lsb | Ch1 | Ch2 | ... | Ch_len |
9824 * +------+-----+------------+------------+-----+-----+-----+--------+
9825 *
9826 * len : The length of characters.
9827 * offset_msb, offset_lsn: offset
9828 * Ch_n: bit 8 = 0
9829 * GSM default alphabets
9830 * bit 8 = 1
9831 * UCS2 character whose char code is (Ch_n & 0x7f) + offset
9832 */
9833 let len = GsmPDUHelper.readHexOctet();
9834 let offset, headerLen;
9835 if (scheme == 0x81) {
9836 offset = GsmPDUHelper.readHexOctet() << 7;
9837 headerLen = 2;
9838 } else {
9839 offset = (GsmPDUHelper.readHexOctet() << 8) | GsmPDUHelper.readHexOctet();
9840 headerLen = 3;
9841 }
9843 for (let i = 0; i < len; i++) {
9844 let ch = GsmPDUHelper.readHexOctet();
9845 if (ch & 0x80) {
9846 // UCS2
9847 str += String.fromCharCode((ch & 0x7f) + offset);
9848 } else {
9849 // GSM 8bit
9850 let count = 0, gotUCS2 = 0;
9851 while ((i + count + 1 < len)) {
9852 count++;
9853 if (GsmPDUHelper.readHexOctet() & 0x80) {
9854 gotUCS2 = 1;
9855 break;
9856 }
9857 }
9858 // Unread.
9859 // +1 for the GSM alphabet indexed at i,
9860 Buf.seekIncoming(-1 * (count + 1) * Buf.PDU_HEX_OCTET_SIZE);
9861 str += this.read8BitUnpackedToString(count + 1 - gotUCS2);
9862 i += count - gotUCS2;
9863 }
9864 }
9866 // Skipping trailing 0xff
9867 Buf.seekIncoming((numOctets - len - headerLen) * Buf.PDU_HEX_OCTET_SIZE);
9868 break;
9869 }
9870 return str;
9871 },
9873 /**
9874 * Read Alpha Id and Dialling number from TS TS 151.011 clause 10.5.1
9875 *
9876 * @param recordSize The size of linear fixed record.
9877 */
9878 readAlphaIdDiallingNumber: function(recordSize) {
9879 let Buf = this.context.Buf;
9880 let length = Buf.readInt32();
9882 let alphaLen = recordSize - ADN_FOOTER_SIZE_BYTES;
9883 let alphaId = this.readAlphaIdentifier(alphaLen);
9885 let number = this.readNumberWithLength();
9887 // Skip 2 unused octets, CCP and EXT1.
9888 Buf.seekIncoming(2 * Buf.PDU_HEX_OCTET_SIZE);
9889 Buf.readStringDelimiter(length);
9891 let contact = null;
9892 if (alphaId || number) {
9893 contact = {alphaId: alphaId,
9894 number: number};
9895 }
9896 return contact;
9897 },
9899 /**
9900 * Write Alpha Identifier and Dialling number from TS 151.011 clause 10.5.1
9901 *
9902 * @param recordSize The size of linear fixed record.
9903 * @param alphaId Alpha Identifier to be written.
9904 * @param number Dialling Number to be written.
9905 */
9906 writeAlphaIdDiallingNumber: function(recordSize, alphaId, number) {
9907 let Buf = this.context.Buf;
9908 let GsmPDUHelper = this.context.GsmPDUHelper;
9910 // Write String length
9911 let strLen = recordSize * 2;
9912 Buf.writeInt32(strLen);
9914 let alphaLen = recordSize - ADN_FOOTER_SIZE_BYTES;
9915 this.writeAlphaIdentifier(alphaLen, alphaId);
9916 this.writeNumberWithLength(number);
9918 // Write unused octets 0xff, CCP and EXT1.
9919 GsmPDUHelper.writeHexOctet(0xff);
9920 GsmPDUHelper.writeHexOctet(0xff);
9921 Buf.writeStringDelimiter(strLen);
9922 },
9924 /**
9925 * Read Alpha Identifier.
9926 *
9927 * @see TS 131.102
9928 *
9929 * @param numOctets
9930 * Number of octets to be read.
9931 *
9932 * It uses either
9933 * 1. SMS default 7-bit alphabet with bit 8 set to 0.
9934 * 2. UCS2 string.
9935 *
9936 * Unused bytes should be set to 0xff.
9937 */
9938 readAlphaIdentifier: function(numOctets) {
9939 if (numOctets === 0) {
9940 return "";
9941 }
9943 let temp;
9944 // Read the 1st octet to determine the encoding.
9945 if ((temp = this.context.GsmPDUHelper.readHexOctet()) == 0x80 ||
9946 temp == 0x81 ||
9947 temp == 0x82) {
9948 numOctets--;
9949 return this.readICCUCS2String(temp, numOctets);
9950 } else {
9951 let Buf = this.context.Buf;
9952 Buf.seekIncoming(-1 * Buf.PDU_HEX_OCTET_SIZE);
9953 return this.read8BitUnpackedToString(numOctets);
9954 }
9955 },
9957 /**
9958 * Write Alpha Identifier.
9959 *
9960 * @param numOctets
9961 * Total number of octets to be written. This includes the length of
9962 * alphaId and the length of trailing unused octets(0xff).
9963 * @param alphaId
9964 * Alpha Identifier to be written.
9965 *
9966 * Unused octets will be written as 0xff.
9967 */
9968 writeAlphaIdentifier: function(numOctets, alphaId) {
9969 if (numOctets === 0) {
9970 return;
9971 }
9973 // If alphaId is empty or it's of GSM 8 bit.
9974 if (!alphaId || this.context.ICCUtilsHelper.isGsm8BitAlphabet(alphaId)) {
9975 this.writeStringTo8BitUnpacked(numOctets, alphaId);
9976 } else {
9977 let GsmPDUHelper = this.context.GsmPDUHelper;
9979 // Currently only support UCS2 coding scheme 0x80.
9980 GsmPDUHelper.writeHexOctet(0x80);
9981 numOctets--;
9982 // Now the alphaId is UCS2 string, each character will take 2 octets.
9983 if (alphaId.length * 2 > numOctets) {
9984 alphaId = alphaId.substring(0, Math.floor(numOctets / 2));
9985 }
9986 GsmPDUHelper.writeUCS2String(alphaId);
9987 for (let i = alphaId.length * 2; i < numOctets; i++) {
9988 GsmPDUHelper.writeHexOctet(0xff);
9989 }
9990 }
9991 },
9993 /**
9994 * Read Dialling number.
9995 *
9996 * @see TS 131.102
9997 *
9998 * @param len
9999 * The Length of BCD number.
10000 *
10001 * From TS 131.102, in EF_ADN, EF_FDN, the field 'Length of BCD number'
10002 * means the total bytes should be allocated to store the TON/NPI and
10003 * the dialing number.
10004 * For example, if the dialing number is 1234567890,
10005 * and the TON/NPI is 0x81,
10006 * The field 'Length of BCD number' should be 06, which is
10007 * 1 byte to store the TON/NPI, 0x81
10008 * 5 bytes to store the BCD number 2143658709.
10009 *
10010 * Here the definition of the length is different from SMS spec,
10011 * TS 23.040 9.1.2.5, which the length means
10012 * "number of useful semi-octets within the Address-Value field".
10013 */
10014 readDiallingNumber: function(len) {
10015 if (DEBUG) this.context.debug("PDU: Going to read Dialling number: " + len);
10016 if (len === 0) {
10017 return "";
10018 }
10020 let GsmPDUHelper = this.context.GsmPDUHelper;
10022 // TOA = TON + NPI
10023 let toa = GsmPDUHelper.readHexOctet();
10025 let number = GsmPDUHelper.readSwappedNibbleBcdString(len - 1);
10026 if (number.length <= 0) {
10027 if (DEBUG) this.context.debug("No number provided");
10028 return "";
10029 }
10030 if ((toa >> 4) == (PDU_TOA_INTERNATIONAL >> 4)) {
10031 number = '+' + number;
10032 }
10033 return number;
10034 },
10036 /**
10037 * Write Dialling Number.
10038 *
10039 * @param number The Dialling number
10040 */
10041 writeDiallingNumber: function(number) {
10042 let GsmPDUHelper = this.context.GsmPDUHelper;
10044 let toa = PDU_TOA_ISDN; // 81
10045 if (number[0] == '+') {
10046 toa = PDU_TOA_INTERNATIONAL | PDU_TOA_ISDN; // 91
10047 number = number.substring(1);
10048 }
10049 GsmPDUHelper.writeHexOctet(toa);
10050 GsmPDUHelper.writeSwappedNibbleBCD(number);
10051 },
10053 readNumberWithLength: function() {
10054 let Buf = this.context.Buf;
10055 let number;
10056 let numLen = this.context.GsmPDUHelper.readHexOctet();
10057 if (numLen != 0xff) {
10058 if (numLen > ADN_MAX_BCD_NUMBER_BYTES) {
10059 throw new Error("invalid length of BCD number/SSC contents - " + numLen);
10060 }
10062 number = this.readDiallingNumber(numLen);
10063 Buf.seekIncoming((ADN_MAX_BCD_NUMBER_BYTES - numLen) * Buf.PDU_HEX_OCTET_SIZE);
10064 } else {
10065 Buf.seekIncoming(ADN_MAX_BCD_NUMBER_BYTES * Buf.PDU_HEX_OCTET_SIZE);
10066 }
10068 return number;
10069 },
10071 writeNumberWithLength: function(number) {
10072 let GsmPDUHelper = this.context.GsmPDUHelper;
10074 if (number) {
10075 let numStart = number[0] == "+" ? 1 : 0;
10076 number = number.substring(0, numStart) +
10077 number.substring(numStart)
10078 .replace(/[^0-9*#,]/g, "")
10079 .replace(/\*/g, "a")
10080 .replace(/\#/g, "b")
10081 .replace(/\,/g, "c");
10083 let numDigits = number.length - numStart;
10084 if (numDigits > ADN_MAX_NUMBER_DIGITS) {
10085 number = number.substring(0, ADN_MAX_NUMBER_DIGITS + numStart);
10086 numDigits = number.length - numStart;
10087 }
10089 // +1 for TON/NPI
10090 let numLen = Math.ceil(numDigits / 2) + 1;
10091 GsmPDUHelper.writeHexOctet(numLen);
10092 this.writeDiallingNumber(number);
10093 // Write trailing 0xff of Dialling Number.
10094 for (let i = 0; i < ADN_MAX_BCD_NUMBER_BYTES - numLen; i++) {
10095 GsmPDUHelper.writeHexOctet(0xff);
10096 }
10097 } else {
10098 // +1 for numLen
10099 for (let i = 0; i < ADN_MAX_BCD_NUMBER_BYTES + 1; i++) {
10100 GsmPDUHelper.writeHexOctet(0xff);
10101 }
10102 }
10103 }
10104 };
10106 function StkCommandParamsFactoryObject(aContext) {
10107 this.context = aContext;
10108 }
10109 StkCommandParamsFactoryObject.prototype = {
10110 context: null,
10112 createParam: function(cmdDetails, ctlvs) {
10113 let method = this[cmdDetails.typeOfCommand];
10114 if (typeof method != "function") {
10115 if (DEBUG) {
10116 this.context.debug("Unknown proactive command " +
10117 cmdDetails.typeOfCommand.toString(16));
10118 }
10119 return null;
10120 }
10121 return method.call(this, cmdDetails, ctlvs);
10122 },
10124 /**
10125 * Construct a param for Refresh.
10126 *
10127 * @param cmdDetails
10128 * The value object of CommandDetails TLV.
10129 * @param ctlvs
10130 * The all TLVs in this proactive command.
10131 */
10132 processRefresh: function(cmdDetails, ctlvs) {
10133 let refreshType = cmdDetails.commandQualifier;
10134 switch (refreshType) {
10135 case STK_REFRESH_FILE_CHANGE:
10136 case STK_REFRESH_NAA_INIT_AND_FILE_CHANGE:
10137 let ctlv = this.context.StkProactiveCmdHelper.searchForTag(
10138 COMPREHENSIONTLV_TAG_FILE_LIST, ctlvs);
10139 if (ctlv) {
10140 let list = ctlv.value.fileList;
10141 if (DEBUG) {
10142 this.context.debug("Refresh, list = " + list);
10143 }
10144 this.context.ICCRecordHelper.fetchICCRecords();
10145 }
10146 break;
10147 }
10148 return null;
10149 },
10151 /**
10152 * Construct a param for Poll Interval.
10153 *
10154 * @param cmdDetails
10155 * The value object of CommandDetails TLV.
10156 * @param ctlvs
10157 * The all TLVs in this proactive command.
10158 */
10159 processPollInterval: function(cmdDetails, ctlvs) {
10160 let ctlv = this.context.StkProactiveCmdHelper.searchForTag(
10161 COMPREHENSIONTLV_TAG_DURATION, ctlvs);
10162 if (!ctlv) {
10163 this.context.RIL.sendStkTerminalResponse({
10164 command: cmdDetails,
10165 resultCode: STK_RESULT_REQUIRED_VALUES_MISSING});
10166 throw new Error("Stk Poll Interval: Required value missing : Duration");
10167 }
10169 return ctlv.value;
10170 },
10172 /**
10173 * Construct a param for Poll Off.
10174 *
10175 * @param cmdDetails
10176 * The value object of CommandDetails TLV.
10177 * @param ctlvs
10178 * The all TLVs in this proactive command.
10179 */
10180 processPollOff: function(cmdDetails, ctlvs) {
10181 return null;
10182 },
10184 /**
10185 * Construct a param for Set Up Event list.
10186 *
10187 * @param cmdDetails
10188 * The value object of CommandDetails TLV.
10189 * @param ctlvs
10190 * The all TLVs in this proactive command.
10191 */
10192 processSetUpEventList: function(cmdDetails, ctlvs) {
10193 let ctlv = this.context.StkProactiveCmdHelper.searchForTag(
10194 COMPREHENSIONTLV_TAG_EVENT_LIST, ctlvs);
10195 if (!ctlv) {
10196 this.context.RIL.sendStkTerminalResponse({
10197 command: cmdDetails,
10198 resultCode: STK_RESULT_REQUIRED_VALUES_MISSING});
10199 throw new Error("Stk Event List: Required value missing : Event List");
10200 }
10202 return ctlv.value || {eventList: null};
10203 },
10205 /**
10206 * Construct a param for Select Item.
10207 *
10208 * @param cmdDetails
10209 * The value object of CommandDetails TLV.
10210 * @param ctlvs
10211 * The all TLVs in this proactive command.
10212 */
10213 processSelectItem: function(cmdDetails, ctlvs) {
10214 let StkProactiveCmdHelper = this.context.StkProactiveCmdHelper;
10215 let menu = {};
10217 let ctlv = StkProactiveCmdHelper.searchForTag(COMPREHENSIONTLV_TAG_ALPHA_ID, ctlvs);
10218 if (ctlv) {
10219 menu.title = ctlv.value.identifier;
10220 }
10222 menu.items = [];
10223 for (let i = 0; i < ctlvs.length; i++) {
10224 let ctlv = ctlvs[i];
10225 if (ctlv.tag == COMPREHENSIONTLV_TAG_ITEM) {
10226 menu.items.push(ctlv.value);
10227 }
10228 }
10230 if (menu.items.length === 0) {
10231 this.context.RIL.sendStkTerminalResponse({
10232 command: cmdDetails,
10233 resultCode: STK_RESULT_REQUIRED_VALUES_MISSING});
10234 throw new Error("Stk Menu: Required value missing : items");
10235 }
10237 ctlv = StkProactiveCmdHelper.searchForTag(COMPREHENSIONTLV_TAG_ITEM_ID, ctlvs);
10238 if (ctlv) {
10239 menu.defaultItem = ctlv.value.identifier - 1;
10240 }
10242 // The 1st bit and 2nd bit determines the presentation type.
10243 menu.presentationType = cmdDetails.commandQualifier & 0x03;
10245 // Help information available.
10246 if (cmdDetails.commandQualifier & 0x80) {
10247 menu.isHelpAvailable = true;
10248 }
10250 ctlv = StkProactiveCmdHelper.searchForTag(COMPREHENSIONTLV_TAG_NEXT_ACTION_IND, ctlvs);
10251 if (ctlv) {
10252 menu.nextActionList = ctlv.value;
10253 }
10255 return menu;
10256 },
10258 processDisplayText: function(cmdDetails, ctlvs) {
10259 let StkProactiveCmdHelper = this.context.StkProactiveCmdHelper;
10260 let textMsg = {};
10262 let ctlv = StkProactiveCmdHelper.searchForTag(COMPREHENSIONTLV_TAG_TEXT_STRING, ctlvs);
10263 if (!ctlv) {
10264 this.context.RIL.sendStkTerminalResponse({
10265 command: cmdDetails,
10266 resultCode: STK_RESULT_REQUIRED_VALUES_MISSING});
10267 throw new Error("Stk Display Text: Required value missing : Text String");
10268 }
10269 textMsg.text = ctlv.value.textString;
10271 ctlv = StkProactiveCmdHelper.searchForTag(COMPREHENSIONTLV_TAG_IMMEDIATE_RESPONSE, ctlvs);
10272 if (ctlv) {
10273 textMsg.responseNeeded = true;
10274 }
10276 ctlv = StkProactiveCmdHelper.searchForTag(COMPREHENSIONTLV_TAG_DURATION, ctlvs);
10277 if (ctlv) {
10278 textMsg.duration = ctlv.value;
10279 }
10281 // High priority.
10282 if (cmdDetails.commandQualifier & 0x01) {
10283 textMsg.isHighPriority = true;
10284 }
10286 // User clear.
10287 if (cmdDetails.commandQualifier & 0x80) {
10288 textMsg.userClear = true;
10289 }
10291 return textMsg;
10292 },
10294 processSetUpIdleModeText: function(cmdDetails, ctlvs) {
10295 let textMsg = {};
10297 let ctlv = this.context.StkProactiveCmdHelper.searchForTag(
10298 COMPREHENSIONTLV_TAG_TEXT_STRING, ctlvs);
10299 if (!ctlv) {
10300 this.context.RIL.sendStkTerminalResponse({
10301 command: cmdDetails,
10302 resultCode: STK_RESULT_REQUIRED_VALUES_MISSING});
10303 throw new Error("Stk Set Up Idle Text: Required value missing : Text String");
10304 }
10305 textMsg.text = ctlv.value.textString;
10307 return textMsg;
10308 },
10310 processGetInkey: function(cmdDetails, ctlvs) {
10311 let StkProactiveCmdHelper = this.context.StkProactiveCmdHelper;
10312 let input = {};
10314 let ctlv = StkProactiveCmdHelper.searchForTag(COMPREHENSIONTLV_TAG_TEXT_STRING, ctlvs);
10315 if (!ctlv) {
10316 this.context.RIL.sendStkTerminalResponse({
10317 command: cmdDetails,
10318 resultCode: STK_RESULT_REQUIRED_VALUES_MISSING});
10319 throw new Error("Stk Get InKey: Required value missing : Text String");
10320 }
10321 input.text = ctlv.value.textString;
10323 // duration
10324 ctlv = StkProactiveCmdHelper.searchForTag(
10325 COMPREHENSIONTLV_TAG_DURATION, ctlvs);
10326 if (ctlv) {
10327 input.duration = ctlv.value;
10328 }
10330 input.minLength = 1;
10331 input.maxLength = 1;
10333 // isAlphabet
10334 if (cmdDetails.commandQualifier & 0x01) {
10335 input.isAlphabet = true;
10336 }
10338 // UCS2
10339 if (cmdDetails.commandQualifier & 0x02) {
10340 input.isUCS2 = true;
10341 }
10343 // Character sets defined in bit 1 and bit 2 are disable and
10344 // the YES/NO reponse is required.
10345 if (cmdDetails.commandQualifier & 0x04) {
10346 input.isYesNoRequested = true;
10347 }
10349 // Help information available.
10350 if (cmdDetails.commandQualifier & 0x80) {
10351 input.isHelpAvailable = true;
10352 }
10354 return input;
10355 },
10357 processGetInput: function(cmdDetails, ctlvs) {
10358 let StkProactiveCmdHelper = this.context.StkProactiveCmdHelper;
10359 let input = {};
10361 let ctlv = StkProactiveCmdHelper.searchForTag(COMPREHENSIONTLV_TAG_TEXT_STRING, ctlvs);
10362 if (!ctlv) {
10363 this.context.RIL.sendStkTerminalResponse({
10364 command: cmdDetails,
10365 resultCode: STK_RESULT_REQUIRED_VALUES_MISSING});
10366 throw new Error("Stk Get Input: Required value missing : Text String");
10367 }
10368 input.text = ctlv.value.textString;
10370 ctlv = StkProactiveCmdHelper.searchForTag(COMPREHENSIONTLV_TAG_RESPONSE_LENGTH, ctlvs);
10371 if (ctlv) {
10372 input.minLength = ctlv.value.minLength;
10373 input.maxLength = ctlv.value.maxLength;
10374 }
10376 ctlv = StkProactiveCmdHelper.searchForTag(COMPREHENSIONTLV_TAG_DEFAULT_TEXT, ctlvs);
10377 if (ctlv) {
10378 input.defaultText = ctlv.value.textString;
10379 }
10381 // Alphabet only
10382 if (cmdDetails.commandQualifier & 0x01) {
10383 input.isAlphabet = true;
10384 }
10386 // UCS2
10387 if (cmdDetails.commandQualifier & 0x02) {
10388 input.isUCS2 = true;
10389 }
10391 // User input shall not be revealed
10392 if (cmdDetails.commandQualifier & 0x04) {
10393 input.hideInput = true;
10394 }
10396 // User input in SMS packed format
10397 if (cmdDetails.commandQualifier & 0x08) {
10398 input.isPacked = true;
10399 }
10401 // Help information available.
10402 if (cmdDetails.commandQualifier & 0x80) {
10403 input.isHelpAvailable = true;
10404 }
10406 return input;
10407 },
10409 processEventNotify: function(cmdDetails, ctlvs) {
10410 let textMsg = {};
10412 let ctlv = this.context.StkProactiveCmdHelper.searchForTag(
10413 COMPREHENSIONTLV_TAG_ALPHA_ID, ctlvs);
10414 if (!ctlv) {
10415 this.context.RIL.sendStkTerminalResponse({
10416 command: cmdDetails,
10417 resultCode: STK_RESULT_REQUIRED_VALUES_MISSING});
10418 throw new Error("Stk Event Notfiy: Required value missing : Alpha ID");
10419 }
10420 textMsg.text = ctlv.value.identifier;
10422 return textMsg;
10423 },
10425 processSetupCall: function(cmdDetails, ctlvs) {
10426 let StkProactiveCmdHelper = this.context.StkProactiveCmdHelper;
10427 let call = {};
10428 let iter = Iterator(ctlvs);
10430 let ctlv = StkProactiveCmdHelper.searchForNextTag(COMPREHENSIONTLV_TAG_ALPHA_ID, iter);
10431 if (ctlv) {
10432 call.confirmMessage = ctlv.value.identifier;
10433 }
10435 ctlv = StkProactiveCmdHelper.searchForNextTag(COMPREHENSIONTLV_TAG_ALPHA_ID, iter);
10436 if (ctlv) {
10437 call.callMessage = ctlv.value.identifier;
10438 }
10440 ctlv = StkProactiveCmdHelper.searchForTag(COMPREHENSIONTLV_TAG_ADDRESS, ctlvs);
10441 if (!ctlv) {
10442 this.context.RIL.sendStkTerminalResponse({
10443 command: cmdDetails,
10444 resultCode: STK_RESULT_REQUIRED_VALUES_MISSING});
10445 throw new Error("Stk Set Up Call: Required value missing : Adress");
10446 }
10447 call.address = ctlv.value.number;
10449 // see 3GPP TS 31.111 section 6.4.13
10450 ctlv = StkProactiveCmdHelper.searchForTag(COMPREHENSIONTLV_TAG_DURATION, ctlvs);
10451 if (ctlv) {
10452 call.duration = ctlv.value;
10453 }
10455 return call;
10456 },
10458 processLaunchBrowser: function(cmdDetails, ctlvs) {
10459 let StkProactiveCmdHelper = this.context.StkProactiveCmdHelper;
10460 let browser = {};
10462 let ctlv = StkProactiveCmdHelper.searchForTag(COMPREHENSIONTLV_TAG_URL, ctlvs);
10463 if (!ctlv) {
10464 this.context.RIL.sendStkTerminalResponse({
10465 command: cmdDetails,
10466 resultCode: STK_RESULT_REQUIRED_VALUES_MISSING});
10467 throw new Error("Stk Launch Browser: Required value missing : URL");
10468 }
10469 browser.url = ctlv.value.url;
10471 ctlv = StkProactiveCmdHelper.searchForTag(COMPREHENSIONTLV_TAG_ALPHA_ID, ctlvs);
10472 if (ctlv) {
10473 browser.confirmMessage = ctlv.value.identifier;
10474 }
10476 browser.mode = cmdDetails.commandQualifier & 0x03;
10478 return browser;
10479 },
10481 processPlayTone: function(cmdDetails, ctlvs) {
10482 let StkProactiveCmdHelper = this.context.StkProactiveCmdHelper;
10483 let playTone = {};
10485 let ctlv = StkProactiveCmdHelper.searchForTag(
10486 COMPREHENSIONTLV_TAG_ALPHA_ID, ctlvs);
10487 if (ctlv) {
10488 playTone.text = ctlv.value.identifier;
10489 }
10491 ctlv = StkProactiveCmdHelper.searchForTag(COMPREHENSIONTLV_TAG_TONE, ctlvs);
10492 if (ctlv) {
10493 playTone.tone = ctlv.value.tone;
10494 }
10496 ctlv = StkProactiveCmdHelper.searchForTag(
10497 COMPREHENSIONTLV_TAG_DURATION, ctlvs);
10498 if (ctlv) {
10499 playTone.duration = ctlv.value;
10500 }
10502 // vibrate is only defined in TS 102.223
10503 playTone.isVibrate = (cmdDetails.commandQualifier & 0x01) !== 0x00;
10505 return playTone;
10506 },
10508 /**
10509 * Construct a param for Provide Local Information
10510 *
10511 * @param cmdDetails
10512 * The value object of CommandDetails TLV.
10513 * @param ctlvs
10514 * The all TLVs in this proactive command.
10515 */
10516 processProvideLocalInfo: function(cmdDetails, ctlvs) {
10517 let provideLocalInfo = {
10518 localInfoType: cmdDetails.commandQualifier
10519 };
10520 return provideLocalInfo;
10521 },
10523 processTimerManagement: function(cmdDetails, ctlvs) {
10524 let StkProactiveCmdHelper = this.context.StkProactiveCmdHelper;
10525 let timer = {
10526 timerAction: cmdDetails.commandQualifier
10527 };
10529 let ctlv = StkProactiveCmdHelper.searchForTag(
10530 COMPREHENSIONTLV_TAG_TIMER_IDENTIFIER, ctlvs);
10531 if (ctlv) {
10532 timer.timerId = ctlv.value.timerId;
10533 }
10535 ctlv = StkProactiveCmdHelper.searchForTag(
10536 COMPREHENSIONTLV_TAG_TIMER_VALUE, ctlvs);
10537 if (ctlv) {
10538 timer.timerValue = ctlv.value.timerValue;
10539 }
10541 return timer;
10542 },
10544 /**
10545 * Construct a param for BIP commands.
10546 *
10547 * @param cmdDetails
10548 * The value object of CommandDetails TLV.
10549 * @param ctlvs
10550 * The all TLVs in this proactive command.
10551 */
10552 processBipMessage: function(cmdDetails, ctlvs) {
10553 let bipMsg = {};
10555 let ctlv = this.context.StkProactiveCmdHelper.searchForTag(
10556 COMPREHENSIONTLV_TAG_ALPHA_ID, ctlvs);
10557 if (ctlv) {
10558 bipMsg.text = ctlv.value.identifier;
10559 }
10561 return bipMsg;
10562 }
10563 };
10564 StkCommandParamsFactoryObject.prototype[STK_CMD_REFRESH] = function STK_CMD_REFRESH(cmdDetails, ctlvs) {
10565 return this.processRefresh(cmdDetails, ctlvs);
10566 };
10567 StkCommandParamsFactoryObject.prototype[STK_CMD_POLL_INTERVAL] = function STK_CMD_POLL_INTERVAL(cmdDetails, ctlvs) {
10568 return this.processPollInterval(cmdDetails, ctlvs);
10569 };
10570 StkCommandParamsFactoryObject.prototype[STK_CMD_POLL_OFF] = function STK_CMD_POLL_OFF(cmdDetails, ctlvs) {
10571 return this.processPollOff(cmdDetails, ctlvs);
10572 };
10573 StkCommandParamsFactoryObject.prototype[STK_CMD_PROVIDE_LOCAL_INFO] = function STK_CMD_PROVIDE_LOCAL_INFO(cmdDetails, ctlvs) {
10574 return this.processProvideLocalInfo(cmdDetails, ctlvs);
10575 };
10576 StkCommandParamsFactoryObject.prototype[STK_CMD_SET_UP_EVENT_LIST] = function STK_CMD_SET_UP_EVENT_LIST(cmdDetails, ctlvs) {
10577 return this.processSetUpEventList(cmdDetails, ctlvs);
10578 };
10579 StkCommandParamsFactoryObject.prototype[STK_CMD_SET_UP_MENU] = function STK_CMD_SET_UP_MENU(cmdDetails, ctlvs) {
10580 return this.processSelectItem(cmdDetails, ctlvs);
10581 };
10582 StkCommandParamsFactoryObject.prototype[STK_CMD_SELECT_ITEM] = function STK_CMD_SELECT_ITEM(cmdDetails, ctlvs) {
10583 return this.processSelectItem(cmdDetails, ctlvs);
10584 };
10585 StkCommandParamsFactoryObject.prototype[STK_CMD_DISPLAY_TEXT] = function STK_CMD_DISPLAY_TEXT(cmdDetails, ctlvs) {
10586 return this.processDisplayText(cmdDetails, ctlvs);
10587 };
10588 StkCommandParamsFactoryObject.prototype[STK_CMD_SET_UP_IDLE_MODE_TEXT] = function STK_CMD_SET_UP_IDLE_MODE_TEXT(cmdDetails, ctlvs) {
10589 return this.processSetUpIdleModeText(cmdDetails, ctlvs);
10590 };
10591 StkCommandParamsFactoryObject.prototype[STK_CMD_GET_INKEY] = function STK_CMD_GET_INKEY(cmdDetails, ctlvs) {
10592 return this.processGetInkey(cmdDetails, ctlvs);
10593 };
10594 StkCommandParamsFactoryObject.prototype[STK_CMD_GET_INPUT] = function STK_CMD_GET_INPUT(cmdDetails, ctlvs) {
10595 return this.processGetInput(cmdDetails, ctlvs);
10596 };
10597 StkCommandParamsFactoryObject.prototype[STK_CMD_SEND_SS] = function STK_CMD_SEND_SS(cmdDetails, ctlvs) {
10598 return this.processEventNotify(cmdDetails, ctlvs);
10599 };
10600 StkCommandParamsFactoryObject.prototype[STK_CMD_SEND_USSD] = function STK_CMD_SEND_USSD(cmdDetails, ctlvs) {
10601 return this.processEventNotify(cmdDetails, ctlvs);
10602 };
10603 StkCommandParamsFactoryObject.prototype[STK_CMD_SEND_SMS] = function STK_CMD_SEND_SMS(cmdDetails, ctlvs) {
10604 return this.processEventNotify(cmdDetails, ctlvs);
10605 };
10606 StkCommandParamsFactoryObject.prototype[STK_CMD_SEND_DTMF] = function STK_CMD_SEND_DTMF(cmdDetails, ctlvs) {
10607 return this.processEventNotify(cmdDetails, ctlvs);
10608 };
10609 StkCommandParamsFactoryObject.prototype[STK_CMD_SET_UP_CALL] = function STK_CMD_SET_UP_CALL(cmdDetails, ctlvs) {
10610 return this.processSetupCall(cmdDetails, ctlvs);
10611 };
10612 StkCommandParamsFactoryObject.prototype[STK_CMD_LAUNCH_BROWSER] = function STK_CMD_LAUNCH_BROWSER(cmdDetails, ctlvs) {
10613 return this.processLaunchBrowser(cmdDetails, ctlvs);
10614 };
10615 StkCommandParamsFactoryObject.prototype[STK_CMD_PLAY_TONE] = function STK_CMD_PLAY_TONE(cmdDetails, ctlvs) {
10616 return this.processPlayTone(cmdDetails, ctlvs);
10617 };
10618 StkCommandParamsFactoryObject.prototype[STK_CMD_TIMER_MANAGEMENT] = function STK_CMD_TIMER_MANAGEMENT(cmdDetails, ctlvs) {
10619 return this.processTimerManagement(cmdDetails, ctlvs);
10620 };
10621 StkCommandParamsFactoryObject.prototype[STK_CMD_OPEN_CHANNEL] = function STK_CMD_OPEN_CHANNEL(cmdDetails, ctlvs) {
10622 return this.processBipMessage(cmdDetails, ctlvs);
10623 };
10624 StkCommandParamsFactoryObject.prototype[STK_CMD_CLOSE_CHANNEL] = function STK_CMD_CLOSE_CHANNEL(cmdDetails, ctlvs) {
10625 return this.processBipMessage(cmdDetails, ctlvs);
10626 };
10627 StkCommandParamsFactoryObject.prototype[STK_CMD_RECEIVE_DATA] = function STK_CMD_RECEIVE_DATA(cmdDetails, ctlvs) {
10628 return this.processBipMessage(cmdDetails, ctlvs);
10629 };
10630 StkCommandParamsFactoryObject.prototype[STK_CMD_SEND_DATA] = function STK_CMD_SEND_DATA(cmdDetails, ctlvs) {
10631 return this.processBipMessage(cmdDetails, ctlvs);
10632 };
10634 function StkProactiveCmdHelperObject(aContext) {
10635 this.context = aContext;
10636 }
10637 StkProactiveCmdHelperObject.prototype = {
10638 context: null,
10640 retrieve: function(tag, length) {
10641 let method = this[tag];
10642 if (typeof method != "function") {
10643 if (DEBUG) {
10644 this.context.debug("Unknown comprehension tag " + tag.toString(16));
10645 }
10646 let Buf = this.context.Buf;
10647 Buf.seekIncoming(length * Buf.PDU_HEX_OCTET_SIZE);
10648 return null;
10649 }
10650 return method.call(this, length);
10651 },
10653 /**
10654 * Command Details.
10655 *
10656 * | Byte | Description | Length |
10657 * | 1 | Command details Tag | 1 |
10658 * | 2 | Length = 03 | 1 |
10659 * | 3 | Command number | 1 |
10660 * | 4 | Type of Command | 1 |
10661 * | 5 | Command Qualifier | 1 |
10662 */
10663 retrieveCommandDetails: function(length) {
10664 let GsmPDUHelper = this.context.GsmPDUHelper;
10665 let cmdDetails = {
10666 commandNumber: GsmPDUHelper.readHexOctet(),
10667 typeOfCommand: GsmPDUHelper.readHexOctet(),
10668 commandQualifier: GsmPDUHelper.readHexOctet()
10669 };
10670 return cmdDetails;
10671 },
10673 /**
10674 * Device Identities.
10675 *
10676 * | Byte | Description | Length |
10677 * | 1 | Device Identity Tag | 1 |
10678 * | 2 | Length = 02 | 1 |
10679 * | 3 | Source device Identity | 1 |
10680 * | 4 | Destination device Id | 1 |
10681 */
10682 retrieveDeviceId: function(length) {
10683 let GsmPDUHelper = this.context.GsmPDUHelper;
10684 let deviceId = {
10685 sourceId: GsmPDUHelper.readHexOctet(),
10686 destinationId: GsmPDUHelper.readHexOctet()
10687 };
10688 return deviceId;
10689 },
10691 /**
10692 * Alpha Identifier.
10693 *
10694 * | Byte | Description | Length |
10695 * | 1 | Alpha Identifier Tag | 1 |
10696 * | 2 ~ (Y-1)+2 | Length (X) | Y |
10697 * | (Y-1)+3 ~ | Alpha identfier | X |
10698 * | (Y-1)+X+2 | | |
10699 */
10700 retrieveAlphaId: function(length) {
10701 let alphaId = {
10702 identifier: this.context.ICCPDUHelper.readAlphaIdentifier(length)
10703 };
10704 return alphaId;
10705 },
10707 /**
10708 * Duration.
10709 *
10710 * | Byte | Description | Length |
10711 * | 1 | Response Length Tag | 1 |
10712 * | 2 | Lenth = 02 | 1 |
10713 * | 3 | Time unit | 1 |
10714 * | 4 | Time interval | 1 |
10715 */
10716 retrieveDuration: function(length) {
10717 let GsmPDUHelper = this.context.GsmPDUHelper;
10718 let duration = {
10719 timeUnit: GsmPDUHelper.readHexOctet(),
10720 timeInterval: GsmPDUHelper.readHexOctet(),
10721 };
10722 return duration;
10723 },
10725 /**
10726 * Address.
10727 *
10728 * | Byte | Description | Length |
10729 * | 1 | Alpha Identifier Tag | 1 |
10730 * | 2 ~ (Y-1)+2 | Length (X) | Y |
10731 * | (Y-1)+3 | TON and NPI | 1 |
10732 * | (Y-1)+4 ~ | Dialling number | X |
10733 * | (Y-1)+X+2 | | |
10734 */
10735 retrieveAddress: function(length) {
10736 let address = {
10737 number : this.context.ICCPDUHelper.readDiallingNumber(length)
10738 };
10739 return address;
10740 },
10742 /**
10743 * Text String.
10744 *
10745 * | Byte | Description | Length |
10746 * | 1 | Text String Tag | 1 |
10747 * | 2 ~ (Y-1)+2 | Length (X) | Y |
10748 * | (Y-1)+3 | Data coding scheme | 1 |
10749 * | (Y-1)+4~ | Text String | X |
10750 * | (Y-1)+X+2 | | |
10751 */
10752 retrieveTextString: function(length) {
10753 if (!length) {
10754 // null string.
10755 return {textString: null};
10756 }
10758 let GsmPDUHelper = this.context.GsmPDUHelper;
10759 let text = {
10760 codingScheme: GsmPDUHelper.readHexOctet()
10761 };
10763 length--; // -1 for the codingScheme.
10764 switch (text.codingScheme & 0x0f) {
10765 case STK_TEXT_CODING_GSM_7BIT_PACKED:
10766 text.textString = GsmPDUHelper.readSeptetsToString(length * 8 / 7, 0, 0, 0);
10767 break;
10768 case STK_TEXT_CODING_GSM_8BIT:
10769 text.textString =
10770 this.context.ICCPDUHelper.read8BitUnpackedToString(length);
10771 break;
10772 case STK_TEXT_CODING_UCS2:
10773 text.textString = GsmPDUHelper.readUCS2String(length);
10774 break;
10775 }
10776 return text;
10777 },
10779 /**
10780 * Tone.
10781 *
10782 * | Byte | Description | Length |
10783 * | 1 | Tone Tag | 1 |
10784 * | 2 | Lenth = 01 | 1 |
10785 * | 3 | Tone | 1 |
10786 */
10787 retrieveTone: function(length) {
10788 let tone = {
10789 tone: this.context.GsmPDUHelper.readHexOctet(),
10790 };
10791 return tone;
10792 },
10794 /**
10795 * Item.
10796 *
10797 * | Byte | Description | Length |
10798 * | 1 | Item Tag | 1 |
10799 * | 2 ~ (Y-1)+2 | Length (X) | Y |
10800 * | (Y-1)+3 | Identifier of item | 1 |
10801 * | (Y-1)+4 ~ | Text string of item | X |
10802 * | (Y-1)+X+2 | | |
10803 */
10804 retrieveItem: function(length) {
10805 // TS 102.223 ,clause 6.6.7 SET-UP MENU
10806 // If the "Item data object for item 1" is a null data object
10807 // (i.e. length = '00' and no value part), this is an indication to the ME
10808 // to remove the existing menu from the menu system in the ME.
10809 if (!length) {
10810 return null;
10811 }
10812 let item = {
10813 identifier: this.context.GsmPDUHelper.readHexOctet(),
10814 text: this.context.ICCPDUHelper.readAlphaIdentifier(length - 1)
10815 };
10816 return item;
10817 },
10819 /**
10820 * Item Identifier.
10821 *
10822 * | Byte | Description | Length |
10823 * | 1 | Item Identifier Tag | 1 |
10824 * | 2 | Lenth = 01 | 1 |
10825 * | 3 | Identifier of Item chosen | 1 |
10826 */
10827 retrieveItemId: function(length) {
10828 let itemId = {
10829 identifier: this.context.GsmPDUHelper.readHexOctet()
10830 };
10831 return itemId;
10832 },
10834 /**
10835 * Response Length.
10836 *
10837 * | Byte | Description | Length |
10838 * | 1 | Response Length Tag | 1 |
10839 * | 2 | Lenth = 02 | 1 |
10840 * | 3 | Minimum length of response | 1 |
10841 * | 4 | Maximum length of response | 1 |
10842 */
10843 retrieveResponseLength: function(length) {
10844 let GsmPDUHelper = this.context.GsmPDUHelper;
10845 let rspLength = {
10846 minLength : GsmPDUHelper.readHexOctet(),
10847 maxLength : GsmPDUHelper.readHexOctet()
10848 };
10849 return rspLength;
10850 },
10852 /**
10853 * File List.
10854 *
10855 * | Byte | Description | Length |
10856 * | 1 | File List Tag | 1 |
10857 * | 2 ~ (Y-1)+2 | Length (X) | Y |
10858 * | (Y-1)+3 | Number of files | 1 |
10859 * | (Y-1)+4 ~ | Files | X |
10860 * | (Y-1)+X+2 | | |
10861 */
10862 retrieveFileList: function(length) {
10863 let num = this.context.GsmPDUHelper.readHexOctet();
10864 let fileList = "";
10865 length--; // -1 for the num octet.
10866 for (let i = 0; i < 2 * length; i++) {
10867 // Didn't use readHexOctet here,
10868 // otherwise 0x00 will be "0", not "00"
10869 fileList += String.fromCharCode(this.context.Buf.readUint16());
10870 }
10871 return {
10872 fileList: fileList
10873 };
10874 },
10876 /**
10877 * Default Text.
10878 *
10879 * Same as Text String.
10880 */
10881 retrieveDefaultText: function(length) {
10882 return this.retrieveTextString(length);
10883 },
10885 /**
10886 * Event List.
10887 */
10888 retrieveEventList: function(length) {
10889 if (!length) {
10890 // null means an indication to ME to remove the existing list of events
10891 // in ME.
10892 return null;
10893 }
10895 let GsmPDUHelper = this.context.GsmPDUHelper;
10896 let eventList = [];
10897 for (let i = 0; i < length; i++) {
10898 eventList.push(GsmPDUHelper.readHexOctet());
10899 }
10900 return {
10901 eventList: eventList
10902 };
10903 },
10905 /**
10906 * Timer Identifier.
10907 *
10908 * | Byte | Description | Length |
10909 * | 1 | Timer Identifier Tag | 1 |
10910 * | 2 | Length = 01 | 1 |
10911 * | 3 | Timer Identifier | 1 |
10912 */
10913 retrieveTimerId: function(length) {
10914 let id = {
10915 timerId: this.context.GsmPDUHelper.readHexOctet()
10916 };
10917 return id;
10918 },
10920 /**
10921 * Timer Value.
10922 *
10923 * | Byte | Description | Length |
10924 * | 1 | Timer Value Tag | 1 |
10925 * | 2 | Length = 03 | 1 |
10926 * | 3 | Hour | 1 |
10927 * | 4 | Minute | 1 |
10928 * | 5 | Second | 1 |
10929 */
10930 retrieveTimerValue: function(length) {
10931 let GsmPDUHelper = this.context.GsmPDUHelper;
10932 let value = {
10933 timerValue: (GsmPDUHelper.readSwappedNibbleBcdNum(1) * 60 * 60) +
10934 (GsmPDUHelper.readSwappedNibbleBcdNum(1) * 60) +
10935 (GsmPDUHelper.readSwappedNibbleBcdNum(1))
10936 };
10937 return value;
10938 },
10940 /**
10941 * Immediate Response.
10942 *
10943 * | Byte | Description | Length |
10944 * | 1 | Immediate Response Tag | 1 |
10945 * | 2 | Length = 00 | 1 |
10946 */
10947 retrieveImmediaResponse: function(length) {
10948 return {};
10949 },
10951 /**
10952 * URL
10953 *
10954 * | Byte | Description | Length |
10955 * | 1 | URL Tag | 1 |
10956 * | 2 ~ (Y+1) | Length(X) | Y |
10957 * | (Y+2) ~ | URL | X |
10958 * | (Y+1+X) | | |
10959 */
10960 retrieveUrl: function(length) {
10961 let GsmPDUHelper = this.context.GsmPDUHelper;
10962 let s = "";
10963 for (let i = 0; i < length; i++) {
10964 s += String.fromCharCode(GsmPDUHelper.readHexOctet());
10965 }
10966 return {url: s};
10967 },
10969 /**
10970 * Next Action Indicator List.
10971 *
10972 * | Byte | Description | Length |
10973 * | 1 | Next Action tag | 1 |
10974 * | 1 | Length(X) | 1 |
10975 * | 3~ | Next Action List | X |
10976 * | 3+X-1 | | |
10977 */
10978 retrieveNextActionList: function(length) {
10979 let GsmPDUHelper = this.context.GsmPDUHelper;
10980 let nextActionList = [];
10981 for (let i = 0; i < length; i++) {
10982 nextActionList.push(GsmPDUHelper.readHexOctet());
10983 }
10984 return nextActionList;
10985 },
10987 searchForTag: function(tag, ctlvs) {
10988 let iter = Iterator(ctlvs);
10989 return this.searchForNextTag(tag, iter);
10990 },
10992 searchForNextTag: function(tag, iter) {
10993 for (let [index, ctlv] in iter) {
10994 if ((ctlv.tag & ~COMPREHENSIONTLV_FLAG_CR) == tag) {
10995 return ctlv;
10996 }
10997 }
10998 return null;
10999 },
11000 };
11001 StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_COMMAND_DETAILS] = function COMPREHENSIONTLV_TAG_COMMAND_DETAILS(length) {
11002 return this.retrieveCommandDetails(length);
11003 };
11004 StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_DEVICE_ID] = function COMPREHENSIONTLV_TAG_DEVICE_ID(length) {
11005 return this.retrieveDeviceId(length);
11006 };
11007 StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_ALPHA_ID] = function COMPREHENSIONTLV_TAG_ALPHA_ID(length) {
11008 return this.retrieveAlphaId(length);
11009 };
11010 StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_DURATION] = function COMPREHENSIONTLV_TAG_DURATION(length) {
11011 return this.retrieveDuration(length);
11012 };
11013 StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_ADDRESS] = function COMPREHENSIONTLV_TAG_ADDRESS(length) {
11014 return this.retrieveAddress(length);
11015 };
11016 StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_TEXT_STRING] = function COMPREHENSIONTLV_TAG_TEXT_STRING(length) {
11017 return this.retrieveTextString(length);
11018 };
11019 StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_TONE] = function COMPREHENSIONTLV_TAG_TONE(length) {
11020 return this.retrieveTone(length);
11021 };
11022 StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_ITEM] = function COMPREHENSIONTLV_TAG_ITEM(length) {
11023 return this.retrieveItem(length);
11024 };
11025 StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_ITEM_ID] = function COMPREHENSIONTLV_TAG_ITEM_ID(length) {
11026 return this.retrieveItemId(length);
11027 };
11028 StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_RESPONSE_LENGTH] = function COMPREHENSIONTLV_TAG_RESPONSE_LENGTH(length) {
11029 return this.retrieveResponseLength(length);
11030 };
11031 StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_FILE_LIST] = function COMPREHENSIONTLV_TAG_FILE_LIST(length) {
11032 return this.retrieveFileList(length);
11033 };
11034 StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_DEFAULT_TEXT] = function COMPREHENSIONTLV_TAG_DEFAULT_TEXT(length) {
11035 return this.retrieveDefaultText(length);
11036 };
11037 StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_EVENT_LIST] = function COMPREHENSIONTLV_TAG_EVENT_LIST(length) {
11038 return this.retrieveEventList(length);
11039 };
11040 StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_TIMER_IDENTIFIER] = function COMPREHENSIONTLV_TAG_TIMER_IDENTIFIER(length) {
11041 return this.retrieveTimerId(length);
11042 };
11043 StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_TIMER_VALUE] = function COMPREHENSIONTLV_TAG_TIMER_VALUE(length) {
11044 return this.retrieveTimerValue(length);
11045 };
11046 StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_IMMEDIATE_RESPONSE] = function COMPREHENSIONTLV_TAG_IMMEDIATE_RESPONSE(length) {
11047 return this.retrieveImmediaResponse(length);
11048 };
11049 StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_URL] = function COMPREHENSIONTLV_TAG_URL(length) {
11050 return this.retrieveUrl(length);
11051 };
11052 StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_NEXT_ACTION_IND] = function COMPREHENSIONTLV_TAG_NEXT_ACTION_IND(length) {
11053 return this.retrieveNextActionList(length);
11054 };
11056 function ComprehensionTlvHelperObject(aContext) {
11057 this.context = aContext;
11058 }
11059 ComprehensionTlvHelperObject.prototype = {
11060 context: null,
11062 /**
11063 * Decode raw data to a Comprehension-TLV.
11064 */
11065 decode: function() {
11066 let GsmPDUHelper = this.context.GsmPDUHelper;
11068 let hlen = 0; // For header(tag field + length field) length.
11069 let temp = GsmPDUHelper.readHexOctet();
11070 hlen++;
11072 // TS 101.220, clause 7.1.1
11073 let tag, cr;
11074 switch (temp) {
11075 // TS 101.220, clause 7.1.1
11076 case 0x0: // Not used.
11077 case 0xff: // Not used.
11078 case 0x80: // Reserved for future use.
11079 throw new Error("Invalid octet when parsing Comprehension TLV :" + temp);
11080 case 0x7f: // Tag is three byte format.
11081 // TS 101.220 clause 7.1.1.2.
11082 // | Byte 1 | Byte 2 | Byte 3 |
11083 // | | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | |
11084 // | 0x7f |CR | Tag Value |
11085 tag = (GsmPDUHelper.readHexOctet() << 8) | GsmPDUHelper.readHexOctet();
11086 hlen += 2;
11087 cr = (tag & 0x8000) !== 0;
11088 tag &= ~0x8000;
11089 break;
11090 default: // Tag is single byte format.
11091 tag = temp;
11092 // TS 101.220 clause 7.1.1.1.
11093 // | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 |
11094 // |CR | Tag Value |
11095 cr = (tag & 0x80) !== 0;
11096 tag &= ~0x80;
11097 }
11099 // TS 101.220 clause 7.1.2, Length Encoding.
11100 // Length | Byte 1 | Byte 2 | Byte 3 | Byte 4 |
11101 // 0 - 127 | 00 - 7f | N/A | N/A | N/A |
11102 // 128-255 | 81 | 80 - ff| N/A | N/A |
11103 // 256-65535| 82 | 0100 - ffff | N/A |
11104 // 65536- | 83 | 010000 - ffffff |
11105 // 16777215
11106 //
11107 // Length errors: TS 11.14, clause 6.10.6
11109 let length; // Data length.
11110 temp = GsmPDUHelper.readHexOctet();
11111 hlen++;
11112 if (temp < 0x80) {
11113 length = temp;
11114 } else if (temp == 0x81) {
11115 length = GsmPDUHelper.readHexOctet();
11116 hlen++;
11117 if (length < 0x80) {
11118 throw new Error("Invalid length in Comprehension TLV :" + length);
11119 }
11120 } else if (temp == 0x82) {
11121 length = (GsmPDUHelper.readHexOctet() << 8) | GsmPDUHelper.readHexOctet();
11122 hlen += 2;
11123 if (lenth < 0x0100) {
11124 throw new Error("Invalid length in 3-byte Comprehension TLV :" + length);
11125 }
11126 } else if (temp == 0x83) {
11127 length = (GsmPDUHelper.readHexOctet() << 16) |
11128 (GsmPDUHelper.readHexOctet() << 8) |
11129 GsmPDUHelper.readHexOctet();
11130 hlen += 3;
11131 if (length < 0x010000) {
11132 throw new Error("Invalid length in 4-byte Comprehension TLV :" + length);
11133 }
11134 } else {
11135 throw new Error("Invalid octet in Comprehension TLV :" + temp);
11136 }
11138 let ctlv = {
11139 tag: tag,
11140 length: length,
11141 value: this.context.StkProactiveCmdHelper.retrieve(tag, length),
11142 cr: cr,
11143 hlen: hlen
11144 };
11145 return ctlv;
11146 },
11148 decodeChunks: function(length) {
11149 let chunks = [];
11150 let index = 0;
11151 while (index < length) {
11152 let tlv = this.decode();
11153 chunks.push(tlv);
11154 index += tlv.length;
11155 index += tlv.hlen;
11156 }
11157 return chunks;
11158 },
11160 /**
11161 * Write Location Info Comprehension TLV.
11162 *
11163 * @param loc location Information.
11164 */
11165 writeLocationInfoTlv: function(loc) {
11166 let GsmPDUHelper = this.context.GsmPDUHelper;
11168 GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_LOCATION_INFO |
11169 COMPREHENSIONTLV_FLAG_CR);
11170 GsmPDUHelper.writeHexOctet(loc.gsmCellId > 0xffff ? 9 : 7);
11171 // From TS 11.14, clause 12.19
11172 // "The mobile country code (MCC), the mobile network code (MNC),
11173 // the location area code (LAC) and the cell ID are
11174 // coded as in TS 04.08."
11175 // And from TS 04.08 and TS 24.008,
11176 // the format is as follows:
11177 //
11178 // MCC = MCC_digit_1 + MCC_digit_2 + MCC_digit_3
11179 //
11180 // 8 7 6 5 4 3 2 1
11181 // +-------------+-------------+
11182 // | MCC digit 2 | MCC digit 1 | octet 2
11183 // | MNC digit 3 | MCC digit 3 | octet 3
11184 // | MNC digit 2 | MNC digit 1 | octet 4
11185 // +-------------+-------------+
11186 //
11187 // Also in TS 24.008
11188 // "However a network operator may decide to
11189 // use only two digits in the MNC in the LAI over the
11190 // radio interface. In this case, bits 5 to 8 of octet 3
11191 // shall be coded as '1111'".
11193 // MCC & MNC, 3 octets
11194 let mcc = loc.mcc, mnc;
11195 if (loc.mnc.length == 2) {
11196 mnc = "F" + loc.mnc;
11197 } else {
11198 mnc = loc.mnc[2] + loc.mnc[0] + loc.mnc[1];
11199 }
11200 GsmPDUHelper.writeSwappedNibbleBCD(mcc + mnc);
11202 // LAC, 2 octets
11203 GsmPDUHelper.writeHexOctet((loc.gsmLocationAreaCode >> 8) & 0xff);
11204 GsmPDUHelper.writeHexOctet(loc.gsmLocationAreaCode & 0xff);
11206 // Cell Id
11207 if (loc.gsmCellId > 0xffff) {
11208 // UMTS/WCDMA, gsmCellId is 28 bits.
11209 GsmPDUHelper.writeHexOctet((loc.gsmCellId >> 24) & 0xff);
11210 GsmPDUHelper.writeHexOctet((loc.gsmCellId >> 16) & 0xff);
11211 GsmPDUHelper.writeHexOctet((loc.gsmCellId >> 8) & 0xff);
11212 GsmPDUHelper.writeHexOctet(loc.gsmCellId & 0xff);
11213 } else {
11214 // GSM, gsmCellId is 16 bits.
11215 GsmPDUHelper.writeHexOctet((loc.gsmCellId >> 8) & 0xff);
11216 GsmPDUHelper.writeHexOctet(loc.gsmCellId & 0xff);
11217 }
11218 },
11220 /**
11221 * Given a geckoError string, this function translates it into cause value
11222 * and write the value into buffer.
11223 *
11224 * @param geckoError Error string that is passed to gecko.
11225 */
11226 writeCauseTlv: function(geckoError) {
11227 let GsmPDUHelper = this.context.GsmPDUHelper;
11229 let cause = -1;
11230 for (let errorNo in RIL_ERROR_TO_GECKO_ERROR) {
11231 if (geckoError == RIL_ERROR_TO_GECKO_ERROR[errorNo]) {
11232 cause = errorNo;
11233 break;
11234 }
11235 }
11236 cause = (cause == -1) ? ERROR_SUCCESS : cause;
11238 GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_CAUSE |
11239 COMPREHENSIONTLV_FLAG_CR);
11240 GsmPDUHelper.writeHexOctet(2); // For single cause value.
11242 // TS 04.08, clause 10.5.4.11: National standard code + user location.
11243 GsmPDUHelper.writeHexOctet(0x60);
11245 // TS 04.08, clause 10.5.4.11: ext bit = 1 + 7 bits for cause.
11246 // +-----------------+----------------------------------+
11247 // | Ext = 1 (1 bit) | Cause (7 bits) |
11248 // +-----------------+----------------------------------+
11249 GsmPDUHelper.writeHexOctet(0x80 | cause);
11250 },
11252 writeDateTimeZoneTlv: function(date) {
11253 let GsmPDUHelper = this.context.GsmPDUHelper;
11255 GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_DATE_TIME_ZONE);
11256 GsmPDUHelper.writeHexOctet(7);
11257 GsmPDUHelper.writeTimestamp(date);
11258 },
11260 writeLanguageTlv: function(language) {
11261 let GsmPDUHelper = this.context.GsmPDUHelper;
11263 GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_LANGUAGE);
11264 GsmPDUHelper.writeHexOctet(2);
11266 // ISO 639-1, Alpha-2 code
11267 // TS 123.038, clause 6.2.1, GSM 7 bit Default Alphabet
11268 GsmPDUHelper.writeHexOctet(
11269 PDU_NL_LOCKING_SHIFT_TABLES[PDU_NL_IDENTIFIER_DEFAULT].indexOf(language[0]));
11270 GsmPDUHelper.writeHexOctet(
11271 PDU_NL_LOCKING_SHIFT_TABLES[PDU_NL_IDENTIFIER_DEFAULT].indexOf(language[1]));
11272 },
11274 /**
11275 * Write Timer Value Comprehension TLV.
11276 *
11277 * @param seconds length of time during of the timer.
11278 * @param cr Comprehension Required or not
11279 */
11280 writeTimerValueTlv: function(seconds, cr) {
11281 let GsmPDUHelper = this.context.GsmPDUHelper;
11283 GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_TIMER_VALUE |
11284 (cr ? COMPREHENSIONTLV_FLAG_CR : 0));
11285 GsmPDUHelper.writeHexOctet(3);
11287 // TS 102.223, clause 8.38
11288 // +----------------+------------------+-------------------+
11289 // | hours (1 byte) | minutes (1 btye) | secounds (1 byte) |
11290 // +----------------+------------------+-------------------+
11291 GsmPDUHelper.writeSwappedNibbleBCDNum(Math.floor(seconds / 60 / 60));
11292 GsmPDUHelper.writeSwappedNibbleBCDNum(Math.floor(seconds / 60) % 60);
11293 GsmPDUHelper.writeSwappedNibbleBCDNum(seconds % 60);
11294 },
11296 getSizeOfLengthOctets: function(length) {
11297 if (length >= 0x10000) {
11298 return 4; // 0x83, len_1, len_2, len_3
11299 } else if (length >= 0x100) {
11300 return 3; // 0x82, len_1, len_2
11301 } else if (length >= 0x80) {
11302 return 2; // 0x81, len
11303 } else {
11304 return 1; // len
11305 }
11306 },
11308 writeLength: function(length) {
11309 let GsmPDUHelper = this.context.GsmPDUHelper;
11311 // TS 101.220 clause 7.1.2, Length Encoding.
11312 // Length | Byte 1 | Byte 2 | Byte 3 | Byte 4 |
11313 // 0 - 127 | 00 - 7f | N/A | N/A | N/A |
11314 // 128-255 | 81 | 80 - ff| N/A | N/A |
11315 // 256-65535| 82 | 0100 - ffff | N/A |
11316 // 65536- | 83 | 010000 - ffffff |
11317 // 16777215
11318 if (length < 0x80) {
11319 GsmPDUHelper.writeHexOctet(length);
11320 } else if (0x80 <= length && length < 0x100) {
11321 GsmPDUHelper.writeHexOctet(0x81);
11322 GsmPDUHelper.writeHexOctet(length);
11323 } else if (0x100 <= length && length < 0x10000) {
11324 GsmPDUHelper.writeHexOctet(0x82);
11325 GsmPDUHelper.writeHexOctet((length >> 8) & 0xff);
11326 GsmPDUHelper.writeHexOctet(length & 0xff);
11327 } else if (0x10000 <= length && length < 0x1000000) {
11328 GsmPDUHelper.writeHexOctet(0x83);
11329 GsmPDUHelper.writeHexOctet((length >> 16) & 0xff);
11330 GsmPDUHelper.writeHexOctet((length >> 8) & 0xff);
11331 GsmPDUHelper.writeHexOctet(length & 0xff);
11332 } else {
11333 throw new Error("Invalid length value :" + length);
11334 }
11335 },
11336 };
11338 function BerTlvHelperObject(aContext) {
11339 this.context = aContext;
11340 }
11341 BerTlvHelperObject.prototype = {
11342 context: null,
11344 /**
11345 * Decode Ber TLV.
11346 *
11347 * @param dataLen
11348 * The length of data in bytes.
11349 */
11350 decode: function(dataLen) {
11351 let GsmPDUHelper = this.context.GsmPDUHelper;
11353 let hlen = 0;
11354 let tag = GsmPDUHelper.readHexOctet();
11355 hlen++;
11357 // The length is coded onto 1 or 2 bytes.
11358 // Length | Byte 1 | Byte 2
11359 // 0 - 127 | length ('00' to '7f') | N/A
11360 // 128 - 255 | '81' | length ('80' to 'ff')
11361 let length;
11362 let temp = GsmPDUHelper.readHexOctet();
11363 hlen++;
11364 if (temp < 0x80) {
11365 length = temp;
11366 } else if (temp === 0x81) {
11367 length = GsmPDUHelper.readHexOctet();
11368 hlen++;
11369 if (length < 0x80) {
11370 throw new Error("Invalid length " + length);
11371 }
11372 } else {
11373 throw new Error("Invalid length octet " + temp);
11374 }
11376 // Header + body length check.
11377 if (dataLen - hlen !== length) {
11378 throw new Error("Unexpected BerTlvHelper value length!!");
11379 }
11381 let method = this[tag];
11382 if (typeof method != "function") {
11383 throw new Error("Unknown Ber tag 0x" + tag.toString(16));
11384 }
11386 let value = method.call(this, length);
11388 return {
11389 tag: tag,
11390 length: length,
11391 value: value
11392 };
11393 },
11395 /**
11396 * Process the value part for FCP template TLV.
11397 *
11398 * @param length
11399 * The length of data in bytes.
11400 */
11401 processFcpTemplate: function(length) {
11402 let tlvs = this.decodeChunks(length);
11403 return tlvs;
11404 },
11406 /**
11407 * Process the value part for proactive command TLV.
11408 *
11409 * @param length
11410 * The length of data in bytes.
11411 */
11412 processProactiveCommand: function(length) {
11413 let ctlvs = this.context.ComprehensionTlvHelper.decodeChunks(length);
11414 return ctlvs;
11415 },
11417 /**
11418 * Decode raw data to a Ber-TLV.
11419 */
11420 decodeInnerTlv: function() {
11421 let GsmPDUHelper = this.context.GsmPDUHelper;
11422 let tag = GsmPDUHelper.readHexOctet();
11423 let length = GsmPDUHelper.readHexOctet();
11424 return {
11425 tag: tag,
11426 length: length,
11427 value: this.retrieve(tag, length)
11428 };
11429 },
11431 decodeChunks: function(length) {
11432 let chunks = [];
11433 let index = 0;
11434 while (index < length) {
11435 let tlv = this.decodeInnerTlv();
11436 if (tlv.value) {
11437 chunks.push(tlv);
11438 }
11439 index += tlv.length;
11440 // tag + length fields consume 2 bytes.
11441 index += 2;
11442 }
11443 return chunks;
11444 },
11446 retrieve: function(tag, length) {
11447 let method = this[tag];
11448 if (typeof method != "function") {
11449 if (DEBUG) {
11450 this.context.debug("Unknown Ber tag : 0x" + tag.toString(16));
11451 }
11452 let Buf = this.context.Buf;
11453 Buf.seekIncoming(length * Buf.PDU_HEX_OCTET_SIZE);
11454 return null;
11455 }
11456 return method.call(this, length);
11457 },
11459 /**
11460 * File Size Data.
11461 *
11462 * | Byte | Description | Length |
11463 * | 1 | Tag | 1 |
11464 * | 2 | Length | 1 |
11465 * | 3 to X+24 | Number of allocated data bytes in the file | X |
11466 * | | , excluding structural information | |
11467 */
11468 retrieveFileSizeData: function(length) {
11469 let GsmPDUHelper = this.context.GsmPDUHelper;
11470 let fileSizeData = 0;
11471 for (let i = 0; i < length; i++) {
11472 fileSizeData = fileSizeData << 8;
11473 fileSizeData += GsmPDUHelper.readHexOctet();
11474 }
11476 return {fileSizeData: fileSizeData};
11477 },
11479 /**
11480 * File Descriptor.
11481 *
11482 * | Byte | Description | Length |
11483 * | 1 | Tag | 1 |
11484 * | 2 | Length | 1 |
11485 * | 3 | File descriptor byte | 1 |
11486 * | 4 | Data coding byte | 1 |
11487 * | 5 ~ 6 | Record length | 2 |
11488 * | 7 | Number of records | 1 |
11489 */
11490 retrieveFileDescriptor: function(length) {
11491 let GsmPDUHelper = this.context.GsmPDUHelper;
11492 let fileDescriptorByte = GsmPDUHelper.readHexOctet();
11493 let dataCodingByte = GsmPDUHelper.readHexOctet();
11494 // See TS 102 221 Table 11.5, we only care the least 3 bits for the
11495 // structure of file.
11496 let fileStructure = fileDescriptorByte & 0x07;
11498 let fileDescriptor = {
11499 fileStructure: fileStructure
11500 };
11501 // byte 5 ~ 7 are mandatory for linear fixed and cyclic files, otherwise
11502 // they are not applicable.
11503 if (fileStructure === UICC_EF_STRUCTURE[EF_TYPE_LINEAR_FIXED] ||
11504 fileStructure === UICC_EF_STRUCTURE[EF_TYPE_CYCLIC]) {
11505 fileDescriptor.recordLength = (GsmPDUHelper.readHexOctet() << 8) +
11506 GsmPDUHelper.readHexOctet();
11507 fileDescriptor.numOfRecords = GsmPDUHelper.readHexOctet();
11508 }
11510 return fileDescriptor;
11511 },
11513 /**
11514 * File identifier.
11515 *
11516 * | Byte | Description | Length |
11517 * | 1 | Tag | 1 |
11518 * | 2 | Length | 1 |
11519 * | 3 ~ 4 | File identifier | 2 |
11520 */
11521 retrieveFileIdentifier: function(length) {
11522 let GsmPDUHelper = this.context.GsmPDUHelper;
11523 return {fileId : (GsmPDUHelper.readHexOctet() << 8) +
11524 GsmPDUHelper.readHexOctet()};
11525 },
11527 searchForNextTag: function(tag, iter) {
11528 for (let [index, tlv] in iter) {
11529 if (tlv.tag === tag) {
11530 return tlv;
11531 }
11532 }
11533 return null;
11534 }
11535 };
11536 BerTlvHelperObject.prototype[BER_FCP_TEMPLATE_TAG] = function BER_FCP_TEMPLATE_TAG(length) {
11537 return this.processFcpTemplate(length);
11538 };
11539 BerTlvHelperObject.prototype[BER_PROACTIVE_COMMAND_TAG] = function BER_PROACTIVE_COMMAND_TAG(length) {
11540 return this.processProactiveCommand(length);
11541 };
11542 BerTlvHelperObject.prototype[BER_FCP_FILE_SIZE_DATA_TAG] = function BER_FCP_FILE_SIZE_DATA_TAG(length) {
11543 return this.retrieveFileSizeData(length);
11544 };
11545 BerTlvHelperObject.prototype[BER_FCP_FILE_DESCRIPTOR_TAG] = function BER_FCP_FILE_DESCRIPTOR_TAG(length) {
11546 return this.retrieveFileDescriptor(length);
11547 };
11548 BerTlvHelperObject.prototype[BER_FCP_FILE_IDENTIFIER_TAG] = function BER_FCP_FILE_IDENTIFIER_TAG(length) {
11549 return this.retrieveFileIdentifier(length);
11550 };
11552 /**
11553 * ICC Helper for getting EF path.
11554 */
11555 function ICCFileHelperObject(aContext) {
11556 this.context = aContext;
11557 }
11558 ICCFileHelperObject.prototype = {
11559 context: null,
11561 /**
11562 * This function handles only EFs that are common to RUIM, SIM, USIM
11563 * and other types of ICC cards.
11564 */
11565 getCommonEFPath: function(fileId) {
11566 switch (fileId) {
11567 case ICC_EF_ICCID:
11568 return EF_PATH_MF_SIM;
11569 case ICC_EF_ADN:
11570 return EF_PATH_MF_SIM + EF_PATH_DF_TELECOM;
11571 case ICC_EF_PBR:
11572 return EF_PATH_MF_SIM + EF_PATH_DF_TELECOM + EF_PATH_DF_PHONEBOOK;
11573 }
11574 return null;
11575 },
11577 /**
11578 * This function handles EFs for SIM.
11579 */
11580 getSimEFPath: function(fileId) {
11581 switch (fileId) {
11582 case ICC_EF_FDN:
11583 case ICC_EF_MSISDN:
11584 case ICC_EF_SMS:
11585 return EF_PATH_MF_SIM + EF_PATH_DF_TELECOM;
11586 case ICC_EF_AD:
11587 case ICC_EF_MBDN:
11588 case ICC_EF_MWIS:
11589 case ICC_EF_PLMNsel:
11590 case ICC_EF_SPN:
11591 case ICC_EF_SPDI:
11592 case ICC_EF_SST:
11593 case ICC_EF_PHASE:
11594 case ICC_EF_CBMI:
11595 case ICC_EF_CBMID:
11596 case ICC_EF_CBMIR:
11597 case ICC_EF_OPL:
11598 case ICC_EF_PNN:
11599 return EF_PATH_MF_SIM + EF_PATH_DF_GSM;
11600 default:
11601 return null;
11602 }
11603 },
11605 /**
11606 * This function handles EFs for USIM.
11607 */
11608 getUSimEFPath: function(fileId) {
11609 switch (fileId) {
11610 case ICC_EF_AD:
11611 case ICC_EF_FDN:
11612 case ICC_EF_MBDN:
11613 case ICC_EF_MWIS:
11614 case ICC_EF_UST:
11615 case ICC_EF_MSISDN:
11616 case ICC_EF_SPN:
11617 case ICC_EF_SPDI:
11618 case ICC_EF_CBMI:
11619 case ICC_EF_CBMID:
11620 case ICC_EF_CBMIR:
11621 case ICC_EF_OPL:
11622 case ICC_EF_PNN:
11623 case ICC_EF_SMS:
11624 return EF_PATH_MF_SIM + EF_PATH_ADF_USIM;
11625 default:
11626 // The file ids in USIM phone book entries are decided by the
11627 // card manufacturer. So if we don't match any of the cases
11628 // above and if its a USIM return the phone book path.
11629 return EF_PATH_MF_SIM + EF_PATH_DF_TELECOM + EF_PATH_DF_PHONEBOOK;
11630 }
11631 },
11633 /**
11634 * This function handles EFs for RUIM
11635 */
11636 getRuimEFPath: function(fileId) {
11637 switch(fileId) {
11638 case ICC_EF_CSIM_IMSI_M:
11639 case ICC_EF_CSIM_CDMAHOME:
11640 case ICC_EF_CSIM_CST:
11641 case ICC_EF_CSIM_SPN:
11642 return EF_PATH_MF_SIM + EF_PATH_DF_CDMA;
11643 case ICC_EF_FDN:
11644 return EF_PATH_MF_SIM + EF_PATH_DF_TELECOM;
11645 default:
11646 return null;
11647 }
11648 },
11650 /**
11651 * Helper function for getting the pathId for the specific ICC record
11652 * depeding on which type of ICC card we are using.
11653 *
11654 * @param fileId
11655 * File id.
11656 * @return The pathId or null in case of an error or invalid input.
11657 */
11658 getEFPath: function(fileId) {
11659 let appType = this.context.RIL.appType;
11660 if (appType == null) {
11661 return null;
11662 }
11664 let path = this.getCommonEFPath(fileId);
11665 if (path) {
11666 return path;
11667 }
11669 switch (appType) {
11670 case CARD_APPTYPE_SIM:
11671 return this.getSimEFPath(fileId);
11672 case CARD_APPTYPE_USIM:
11673 return this.getUSimEFPath(fileId);
11674 case CARD_APPTYPE_RUIM:
11675 return this.getRuimEFPath(fileId);
11676 default:
11677 return null;
11678 }
11679 }
11680 };
11682 /**
11683 * Helper for ICC IO functionalities.
11684 */
11685 function ICCIOHelperObject(aContext) {
11686 this.context = aContext;
11687 }
11688 ICCIOHelperObject.prototype = {
11689 context: null,
11691 /**
11692 * Load EF with type 'Linear Fixed'.
11693 *
11694 * @param fileId
11695 * The file to operate on, one of the ICC_EF_* constants.
11696 * @param recordNumber [optional]
11697 * The number of the record shall be loaded.
11698 * @param recordSize [optional]
11699 * The size of the record.
11700 * @param callback [optional]
11701 * The callback function shall be called when the record(s) is read.
11702 * @param onerror [optional]
11703 * The callback function shall be called when failure.
11704 */
11705 loadLinearFixedEF: function(options) {
11706 let cb;
11707 let readRecord = (function(options) {
11708 options.command = ICC_COMMAND_READ_RECORD;
11709 options.p1 = options.recordNumber || 1; // Record number
11710 options.p2 = READ_RECORD_ABSOLUTE_MODE;
11711 options.p3 = options.recordSize;
11712 options.callback = cb || options.callback;
11713 this.context.RIL.iccIO(options);
11714 }).bind(this);
11716 options.type = EF_TYPE_LINEAR_FIXED;
11717 options.pathId = this.context.ICCFileHelper.getEFPath(options.fileId);
11718 if (options.recordSize) {
11719 readRecord(options);
11720 return;
11721 }
11723 cb = options.callback;
11724 options.callback = readRecord;
11725 this.getResponse(options);
11726 },
11728 /**
11729 * Load next record from current record Id.
11730 */
11731 loadNextRecord: function(options) {
11732 options.p1++;
11733 this.context.RIL.iccIO(options);
11734 },
11736 /**
11737 * Update EF with type 'Linear Fixed'.
11738 *
11739 * @param fileId
11740 * The file to operate on, one of the ICC_EF_* constants.
11741 * @param recordNumber
11742 * The number of the record shall be updated.
11743 * @param dataWriter [optional]
11744 * The function for writing string parameter for the ICC_COMMAND_UPDATE_RECORD.
11745 * @param pin2 [optional]
11746 * PIN2 is required when updating ICC_EF_FDN.
11747 * @param callback [optional]
11748 * The callback function shall be called when the record is updated.
11749 * @param onerror [optional]
11750 * The callback function shall be called when failure.
11751 */
11752 updateLinearFixedEF: function(options) {
11753 if (!options.fileId || !options.recordNumber) {
11754 throw new Error("Unexpected fileId " + options.fileId +
11755 " or recordNumber " + options.recordNumber);
11756 }
11758 options.type = EF_TYPE_LINEAR_FIXED;
11759 options.pathId = this.context.ICCFileHelper.getEFPath(options.fileId);
11760 let cb = options.callback;
11761 options.callback = function callback(options) {
11762 options.callback = cb;
11763 options.command = ICC_COMMAND_UPDATE_RECORD;
11764 options.p1 = options.recordNumber;
11765 options.p2 = READ_RECORD_ABSOLUTE_MODE;
11766 options.p3 = options.recordSize;
11767 this.context.RIL.iccIO(options);
11768 }.bind(this);
11769 this.getResponse(options);
11770 },
11772 /**
11773 * Load EF with type 'Transparent'.
11774 *
11775 * @param fileId
11776 * The file to operate on, one of the ICC_EF_* constants.
11777 * @param callback [optional]
11778 * The callback function shall be called when the record(s) is read.
11779 * @param onerror [optional]
11780 * The callback function shall be called when failure.
11781 */
11782 loadTransparentEF: function(options) {
11783 options.type = EF_TYPE_TRANSPARENT;
11784 let cb = options.callback;
11785 options.callback = function callback(options) {
11786 options.callback = cb;
11787 options.command = ICC_COMMAND_READ_BINARY;
11788 options.p3 = options.fileSize;
11789 this.context.RIL.iccIO(options);
11790 }.bind(this);
11791 this.getResponse(options);
11792 },
11794 /**
11795 * Use ICC_COMMAND_GET_RESPONSE to query the EF.
11796 *
11797 * @param fileId
11798 * The file to operate on, one of the ICC_EF_* constants.
11799 */
11800 getResponse: function(options) {
11801 options.command = ICC_COMMAND_GET_RESPONSE;
11802 options.pathId = options.pathId ||
11803 this.context.ICCFileHelper.getEFPath(options.fileId);
11804 if (!options.pathId) {
11805 throw new Error("Unknown pathId for " + options.fileId.toString(16));
11806 }
11807 options.p1 = 0; // For GET_RESPONSE, p1 = 0
11808 options.p2 = 0; // For GET_RESPONSE, p2 = 0
11809 options.p3 = GET_RESPONSE_EF_SIZE_BYTES;
11810 this.context.RIL.iccIO(options);
11811 },
11813 /**
11814 * Process ICC I/O response.
11815 */
11816 processICCIO: function(options) {
11817 let func = this[options.command];
11818 func.call(this, options);
11819 },
11821 /**
11822 * Process a ICC_COMMAND_GET_RESPONSE type command for REQUEST_SIM_IO.
11823 */
11824 processICCIOGetResponse: function(options) {
11825 let Buf = this.context.Buf;
11826 let strLen = Buf.readInt32();
11828 let peek = this.context.GsmPDUHelper.readHexOctet();
11829 Buf.seekIncoming(-1 * Buf.PDU_HEX_OCTET_SIZE);
11830 if (peek === BER_FCP_TEMPLATE_TAG) {
11831 this.processUSimGetResponse(options, strLen / 2);
11832 } else {
11833 this.processSimGetResponse(options);
11834 }
11835 Buf.readStringDelimiter(strLen);
11837 if (options.callback) {
11838 options.callback(options);
11839 }
11840 },
11842 /**
11843 * Helper function for processing USIM get response.
11844 */
11845 processUSimGetResponse: function(options, octetLen) {
11846 let BerTlvHelper = this.context.BerTlvHelper;
11848 let berTlv = BerTlvHelper.decode(octetLen);
11849 // See TS 102 221 Table 11.4 for the content order of getResponse.
11850 let iter = Iterator(berTlv.value);
11851 let tlv = BerTlvHelper.searchForNextTag(BER_FCP_FILE_DESCRIPTOR_TAG,
11852 iter);
11853 if (!tlv || (tlv.value.fileStructure !== UICC_EF_STRUCTURE[options.type])) {
11854 throw new Error("Expected EF type " + UICC_EF_STRUCTURE[options.type] +
11855 " but read " + tlv.value.fileStructure);
11856 }
11858 if (tlv.value.fileStructure === UICC_EF_STRUCTURE[EF_TYPE_LINEAR_FIXED] ||
11859 tlv.value.fileStructure === UICC_EF_STRUCTURE[EF_TYPE_CYCLIC]) {
11860 options.recordSize = tlv.value.recordLength;
11861 options.totalRecords = tlv.value.numOfRecords;
11862 }
11864 tlv = BerTlvHelper.searchForNextTag(BER_FCP_FILE_IDENTIFIER_TAG, iter);
11865 if (!tlv || (tlv.value.fileId !== options.fileId)) {
11866 throw new Error("Expected file ID " + options.fileId.toString(16) +
11867 " but read " + fileId.toString(16));
11868 }
11870 tlv = BerTlvHelper.searchForNextTag(BER_FCP_FILE_SIZE_DATA_TAG, iter);
11871 if (!tlv) {
11872 throw new Error("Unexpected file size data");
11873 }
11874 options.fileSize = tlv.value.fileSizeData;
11875 },
11877 /**
11878 * Helper function for processing SIM get response.
11879 */
11880 processSimGetResponse: function(options) {
11881 let Buf = this.context.Buf;
11882 let GsmPDUHelper = this.context.GsmPDUHelper;
11884 // The format is from TS 51.011, clause 9.2.1
11886 // Skip RFU, data[0] data[1].
11887 Buf.seekIncoming(2 * Buf.PDU_HEX_OCTET_SIZE);
11889 // File size, data[2], data[3]
11890 options.fileSize = (GsmPDUHelper.readHexOctet() << 8) |
11891 GsmPDUHelper.readHexOctet();
11893 // 2 bytes File id. data[4], data[5]
11894 let fileId = (GsmPDUHelper.readHexOctet() << 8) |
11895 GsmPDUHelper.readHexOctet();
11896 if (fileId != options.fileId) {
11897 throw new Error("Expected file ID " + options.fileId.toString(16) +
11898 " but read " + fileId.toString(16));
11899 }
11901 // Type of file, data[6]
11902 let fileType = GsmPDUHelper.readHexOctet();
11903 if (fileType != TYPE_EF) {
11904 throw new Error("Unexpected file type " + fileType);
11905 }
11907 // Skip 1 byte RFU, data[7],
11908 // 3 bytes Access conditions, data[8] data[9] data[10],
11909 // 1 byte File status, data[11],
11910 // 1 byte Length of the following data, data[12].
11911 Buf.seekIncoming(((RESPONSE_DATA_STRUCTURE - RESPONSE_DATA_FILE_TYPE - 1) *
11912 Buf.PDU_HEX_OCTET_SIZE));
11914 // Read Structure of EF, data[13]
11915 let efType = GsmPDUHelper.readHexOctet();
11916 if (efType != options.type) {
11917 throw new Error("Expected EF type " + options.type + " but read " + efType);
11918 }
11920 // TODO: Bug 952025.
11921 // Length of a record, data[14].
11922 // Only available for LINEAR_FIXED and CYCLIC.
11923 if (efType == EF_TYPE_LINEAR_FIXED || efType == EF_TYPE_CYCLIC) {
11924 options.recordSize = GsmPDUHelper.readHexOctet();
11925 options.totalRecords = options.fileSize / options.recordSize;
11926 } else {
11927 Buf.seekIncoming(1 * Buf.PDU_HEX_OCTET_SIZE);
11928 }
11929 },
11931 /**
11932 * Process a ICC_COMMAND_READ_RECORD type command for REQUEST_SIM_IO.
11933 */
11934 processICCIOReadRecord: function(options) {
11935 if (options.callback) {
11936 options.callback(options);
11937 }
11938 },
11940 /**
11941 * Process a ICC_COMMAND_READ_BINARY type command for REQUEST_SIM_IO.
11942 */
11943 processICCIOReadBinary: function(options) {
11944 if (options.callback) {
11945 options.callback(options);
11946 }
11947 },
11949 /**
11950 * Process a ICC_COMMAND_UPDATE_RECORD type command for REQUEST_SIM_IO.
11951 */
11952 processICCIOUpdateRecord: function(options) {
11953 if (options.callback) {
11954 options.callback(options);
11955 }
11956 },
11958 /**
11959 * Process ICC IO error.
11960 */
11961 processICCIOError: function(options) {
11962 let requestError = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError];
11963 if (DEBUG) {
11964 // See GSM11.11, TS 51.011 clause 9.4, and ISO 7816-4 for the error
11965 // description.
11966 let errorMsg = "ICC I/O Error code " + requestError +
11967 " EF id = " + options.fileId.toString(16) +
11968 " command = " + options.command.toString(16);
11969 if (options.sw1 && options.sw2) {
11970 errorMsg += "(" + options.sw1.toString(16) +
11971 "/" + options.sw2.toString(16) + ")";
11972 }
11973 this.context.debug(errorMsg);
11974 }
11975 if (options.onerror) {
11976 options.onerror(requestError);
11977 }
11978 },
11979 };
11980 ICCIOHelperObject.prototype[ICC_COMMAND_SEEK] = null;
11981 ICCIOHelperObject.prototype[ICC_COMMAND_READ_BINARY] = function ICC_COMMAND_READ_BINARY(options) {
11982 this.processICCIOReadBinary(options);
11983 };
11984 ICCIOHelperObject.prototype[ICC_COMMAND_READ_RECORD] = function ICC_COMMAND_READ_RECORD(options) {
11985 this.processICCIOReadRecord(options);
11986 };
11987 ICCIOHelperObject.prototype[ICC_COMMAND_GET_RESPONSE] = function ICC_COMMAND_GET_RESPONSE(options) {
11988 this.processICCIOGetResponse(options);
11989 };
11990 ICCIOHelperObject.prototype[ICC_COMMAND_UPDATE_BINARY] = null;
11991 ICCIOHelperObject.prototype[ICC_COMMAND_UPDATE_RECORD] = function ICC_COMMAND_UPDATE_RECORD(options) {
11992 this.processICCIOUpdateRecord(options);
11993 };
11995 /**
11996 * Helper for ICC records.
11997 */
11998 function ICCRecordHelperObject(aContext) {
11999 this.context = aContext;
12000 }
12001 ICCRecordHelperObject.prototype = {
12002 context: null,
12004 /**
12005 * Fetch ICC records.
12006 */
12007 fetchICCRecords: function() {
12008 switch (this.context.RIL.appType) {
12009 case CARD_APPTYPE_SIM:
12010 case CARD_APPTYPE_USIM:
12011 this.context.SimRecordHelper.fetchSimRecords();
12012 break;
12013 case CARD_APPTYPE_RUIM:
12014 this.context.RuimRecordHelper.fetchRuimRecords();
12015 break;
12016 }
12017 },
12019 /**
12020 * Read the ICCID.
12021 */
12022 readICCID: function() {
12023 function callback() {
12024 let Buf = this.context.Buf;
12025 let RIL = this.context.RIL;
12027 let strLen = Buf.readInt32();
12028 let octetLen = strLen / 2;
12029 RIL.iccInfo.iccid =
12030 this.context.GsmPDUHelper.readSwappedNibbleBcdString(octetLen, true);
12031 // Consumes the remaining buffer if any.
12032 let unReadBuffer = this.context.Buf.getReadAvailable() -
12033 this.context.Buf.PDU_HEX_OCTET_SIZE;
12034 if (unReadBuffer > 0) {
12035 this.context.Buf.seekIncoming(unReadBuffer);
12036 }
12037 Buf.readStringDelimiter(strLen);
12039 if (DEBUG) this.context.debug("ICCID: " + RIL.iccInfo.iccid);
12040 if (RIL.iccInfo.iccid) {
12041 this.context.ICCUtilsHelper.handleICCInfoChange();
12042 RIL.reportStkServiceIsRunning();
12043 }
12044 }
12046 this.context.ICCIOHelper.loadTransparentEF({
12047 fileId: ICC_EF_ICCID,
12048 callback: callback.bind(this)
12049 });
12050 },
12052 /**
12053 * Read ICC ADN like EF, i.e. EF_ADN, EF_FDN.
12054 *
12055 * @param fileId EF id of the ADN or FDN.
12056 * @param onsuccess Callback to be called when success.
12057 * @param onerror Callback to be called when error.
12058 */
12059 readADNLike: function(fileId, onsuccess, onerror) {
12060 let ICCIOHelper = this.context.ICCIOHelper;
12062 function callback(options) {
12063 let contact =
12064 this.context.ICCPDUHelper.readAlphaIdDiallingNumber(options.recordSize);
12065 if (contact) {
12066 contact.recordId = options.p1;
12067 contacts.push(contact);
12068 }
12070 if (options.p1 < options.totalRecords) {
12071 ICCIOHelper.loadNextRecord(options);
12072 } else {
12073 if (DEBUG) {
12074 for (let i = 0; i < contacts.length; i++) {
12075 this.context.debug("contact [" + i + "] " +
12076 JSON.stringify(contacts[i]));
12077 }
12078 }
12079 if (onsuccess) {
12080 onsuccess(contacts);
12081 }
12082 }
12083 }
12085 let contacts = [];
12086 ICCIOHelper.loadLinearFixedEF({fileId: fileId,
12087 callback: callback.bind(this),
12088 onerror: onerror});
12089 },
12091 /**
12092 * Update ICC ADN like EFs, like EF_ADN, EF_FDN.
12093 *
12094 * @param fileId EF id of the ADN or FDN.
12095 * @param contact The contact will be updated. (Shall have recordId property)
12096 * @param pin2 PIN2 is required when updating ICC_EF_FDN.
12097 * @param onsuccess Callback to be called when success.
12098 * @param onerror Callback to be called when error.
12099 */
12100 updateADNLike: function(fileId, contact, pin2, onsuccess, onerror) {
12101 function dataWriter(recordSize) {
12102 this.context.ICCPDUHelper.writeAlphaIdDiallingNumber(recordSize,
12103 contact.alphaId,
12104 contact.number);
12105 }
12107 function callback(options) {
12108 if (onsuccess) {
12109 onsuccess();
12110 }
12111 }
12113 if (!contact || !contact.recordId) {
12114 if (onerror) onerror(GECKO_ERROR_INVALID_PARAMETER);
12115 return;
12116 }
12118 this.context.ICCIOHelper.updateLinearFixedEF({
12119 fileId: fileId,
12120 recordNumber: contact.recordId,
12121 dataWriter: dataWriter.bind(this),
12122 pin2: pin2,
12123 callback: callback.bind(this),
12124 onerror: onerror
12125 });
12126 },
12128 /**
12129 * Read USIM/RUIM Phonebook.
12130 *
12131 * @param onsuccess Callback to be called when success.
12132 * @param onerror Callback to be called when error.
12133 */
12134 readPBR: function(onsuccess, onerror) {
12135 let Buf = this.context.Buf;
12136 let GsmPDUHelper = this.context.GsmPDUHelper;
12137 let ICCIOHelper = this.context.ICCIOHelper;
12138 let ICCUtilsHelper = this.context.ICCUtilsHelper;
12139 let RIL = this.context.RIL;
12141 function callback(options) {
12142 let strLen = Buf.readInt32();
12143 let octetLen = strLen / 2, readLen = 0;
12145 let pbrTlvs = [];
12146 while (readLen < octetLen) {
12147 let tag = GsmPDUHelper.readHexOctet();
12148 if (tag == 0xff) {
12149 readLen++;
12150 Buf.seekIncoming((octetLen - readLen) * Buf.PDU_HEX_OCTET_SIZE);
12151 break;
12152 }
12154 let tlvLen = GsmPDUHelper.readHexOctet();
12155 let tlvs = ICCUtilsHelper.decodeSimTlvs(tlvLen);
12156 pbrTlvs.push({tag: tag,
12157 length: tlvLen,
12158 value: tlvs});
12160 readLen += tlvLen + 2; // +2 for tag and tlvLen
12161 }
12162 Buf.readStringDelimiter(strLen);
12164 if (pbrTlvs.length > 0) {
12165 let pbr = ICCUtilsHelper.parsePbrTlvs(pbrTlvs);
12166 // EF_ADN is mandatory if and only if DF_PHONEBOOK is present.
12167 if (!pbr.adn) {
12168 if (onerror) onerror("Cannot access ADN.");
12169 return;
12170 }
12171 pbrs.push(pbr);
12172 }
12174 if (options.p1 < options.totalRecords) {
12175 ICCIOHelper.loadNextRecord(options);
12176 } else {
12177 if (onsuccess) {
12178 RIL.iccInfoPrivate.pbrs = pbrs;
12179 onsuccess(pbrs);
12180 }
12181 }
12182 }
12184 if (RIL.iccInfoPrivate.pbrs) {
12185 onsuccess(RIL.iccInfoPrivate.pbrs);
12186 return;
12187 }
12189 let pbrs = [];
12190 ICCIOHelper.loadLinearFixedEF({fileId : ICC_EF_PBR,
12191 callback: callback.bind(this),
12192 onerror: onerror});
12193 },
12195 /**
12196 * Cache EF_IAP record size.
12197 */
12198 _iapRecordSize: null,
12200 /**
12201 * Read ICC EF_IAP. (Index Administration Phonebook)
12202 *
12203 * @see TS 131.102, clause 4.4.2.2
12204 *
12205 * @param fileId EF id of the IAP.
12206 * @param recordNumber The number of the record shall be loaded.
12207 * @param onsuccess Callback to be called when success.
12208 * @param onerror Callback to be called when error.
12209 */
12210 readIAP: function(fileId, recordNumber, onsuccess, onerror) {
12211 function callback(options) {
12212 let Buf = this.context.Buf;
12213 let strLen = Buf.readInt32();
12214 let octetLen = strLen / 2;
12215 this._iapRecordSize = options.recordSize;
12217 let iap = this.context.GsmPDUHelper.readHexOctetArray(octetLen);
12218 Buf.readStringDelimiter(strLen);
12220 if (onsuccess) {
12221 onsuccess(iap);
12222 }
12223 }
12225 this.context.ICCIOHelper.loadLinearFixedEF({
12226 fileId: fileId,
12227 recordNumber: recordNumber,
12228 recordSize: this._iapRecordSize,
12229 callback: callback.bind(this),
12230 onerror: onerror
12231 });
12232 },
12234 /**
12235 * Update USIM/RUIM Phonebook EF_IAP.
12236 *
12237 * @see TS 131.102, clause 4.4.2.13
12238 *
12239 * @param fileId EF id of the IAP.
12240 * @param recordNumber The identifier of the record shall be updated.
12241 * @param iap The IAP value to be written.
12242 * @param onsuccess Callback to be called when success.
12243 * @param onerror Callback to be called when error.
12244 */
12245 updateIAP: function(fileId, recordNumber, iap, onsuccess, onerror) {
12246 let dataWriter = function dataWriter(recordSize) {
12247 let Buf = this.context.Buf;
12248 let GsmPDUHelper = this.context.GsmPDUHelper;
12250 // Write String length
12251 let strLen = recordSize * 2;
12252 Buf.writeInt32(strLen);
12254 for (let i = 0; i < iap.length; i++) {
12255 GsmPDUHelper.writeHexOctet(iap[i]);
12256 }
12258 Buf.writeStringDelimiter(strLen);
12259 }.bind(this);
12261 this.context.ICCIOHelper.updateLinearFixedEF({
12262 fileId: fileId,
12263 recordNumber: recordNumber,
12264 dataWriter: dataWriter,
12265 callback: onsuccess,
12266 onerror: onerror
12267 });
12268 },
12270 /**
12271 * Cache EF_Email record size.
12272 */
12273 _emailRecordSize: null,
12275 /**
12276 * Read USIM/RUIM Phonebook EF_EMAIL.
12277 *
12278 * @see TS 131.102, clause 4.4.2.13
12279 *
12280 * @param fileId EF id of the EMAIL.
12281 * @param fileType The type of the EMAIL, one of the ICC_USIM_TYPE* constants.
12282 * @param recordNumber The number of the record shall be loaded.
12283 * @param onsuccess Callback to be called when success.
12284 * @param onerror Callback to be called when error.
12285 */
12286 readEmail: function(fileId, fileType, recordNumber, onsuccess, onerror) {
12287 function callback(options) {
12288 let Buf = this.context.Buf;
12289 let ICCPDUHelper = this.context.ICCPDUHelper;
12291 let strLen = Buf.readInt32();
12292 let octetLen = strLen / 2;
12293 let email = null;
12294 this._emailRecordSize = options.recordSize;
12296 // Read contact's email
12297 //
12298 // | Byte | Description | Length | M/O
12299 // | 1 ~ X | E-mail Address | X | M
12300 // | X+1 | ADN file SFI | 1 | C
12301 // | X+2 | ADN file Record Identifier | 1 | C
12302 // Note: The fields marked as C above are mandatort if the file
12303 // is not type 1 (as specified in EF_PBR)
12304 if (fileType == ICC_USIM_TYPE1_TAG) {
12305 email = ICCPDUHelper.read8BitUnpackedToString(octetLen);
12306 } else {
12307 email = ICCPDUHelper.read8BitUnpackedToString(octetLen - 2);
12309 // Consumes the remaining buffer
12310 Buf.seekIncoming(2 * Buf.PDU_HEX_OCTET_SIZE); // For ADN SFI and Record Identifier
12311 }
12313 Buf.readStringDelimiter(strLen);
12315 if (onsuccess) {
12316 onsuccess(email);
12317 }
12318 }
12320 this.context.ICCIOHelper.loadLinearFixedEF({
12321 fileId: fileId,
12322 recordNumber: recordNumber,
12323 recordSize: this._emailRecordSize,
12324 callback: callback.bind(this),
12325 onerror: onerror
12326 });
12327 },
12329 /**
12330 * Update USIM/RUIM Phonebook EF_EMAIL.
12331 *
12332 * @see TS 131.102, clause 4.4.2.13
12333 *
12334 * @param pbr Phonebook Reference File.
12335 * @param recordNumber The identifier of the record shall be updated.
12336 * @param email The value to be written.
12337 * @param adnRecordId The record Id of ADN, only needed if the fileType of Email is TYPE2.
12338 * @param onsuccess Callback to be called when success.
12339 * @param onerror Callback to be called when error.
12340 */
12341 updateEmail: function(pbr, recordNumber, email, adnRecordId, onsuccess, onerror) {
12342 let fileId = pbr[USIM_PBR_EMAIL].fileId;
12343 let fileType = pbr[USIM_PBR_EMAIL].fileType;
12344 let dataWriter = function dataWriter(recordSize) {
12345 let Buf = this.context.Buf;
12346 let GsmPDUHelper = this.context.GsmPDUHelper;
12347 let ICCPDUHelper = this.context.ICCPDUHelper;
12349 // Write String length
12350 let strLen = recordSize * 2;
12351 Buf.writeInt32(strLen);
12353 if (fileType == ICC_USIM_TYPE1_TAG) {
12354 ICCPDUHelper.writeStringTo8BitUnpacked(recordSize, email);
12355 } else {
12356 ICCPDUHelper.writeStringTo8BitUnpacked(recordSize - 2, email);
12357 GsmPDUHelper.writeHexOctet(pbr.adn.sfi || 0xff);
12358 GsmPDUHelper.writeHexOctet(adnRecordId);
12359 }
12361 Buf.writeStringDelimiter(strLen);
12362 }.bind(this);
12364 this.context.ICCIOHelper.updateLinearFixedEF({
12365 fileId: fileId,
12366 recordNumber: recordNumber,
12367 dataWriter: dataWriter,
12368 callback: onsuccess,
12369 onerror: onerror
12370 });
12371 },
12373 /**
12374 * Cache EF_ANR record size.
12375 */
12376 _anrRecordSize: null,
12378 /**
12379 * Read USIM/RUIM Phonebook EF_ANR.
12380 *
12381 * @see TS 131.102, clause 4.4.2.9
12382 *
12383 * @param fileId EF id of the ANR.
12384 * @param fileType One of the ICC_USIM_TYPE* constants.
12385 * @param recordNumber The number of the record shall be loaded.
12386 * @param onsuccess Callback to be called when success.
12387 * @param onerror Callback to be called when error.
12388 */
12389 readANR: function(fileId, fileType, recordNumber, onsuccess, onerror) {
12390 function callback(options) {
12391 let Buf = this.context.Buf;
12392 let strLen = Buf.readInt32();
12393 let number = null;
12394 this._anrRecordSize = options.recordSize;
12396 // Skip EF_AAS Record ID.
12397 Buf.seekIncoming(1 * Buf.PDU_HEX_OCTET_SIZE);
12399 number = this.context.ICCPDUHelper.readNumberWithLength();
12401 // Skip 2 unused octets, CCP and EXT1.
12402 Buf.seekIncoming(2 * Buf.PDU_HEX_OCTET_SIZE);
12404 // For Type 2 there are two extra octets.
12405 if (fileType == ICC_USIM_TYPE2_TAG) {
12406 // Skip 2 unused octets, ADN SFI and Record Identifier.
12407 Buf.seekIncoming(2 * Buf.PDU_HEX_OCTET_SIZE);
12408 }
12410 Buf.readStringDelimiter(strLen);
12412 if (onsuccess) {
12413 onsuccess(number);
12414 }
12415 }
12417 this.context.ICCIOHelper.loadLinearFixedEF({
12418 fileId: fileId,
12419 recordNumber: recordNumber,
12420 recordSize: this._anrRecordSize,
12421 callback: callback.bind(this),
12422 onerror: onerror
12423 });
12424 },
12426 /**
12427 * Update USIM/RUIM Phonebook EF_ANR.
12428 *
12429 * @see TS 131.102, clause 4.4.2.9
12430 *
12431 * @param pbr Phonebook Reference File.
12432 * @param recordNumber The identifier of the record shall be updated.
12433 * @param number The value to be written.
12434 * @param adnRecordId The record Id of ADN, only needed if the fileType of Email is TYPE2.
12435 * @param onsuccess Callback to be called when success.
12436 * @param onerror Callback to be called when error.
12437 */
12438 updateANR: function(pbr, recordNumber, number, adnRecordId, onsuccess, onerror) {
12439 let fileId = pbr[USIM_PBR_ANR0].fileId;
12440 let fileType = pbr[USIM_PBR_ANR0].fileType;
12441 let dataWriter = function dataWriter(recordSize) {
12442 let Buf = this.context.Buf;
12443 let GsmPDUHelper = this.context.GsmPDUHelper;
12445 // Write String length
12446 let strLen = recordSize * 2;
12447 Buf.writeInt32(strLen);
12449 // EF_AAS record Id. Unused for now.
12450 GsmPDUHelper.writeHexOctet(0xff);
12452 this.context.ICCPDUHelper.writeNumberWithLength(number);
12454 // Write unused octets 0xff, CCP and EXT1.
12455 GsmPDUHelper.writeHexOctet(0xff);
12456 GsmPDUHelper.writeHexOctet(0xff);
12458 // For Type 2 there are two extra octets.
12459 if (fileType == ICC_USIM_TYPE2_TAG) {
12460 GsmPDUHelper.writeHexOctet(pbr.adn.sfi || 0xff);
12461 GsmPDUHelper.writeHexOctet(adnRecordId);
12462 }
12464 Buf.writeStringDelimiter(strLen);
12465 }.bind(this);
12467 this.context.ICCIOHelper.updateLinearFixedEF({
12468 fileId: fileId,
12469 recordNumber: recordNumber,
12470 dataWriter: dataWriter,
12471 callback: onsuccess,
12472 onerror: onerror
12473 });
12474 },
12476 /**
12477 * Find free record id.
12478 *
12479 * @param fileId EF id.
12480 * @param onsuccess Callback to be called when success.
12481 * @param onerror Callback to be called when error.
12482 */
12483 findFreeRecordId: function(fileId, onsuccess, onerror) {
12484 let ICCIOHelper = this.context.ICCIOHelper;
12486 function callback(options) {
12487 let Buf = this.context.Buf;
12488 let GsmPDUHelper = this.context.GsmPDUHelper;
12490 let strLen = Buf.readInt32();
12491 let octetLen = strLen / 2;
12492 let readLen = 0;
12494 while (readLen < octetLen) {
12495 let octet = GsmPDUHelper.readHexOctet();
12496 readLen++;
12497 if (octet != 0xff) {
12498 break;
12499 }
12500 }
12502 if (readLen == octetLen) {
12503 // Find free record.
12504 if (onsuccess) {
12505 onsuccess(options.p1);
12506 }
12507 return;
12508 } else {
12509 Buf.seekIncoming((octetLen - readLen) * Buf.PDU_HEX_OCTET_SIZE);
12510 }
12512 Buf.readStringDelimiter(strLen);
12514 if (options.p1 < options.totalRecords) {
12515 ICCIOHelper.loadNextRecord(options);
12516 } else {
12517 // No free record found.
12518 if (DEBUG) {
12519 this.context.debug(CONTACT_ERR_NO_FREE_RECORD_FOUND);
12520 }
12521 onerror(CONTACT_ERR_NO_FREE_RECORD_FOUND);
12522 }
12523 }
12525 ICCIOHelper.loadLinearFixedEF({fileId: fileId,
12526 callback: callback.bind(this),
12527 onerror: onerror});
12528 },
12529 };
12531 /**
12532 * Helper for (U)SIM Records.
12533 */
12534 function SimRecordHelperObject(aContext) {
12535 this.context = aContext;
12536 }
12537 SimRecordHelperObject.prototype = {
12538 context: null,
12540 /**
12541 * Fetch (U)SIM records.
12542 */
12543 fetchSimRecords: function() {
12544 this.context.RIL.getIMSI();
12545 this.readAD();
12546 this.readSST();
12547 },
12549 /**
12550 * Read EF_phase.
12551 * This EF is only available in SIM.
12552 */
12553 readSimPhase: function() {
12554 function callback() {
12555 let Buf = this.context.Buf;
12556 let strLen = Buf.readInt32();
12558 let GsmPDUHelper = this.context.GsmPDUHelper;
12559 let phase = GsmPDUHelper.readHexOctet();
12560 // If EF_phase is coded '03' or greater, an ME supporting STK shall
12561 // perform the PROFILE DOWNLOAD procedure.
12562 if (RILQUIRKS_SEND_STK_PROFILE_DOWNLOAD &&
12563 phase >= ICC_PHASE_2_PROFILE_DOWNLOAD_REQUIRED) {
12564 this.context.RIL.sendStkTerminalProfile(STK_SUPPORTED_TERMINAL_PROFILE);
12565 }
12567 Buf.readStringDelimiter(strLen);
12568 }
12570 this.context.ICCIOHelper.loadTransparentEF({
12571 fileId: ICC_EF_PHASE,
12572 callback: callback.bind(this)
12573 });
12574 },
12576 /**
12577 * Read the MSISDN from the (U)SIM.
12578 */
12579 readMSISDN: function() {
12580 function callback(options) {
12581 let RIL = this.context.RIL;
12583 let contact =
12584 this.context.ICCPDUHelper.readAlphaIdDiallingNumber(options.recordSize);
12585 if (!contact ||
12586 (RIL.iccInfo.msisdn !== undefined &&
12587 RIL.iccInfo.msisdn === contact.number)) {
12588 return;
12589 }
12590 RIL.iccInfo.msisdn = contact.number;
12591 if (DEBUG) this.context.debug("MSISDN: " + RIL.iccInfo.msisdn);
12592 this.context.ICCUtilsHelper.handleICCInfoChange();
12593 }
12595 this.context.ICCIOHelper.loadLinearFixedEF({
12596 fileId: ICC_EF_MSISDN,
12597 callback: callback.bind(this)
12598 });
12599 },
12601 /**
12602 * Read the AD (Administrative Data) from the (U)SIM.
12603 */
12604 readAD: function() {
12605 function callback() {
12606 let Buf = this.context.Buf;
12607 let strLen = Buf.readInt32();
12608 // Each octet is encoded into two chars.
12609 let octetLen = strLen / 2;
12610 let ad = this.context.GsmPDUHelper.readHexOctetArray(octetLen);
12611 Buf.readStringDelimiter(strLen);
12613 if (DEBUG) {
12614 let str = "";
12615 for (let i = 0; i < ad.length; i++) {
12616 str += ad[i] + ", ";
12617 }
12618 this.context.debug("AD: " + str);
12619 }
12621 let ICCUtilsHelper = this.context.ICCUtilsHelper;
12622 let RIL = this.context.RIL;
12623 // The 4th byte of the response is the length of MNC.
12624 let mccMnc = ICCUtilsHelper.parseMccMncFromImsi(RIL.iccInfoPrivate.imsi,
12625 ad && ad[3]);
12626 if (mccMnc) {
12627 RIL.iccInfo.mcc = mccMnc.mcc;
12628 RIL.iccInfo.mnc = mccMnc.mnc;
12629 ICCUtilsHelper.handleICCInfoChange();
12630 }
12631 }
12633 this.context.ICCIOHelper.loadTransparentEF({
12634 fileId: ICC_EF_AD,
12635 callback: callback.bind(this)
12636 });
12637 },
12639 /**
12640 * Read the SPN (Service Provider Name) from the (U)SIM.
12641 */
12642 readSPN: function() {
12643 function callback() {
12644 let Buf = this.context.Buf;
12645 let strLen = Buf.readInt32();
12646 // Each octet is encoded into two chars.
12647 let octetLen = strLen / 2;
12648 let spnDisplayCondition = this.context.GsmPDUHelper.readHexOctet();
12649 // Minus 1 because the first octet is used to store display condition.
12650 let spn = this.context.ICCPDUHelper.readAlphaIdentifier(octetLen - 1);
12651 Buf.readStringDelimiter(strLen);
12653 if (DEBUG) {
12654 this.context.debug("SPN: spn = " + spn +
12655 ", spnDisplayCondition = " + spnDisplayCondition);
12656 }
12658 let RIL = this.context.RIL;
12659 RIL.iccInfoPrivate.spnDisplayCondition = spnDisplayCondition;
12660 RIL.iccInfo.spn = spn;
12661 let ICCUtilsHelper = this.context.ICCUtilsHelper;
12662 ICCUtilsHelper.updateDisplayCondition();
12663 ICCUtilsHelper.handleICCInfoChange();
12664 }
12666 this.context.ICCIOHelper.loadTransparentEF({
12667 fileId: ICC_EF_SPN,
12668 callback: callback.bind(this)
12669 });
12670 },
12672 /**
12673 * Read the (U)SIM Service Table from the (U)SIM.
12674 */
12675 readSST: function() {
12676 function callback() {
12677 let Buf = this.context.Buf;
12678 let RIL = this.context.RIL;
12680 let strLen = Buf.readInt32();
12681 // Each octet is encoded into two chars.
12682 let octetLen = strLen / 2;
12683 let sst = this.context.GsmPDUHelper.readHexOctetArray(octetLen);
12684 Buf.readStringDelimiter(strLen);
12685 RIL.iccInfoPrivate.sst = sst;
12686 if (DEBUG) {
12687 let str = "";
12688 for (let i = 0; i < sst.length; i++) {
12689 str += sst[i] + ", ";
12690 }
12691 this.context.debug("SST: " + str);
12692 }
12694 let ICCUtilsHelper = this.context.ICCUtilsHelper;
12695 if (ICCUtilsHelper.isICCServiceAvailable("MSISDN")) {
12696 if (DEBUG) this.context.debug("MSISDN: MSISDN is available");
12697 this.readMSISDN();
12698 } else {
12699 if (DEBUG) this.context.debug("MSISDN: MSISDN service is not available");
12700 }
12702 // Fetch SPN and PLMN list, if some of them are available.
12703 if (ICCUtilsHelper.isICCServiceAvailable("SPN")) {
12704 if (DEBUG) this.context.debug("SPN: SPN is available");
12705 this.readSPN();
12706 } else {
12707 if (DEBUG) this.context.debug("SPN: SPN service is not available");
12708 }
12710 if (ICCUtilsHelper.isICCServiceAvailable("MDN")) {
12711 if (DEBUG) this.context.debug("MDN: MDN available.");
12712 this.readMBDN();
12713 } else {
12714 if (DEBUG) this.context.debug("MDN: MDN service is not available");
12715 }
12717 if (ICCUtilsHelper.isICCServiceAvailable("MWIS")) {
12718 if (DEBUG) this.context.debug("MWIS: MWIS is available");
12719 this.readMWIS();
12720 } else {
12721 if (DEBUG) this.context.debug("MWIS: MWIS is not available");
12722 }
12724 if (ICCUtilsHelper.isICCServiceAvailable("SPDI")) {
12725 if (DEBUG) this.context.debug("SPDI: SPDI available.");
12726 this.readSPDI();
12727 } else {
12728 if (DEBUG) this.context.debug("SPDI: SPDI service is not available");
12729 }
12731 if (ICCUtilsHelper.isICCServiceAvailable("PNN")) {
12732 if (DEBUG) this.context.debug("PNN: PNN is available");
12733 this.readPNN();
12734 } else {
12735 if (DEBUG) this.context.debug("PNN: PNN is not available");
12736 }
12738 if (ICCUtilsHelper.isICCServiceAvailable("OPL")) {
12739 if (DEBUG) this.context.debug("OPL: OPL is available");
12740 this.readOPL();
12741 } else {
12742 if (DEBUG) this.context.debug("OPL: OPL is not available");
12743 }
12745 if (ICCUtilsHelper.isICCServiceAvailable("CBMI")) {
12746 this.readCBMI();
12747 } else {
12748 RIL.cellBroadcastConfigs.CBMI = null;
12749 }
12750 if (ICCUtilsHelper.isICCServiceAvailable("DATA_DOWNLOAD_SMS_CB")) {
12751 this.readCBMID();
12752 } else {
12753 RIL.cellBroadcastConfigs.CBMID = null;
12754 }
12755 if (ICCUtilsHelper.isICCServiceAvailable("CBMIR")) {
12756 this.readCBMIR();
12757 } else {
12758 RIL.cellBroadcastConfigs.CBMIR = null;
12759 }
12760 RIL._mergeAllCellBroadcastConfigs();
12761 }
12763 // ICC_EF_UST has the same value with ICC_EF_SST.
12764 this.context.ICCIOHelper.loadTransparentEF({
12765 fileId: ICC_EF_SST,
12766 callback: callback.bind(this)
12767 });
12768 },
12770 /**
12771 * Read (U)SIM MBDN. (Mailbox Dialling Number)
12772 *
12773 * @see TS 131.102, clause 4.2.60
12774 */
12775 readMBDN: function() {
12776 function callback(options) {
12777 let RIL = this.context.RIL;
12778 let contact =
12779 this.context.ICCPDUHelper.readAlphaIdDiallingNumber(options.recordSize);
12780 if (!contact ||
12781 (RIL.iccInfoPrivate.mbdn !== undefined &&
12782 RIL.iccInfoPrivate.mbdn === contact.number)) {
12783 return;
12784 }
12785 RIL.iccInfoPrivate.mbdn = contact.number;
12786 if (DEBUG) {
12787 this.context.debug("MBDN, alphaId=" + contact.alphaId +
12788 " number=" + contact.number);
12789 }
12790 contact.rilMessageType = "iccmbdn";
12791 RIL.sendChromeMessage(contact);
12792 }
12794 this.context.ICCIOHelper.loadLinearFixedEF({
12795 fileId: ICC_EF_MBDN,
12796 callback: callback.bind(this)
12797 });
12798 },
12800 /**
12801 * Read ICC MWIS. (Message Waiting Indication Status)
12802 *
12803 * @see TS 31.102, clause 4.2.63 for USIM and TS 51.011, clause 10.3.45 for SIM.
12804 */
12805 readMWIS: function() {
12806 function callback(options) {
12807 let Buf = this.context.Buf;
12808 let RIL = this.context.RIL;
12810 let strLen = Buf.readInt32();
12811 // Each octet is encoded into two chars.
12812 let octetLen = strLen / 2;
12813 let mwis = this.context.GsmPDUHelper.readHexOctetArray(octetLen);
12814 Buf.readStringDelimiter(strLen);
12815 if (!mwis) {
12816 return;
12817 }
12818 RIL.iccInfoPrivate.mwis = mwis; //Keep raw MWIS for updateMWIS()
12820 let mwi = {};
12821 // b8 b7 B6 b5 b4 b3 b2 b1 4.2.63, TS 31.102 version 11.6.0
12822 // | | | | | | | |__ Voicemail
12823 // | | | | | | |_____ Fax
12824 // | | | | | |________ Electronic Mail
12825 // | | | | |___________ Other
12826 // | | | |______________ Videomail
12827 // |__|__|_________________ RFU
12828 mwi.active = ((mwis[0] & 0x01) != 0);
12830 if (mwi.active) {
12831 // In TS 23.040 msgCount is in the range from 0 to 255.
12832 // The value 255 shall be taken to mean 255 or greater.
12833 //
12834 // However, There is no definition about 0 when MWI is active.
12835 //
12836 // Normally, when mwi is active, the msgCount must be larger than 0.
12837 // Refer to other reference phone,
12838 // 0 is usually treated as UNKNOWN for storing 2nd level MWI status (DCS).
12839 mwi.msgCount = (mwis[1] === 0) ? GECKO_VOICEMAIL_MESSAGE_COUNT_UNKNOWN
12840 : mwis[1];
12841 } else {
12842 mwi.msgCount = 0;
12843 }
12845 RIL.sendChromeMessage({ rilMessageType: "iccmwis",
12846 mwi: mwi });
12847 }
12849 this.context.ICCIOHelper.loadLinearFixedEF({
12850 fileId: ICC_EF_MWIS,
12851 recordNumber: 1, // Get 1st Subscriber Profile.
12852 callback: callback.bind(this)
12853 });
12854 },
12856 /**
12857 * Update ICC MWIS. (Message Waiting Indication Status)
12858 *
12859 * @see TS 31.102, clause 4.2.63 for USIM and TS 51.011, clause 10.3.45 for SIM.
12860 */
12861 updateMWIS: function(mwi) {
12862 let RIL = this.context.RIL;
12863 if (!RIL.iccInfoPrivate.mwis) {
12864 return;
12865 }
12867 function dataWriter(recordSize) {
12868 let mwis = RIL.iccInfoPrivate.mwis;
12870 let msgCount =
12871 (mwi.msgCount === GECKO_VOICEMAIL_MESSAGE_COUNT_UNKNOWN) ? 0 : mwi.msgCount;
12873 [mwis[0], mwis[1]] = (mwi.active) ? [(mwis[0] | 0x01), msgCount]
12874 : [(mwis[0] & 0xFE), 0];
12876 let strLen = recordSize * 2;
12877 let Buf = this.context.Buf;
12878 Buf.writeInt32(strLen);
12880 let GsmPDUHelper = this.context.GsmPDUHelper;
12881 for (let i = 0; i < mwis.length; i++) {
12882 GsmPDUHelper.writeHexOctet(mwis[i]);
12883 }
12885 Buf.writeStringDelimiter(strLen);
12886 }
12888 this.context.ICCIOHelper.updateLinearFixedEF({
12889 fileId: ICC_EF_MWIS,
12890 recordNumber: 1, // Update 1st Subscriber Profile.
12891 dataWriter: dataWriter.bind(this)
12892 });
12893 },
12895 /**
12896 * Read the SPDI (Service Provider Display Information) from the (U)SIM.
12897 *
12898 * See TS 131.102 section 4.2.66 for USIM and TS 51.011 section 10.3.50
12899 * for SIM.
12900 */
12901 readSPDI: function() {
12902 function callback() {
12903 let Buf = this.context.Buf;
12904 let strLen = Buf.readInt32();
12905 let octetLen = strLen / 2;
12906 let readLen = 0;
12907 let endLoop = false;
12909 let RIL = this.context.RIL;
12910 RIL.iccInfoPrivate.SPDI = null;
12912 let GsmPDUHelper = this.context.GsmPDUHelper;
12913 while ((readLen < octetLen) && !endLoop) {
12914 let tlvTag = GsmPDUHelper.readHexOctet();
12915 let tlvLen = GsmPDUHelper.readHexOctet();
12916 readLen += 2; // For tag and length fields.
12917 switch (tlvTag) {
12918 case SPDI_TAG_SPDI:
12919 // The value part itself is a TLV.
12920 continue;
12921 case SPDI_TAG_PLMN_LIST:
12922 // This PLMN list is what we want.
12923 RIL.iccInfoPrivate.SPDI = this.readPLMNEntries(tlvLen / 3);
12924 readLen += tlvLen;
12925 endLoop = true;
12926 break;
12927 default:
12928 // We don't care about its content if its tag is not SPDI nor
12929 // PLMN_LIST.
12930 endLoop = true;
12931 break;
12932 }
12933 }
12935 // Consume unread octets.
12936 Buf.seekIncoming((octetLen - readLen) * Buf.PDU_HEX_OCTET_SIZE);
12937 Buf.readStringDelimiter(strLen);
12939 if (DEBUG) {
12940 this.context.debug("SPDI: " + JSON.stringify(RIL.iccInfoPrivate.SPDI));
12941 }
12942 let ICCUtilsHelper = this.context.ICCUtilsHelper;
12943 if (ICCUtilsHelper.updateDisplayCondition()) {
12944 ICCUtilsHelper.handleICCInfoChange();
12945 }
12946 }
12948 // PLMN List is Servive 51 in USIM, EF_SPDI
12949 this.context.ICCIOHelper.loadTransparentEF({
12950 fileId: ICC_EF_SPDI,
12951 callback: callback.bind(this)
12952 });
12953 },
12955 _readCbmiHelper: function(which) {
12956 let RIL = this.context.RIL;
12958 function callback() {
12959 let Buf = this.context.Buf;
12960 let strLength = Buf.readInt32();
12962 // Each Message Identifier takes two octets and each octet is encoded
12963 // into two chars.
12964 let numIds = strLength / 4, list = null;
12965 if (numIds) {
12966 list = [];
12967 let GsmPDUHelper = this.context.GsmPDUHelper;
12968 for (let i = 0, id; i < numIds; i++) {
12969 id = GsmPDUHelper.readHexOctet() << 8 | GsmPDUHelper.readHexOctet();
12970 // `Unused entries shall be set to 'FF FF'.`
12971 if (id != 0xFFFF) {
12972 list.push(id);
12973 list.push(id + 1);
12974 }
12975 }
12976 }
12977 if (DEBUG) {
12978 this.context.debug(which + ": " + JSON.stringify(list));
12979 }
12981 Buf.readStringDelimiter(strLength);
12983 RIL.cellBroadcastConfigs[which] = list;
12984 RIL._mergeAllCellBroadcastConfigs();
12985 }
12987 function onerror() {
12988 RIL.cellBroadcastConfigs[which] = null;
12989 RIL._mergeAllCellBroadcastConfigs();
12990 }
12992 let fileId = GLOBAL["ICC_EF_" + which];
12993 this.context.ICCIOHelper.loadTransparentEF({
12994 fileId: fileId,
12995 callback: callback.bind(this),
12996 onerror: onerror.bind(this)
12997 });
12998 },
13000 /**
13001 * Read EFcbmi (Cell Broadcast Message Identifier selection)
13002 *
13003 * @see 3GPP TS 31.102 v110.02.0 section 4.2.14 EFcbmi
13004 * @see 3GPP TS 51.011 v5.0.0 section 10.3.13 EFcbmi
13005 */
13006 readCBMI: function() {
13007 this._readCbmiHelper("CBMI");
13008 },
13010 /**
13011 * Read EFcbmid (Cell Broadcast Message Identifier for Data Download)
13012 *
13013 * @see 3GPP TS 31.102 v110.02.0 section 4.2.20 EFcbmid
13014 * @see 3GPP TS 51.011 v5.0.0 section 10.3.26 EFcbmid
13015 */
13016 readCBMID: function() {
13017 this._readCbmiHelper("CBMID");
13018 },
13020 /**
13021 * Read EFcbmir (Cell Broadcast Message Identifier Range selection)
13022 *
13023 * @see 3GPP TS 31.102 v110.02.0 section 4.2.22 EFcbmir
13024 * @see 3GPP TS 51.011 v5.0.0 section 10.3.28 EFcbmir
13025 */
13026 readCBMIR: function() {
13027 let RIL = this.context.RIL;
13029 function callback() {
13030 let Buf = this.context.Buf;
13031 let strLength = Buf.readInt32();
13033 // Each Message Identifier range takes four octets and each octet is
13034 // encoded into two chars.
13035 let numIds = strLength / 8, list = null;
13036 if (numIds) {
13037 list = [];
13038 let GsmPDUHelper = this.context.GsmPDUHelper;
13039 for (let i = 0, from, to; i < numIds; i++) {
13040 // `Bytes one and two of each range identifier equal the lower value
13041 // of a cell broadcast range, bytes three and four equal the upper
13042 // value of a cell broadcast range.`
13043 from = GsmPDUHelper.readHexOctet() << 8 | GsmPDUHelper.readHexOctet();
13044 to = GsmPDUHelper.readHexOctet() << 8 | GsmPDUHelper.readHexOctet();
13045 // `Unused entries shall be set to 'FF FF'.`
13046 if ((from != 0xFFFF) && (to != 0xFFFF)) {
13047 list.push(from);
13048 list.push(to + 1);
13049 }
13050 }
13051 }
13052 if (DEBUG) {
13053 this.context.debug("CBMIR: " + JSON.stringify(list));
13054 }
13056 Buf.readStringDelimiter(strLength);
13058 RIL.cellBroadcastConfigs.CBMIR = list;
13059 RIL._mergeAllCellBroadcastConfigs();
13060 }
13062 function onerror() {
13063 RIL.cellBroadcastConfigs.CBMIR = null;
13064 RIL._mergeAllCellBroadcastConfigs();
13065 }
13067 this.context.ICCIOHelper.loadTransparentEF({
13068 fileId: ICC_EF_CBMIR,
13069 callback: callback.bind(this),
13070 onerror: onerror.bind(this)
13071 });
13072 },
13074 /**
13075 * Read OPL (Operator PLMN List) from (U)SIM.
13076 *
13077 * See 3GPP TS 31.102 Sec. 4.2.59 for USIM
13078 * 3GPP TS 51.011 Sec. 10.3.42 for SIM.
13079 */
13080 readOPL: function() {
13081 let ICCIOHelper = this.context.ICCIOHelper;
13082 let opl = [];
13083 function callback(options) {
13084 let Buf = this.context.Buf;
13085 let GsmPDUHelper = this.context.GsmPDUHelper;
13087 let strLen = Buf.readInt32();
13088 // The first 7 bytes are LAI (for UMTS) and the format of LAI is defined
13089 // in 3GPP TS 23.003, Sec 4.1
13090 // +-------------+---------+
13091 // | Octet 1 - 3 | MCC/MNC |
13092 // +-------------+---------+
13093 // | Octet 4 - 7 | LAC |
13094 // +-------------+---------+
13095 let mccMnc = [GsmPDUHelper.readHexOctet(),
13096 GsmPDUHelper.readHexOctet(),
13097 GsmPDUHelper.readHexOctet()];
13098 if (mccMnc[0] != 0xFF || mccMnc[1] != 0xFF || mccMnc[2] != 0xFF) {
13099 let oplElement = {};
13100 let semiOctets = [];
13101 for (let i = 0; i < mccMnc.length; i++) {
13102 semiOctets.push((mccMnc[i] & 0xf0) >> 4);
13103 semiOctets.push(mccMnc[i] & 0x0f);
13104 }
13105 let reformat = [semiOctets[1], semiOctets[0], semiOctets[3],
13106 semiOctets[5], semiOctets[4], semiOctets[2]];
13107 let buf = "";
13108 for (let i = 0; i < reformat.length; i++) {
13109 if (reformat[i] != 0xF) {
13110 buf += GsmPDUHelper.semiOctetToBcdChar(reformat[i]);
13111 }
13112 if (i === 2) {
13113 // 0-2: MCC
13114 oplElement.mcc = buf;
13115 buf = "";
13116 } else if (i === 5) {
13117 // 3-5: MNC
13118 oplElement.mnc = buf;
13119 }
13120 }
13121 // LAC/TAC
13122 oplElement.lacTacStart =
13123 (GsmPDUHelper.readHexOctet() << 8) | GsmPDUHelper.readHexOctet();
13124 oplElement.lacTacEnd =
13125 (GsmPDUHelper.readHexOctet() << 8) | GsmPDUHelper.readHexOctet();
13126 // PLMN Network Name Record Identifier
13127 oplElement.pnnRecordId = GsmPDUHelper.readHexOctet();
13128 if (DEBUG) {
13129 this.context.debug("OPL: [" + (opl.length + 1) + "]: " +
13130 JSON.stringify(oplElement));
13131 }
13132 opl.push(oplElement);
13133 } else {
13134 Buf.seekIncoming(5 * Buf.PDU_HEX_OCTET_SIZE);
13135 }
13136 Buf.readStringDelimiter(strLen);
13138 if (options.p1 < options.totalRecords) {
13139 ICCIOHelper.loadNextRecord(options);
13140 } else {
13141 this.context.RIL.iccInfoPrivate.OPL = opl;
13142 }
13143 }
13145 ICCIOHelper.loadLinearFixedEF({fileId: ICC_EF_OPL,
13146 callback: callback.bind(this)});
13147 },
13149 /**
13150 * Read PNN (PLMN Network Name) from (U)SIM.
13151 *
13152 * See 3GPP TS 31.102 Sec. 4.2.58 for USIM
13153 * 3GPP TS 51.011 Sec. 10.3.41 for SIM.
13154 */
13155 readPNN: function() {
13156 let ICCIOHelper = this.context.ICCIOHelper;
13157 function callback(options) {
13158 let pnnElement;
13159 let Buf = this.context.Buf;
13160 let strLen = Buf.readInt32();
13161 let octetLen = strLen / 2;
13162 let readLen = 0;
13164 let GsmPDUHelper = this.context.GsmPDUHelper;
13165 while (readLen < octetLen) {
13166 let tlvTag = GsmPDUHelper.readHexOctet();
13168 if (tlvTag == 0xFF) {
13169 // Unused byte
13170 readLen++;
13171 Buf.seekIncoming((octetLen - readLen) * Buf.PDU_HEX_OCTET_SIZE);
13172 break;
13173 }
13175 // Needs this check to avoid initializing twice.
13176 pnnElement = pnnElement || {};
13178 let tlvLen = GsmPDUHelper.readHexOctet();
13180 switch (tlvTag) {
13181 case PNN_IEI_FULL_NETWORK_NAME:
13182 pnnElement.fullName = GsmPDUHelper.readNetworkName(tlvLen);
13183 break;
13184 case PNN_IEI_SHORT_NETWORK_NAME:
13185 pnnElement.shortName = GsmPDUHelper.readNetworkName(tlvLen);
13186 break;
13187 default:
13188 Buf.seekIncoming(tlvLen * Buf.PDU_HEX_OCTET_SIZE);
13189 break;
13190 }
13192 readLen += (tlvLen + 2); // +2 for tlvTag and tlvLen
13193 }
13194 Buf.readStringDelimiter(strLen);
13196 if (pnnElement) {
13197 pnn.push(pnnElement);
13198 }
13200 // Will ignore remaining records when got the contents of a record are all 0xff.
13201 if (pnnElement && options.p1 < options.totalRecords) {
13202 ICCIOHelper.loadNextRecord(options);
13203 } else {
13204 if (DEBUG) {
13205 for (let i = 0; i < pnn.length; i++) {
13206 this.context.debug("PNN: [" + i + "]: " + JSON.stringify(pnn[i]));
13207 }
13208 }
13209 this.context.RIL.iccInfoPrivate.PNN = pnn;
13210 }
13211 }
13213 let pnn = [];
13214 ICCIOHelper.loadLinearFixedEF({fileId: ICC_EF_PNN,
13215 callback: callback.bind(this)});
13216 },
13218 /**
13219 * Read the list of PLMN (Public Land Mobile Network) entries
13220 * We cannot directly rely on readSwappedNibbleBcdToString(),
13221 * since it will no correctly handle some corner-cases that are
13222 * not a problem in our case (0xFF 0xFF 0xFF).
13223 *
13224 * @param length The number of PLMN records.
13225 * @return An array of string corresponding to the PLMNs.
13226 */
13227 readPLMNEntries: function(length) {
13228 let plmnList = [];
13229 // Each PLMN entry has 3 bytes.
13230 if (DEBUG) {
13231 this.context.debug("PLMN entries length = " + length);
13232 }
13233 let GsmPDUHelper = this.context.GsmPDUHelper;
13234 let index = 0;
13235 while (index < length) {
13236 // Unused entries will be 0xFFFFFF, according to EF_SPDI
13237 // specs (TS 131 102, section 4.2.66)
13238 try {
13239 let plmn = [GsmPDUHelper.readHexOctet(),
13240 GsmPDUHelper.readHexOctet(),
13241 GsmPDUHelper.readHexOctet()];
13242 if (DEBUG) {
13243 this.context.debug("Reading PLMN entry: [" + index + "]: '" + plmn + "'");
13244 }
13245 if (plmn[0] != 0xFF &&
13246 plmn[1] != 0xFF &&
13247 plmn[2] != 0xFF) {
13248 let semiOctets = [];
13249 for (let idx = 0; idx < plmn.length; idx++) {
13250 semiOctets.push((plmn[idx] & 0xF0) >> 4);
13251 semiOctets.push(plmn[idx] & 0x0F);
13252 }
13254 // According to TS 24.301, 9.9.3.12, the semi octets is arranged
13255 // in format:
13256 // Byte 1: MCC[2] | MCC[1]
13257 // Byte 2: MNC[3] | MCC[3]
13258 // Byte 3: MNC[2] | MNC[1]
13259 // Therefore, we need to rearrange them.
13260 let reformat = [semiOctets[1], semiOctets[0], semiOctets[3],
13261 semiOctets[5], semiOctets[4], semiOctets[2]];
13262 let buf = "";
13263 let plmnEntry = {};
13264 for (let i = 0; i < reformat.length; i++) {
13265 if (reformat[i] != 0xF) {
13266 buf += GsmPDUHelper.semiOctetToBcdChar(reformat[i]);
13267 }
13268 if (i === 2) {
13269 // 0-2: MCC
13270 plmnEntry.mcc = buf;
13271 buf = "";
13272 } else if (i === 5) {
13273 // 3-5: MNC
13274 plmnEntry.mnc = buf;
13275 }
13276 }
13277 if (DEBUG) {
13278 this.context.debug("PLMN = " + plmnEntry.mcc + ", " + plmnEntry.mnc);
13279 }
13280 plmnList.push(plmnEntry);
13281 }
13282 } catch (e) {
13283 if (DEBUG) {
13284 this.context.debug("PLMN entry " + index + " is invalid.");
13285 }
13286 break;
13287 }
13288 index ++;
13289 }
13290 return plmnList;
13291 },
13293 /**
13294 * Read the SMS from the ICC.
13295 *
13296 * @param recordNumber The number of the record shall be loaded.
13297 * @param onsuccess Callback to be called when success.
13298 * @param onerror Callback to be called when error.
13299 */
13300 readSMS: function(recordNumber, onsuccess, onerror) {
13301 function callback(options) {
13302 let Buf = this.context.Buf;
13303 let strLen = Buf.readInt32();
13305 // TS 51.011, 10.5.3 EF_SMS
13306 // b3 b2 b1
13307 // 0 0 1 message received by MS from network; message read
13308 // 0 1 1 message received by MS from network; message to be read
13309 // 1 1 1 MS originating message; message to be sent
13310 // 1 0 1 MS originating message; message sent to the network:
13311 let GsmPDUHelper = this.context.GsmPDUHelper;
13312 let status = GsmPDUHelper.readHexOctet();
13314 let message = GsmPDUHelper.readMessage();
13315 message.simStatus = status;
13317 // Consumes the remaining buffer
13318 Buf.seekIncoming(Buf.getReadAvailable() - Buf.PDU_HEX_OCTET_SIZE);
13320 Buf.readStringDelimiter(strLen);
13322 if (message) {
13323 onsuccess(message);
13324 } else {
13325 onerror("Failed to decode SMS on SIM #" + recordNumber);
13326 }
13327 }
13329 this.context.ICCIOHelper.loadLinearFixedEF({
13330 fileId: ICC_EF_SMS,
13331 recordNumber: recordNumber,
13332 callback: callback.bind(this),
13333 onerror: onerror
13334 });
13335 },
13336 };
13338 function RuimRecordHelperObject(aContext) {
13339 this.context = aContext;
13340 }
13341 RuimRecordHelperObject.prototype = {
13342 context: null,
13344 fetchRuimRecords: function() {
13345 this.getIMSI_M();
13346 this.readCST();
13347 this.readCDMAHome();
13348 this.context.RIL.getCdmaSubscription();
13349 },
13351 /**
13352 * Get IMSI_M from CSIM/RUIM.
13353 * See 3GPP2 C.S0065 Sec. 5.2.2
13354 */
13355 getIMSI_M: function() {
13356 function callback() {
13357 let Buf = this.context.Buf;
13358 let strLen = Buf.readInt32();
13359 let encodedImsi = this.context.GsmPDUHelper.readHexOctetArray(strLen / 2);
13360 Buf.readStringDelimiter(strLen);
13362 if ((encodedImsi[CSIM_IMSI_M_PROGRAMMED_BYTE] & 0x80)) { // IMSI_M programmed
13363 let RIL = this.context.RIL;
13364 RIL.iccInfoPrivate.imsi = this.decodeIMSI(encodedImsi);
13365 RIL.sendChromeMessage({rilMessageType: "iccimsi",
13366 imsi: RIL.iccInfoPrivate.imsi});
13368 let ICCUtilsHelper = this.context.ICCUtilsHelper;
13369 let mccMnc = ICCUtilsHelper.parseMccMncFromImsi(RIL.iccInfoPrivate.imsi);
13370 if (mccMnc) {
13371 RIL.iccInfo.mcc = mccMnc.mcc;
13372 RIL.iccInfo.mnc = mccMnc.mnc;
13373 ICCUtilsHelper.handleICCInfoChange();
13374 }
13375 }
13376 }
13378 this.context.ICCIOHelper.loadTransparentEF({
13379 fileId: ICC_EF_CSIM_IMSI_M,
13380 callback: callback.bind(this)
13381 });
13382 },
13384 /**
13385 * Decode IMSI from IMSI_M
13386 * See 3GPP2 C.S0005 Sec. 2.3.1
13387 * +---+---------+------------+---+--------+---------+---+---------+--------+
13388 * |RFU| MCC | programmed |RFU| MNC | MIN1 |RFU| MIN2 | CLASS |
13389 * +---+---------+------------+---+--------+---------+---+---------+--------+
13390 * | 6 | 10 bits | 8 bits | 1 | 7 bits | 24 bits | 6 | 10 bits | 8 bits |
13391 * +---+---------+------------+---+--------+---------+---+---------+--------+
13392 */
13393 decodeIMSI: function(encodedImsi) {
13394 // MCC: 10 bits, 3 digits
13395 let encodedMCC = ((encodedImsi[CSIM_IMSI_M_MCC_BYTE + 1] & 0x03) << 8) +
13396 (encodedImsi[CSIM_IMSI_M_MCC_BYTE] & 0xff);
13397 let mcc = this.decodeIMSIValue(encodedMCC, 3);
13399 // MNC: 7 bits, 2 digits
13400 let encodedMNC = encodedImsi[CSIM_IMSI_M_MNC_BYTE] & 0x7f;
13401 let mnc = this.decodeIMSIValue(encodedMNC, 2);
13403 // MIN2: 10 bits, 3 digits
13404 let encodedMIN2 = ((encodedImsi[CSIM_IMSI_M_MIN2_BYTE + 1] & 0x03) << 8) +
13405 (encodedImsi[CSIM_IMSI_M_MIN2_BYTE] & 0xff);
13406 let min2 = this.decodeIMSIValue(encodedMIN2, 3);
13408 // MIN1: 10+4+10 bits, 3+1+3 digits
13409 let encodedMIN1First3 = ((encodedImsi[CSIM_IMSI_M_MIN1_BYTE + 2] & 0xff) << 2) +
13410 ((encodedImsi[CSIM_IMSI_M_MIN1_BYTE + 1] & 0xc0) >> 6);
13411 let min1First3 = this.decodeIMSIValue(encodedMIN1First3, 3);
13413 let encodedFourthDigit = (encodedImsi[CSIM_IMSI_M_MIN1_BYTE + 1] & 0x3c) >> 2;
13414 if (encodedFourthDigit > 9) {
13415 encodedFourthDigit = 0;
13416 }
13417 let fourthDigit = encodedFourthDigit.toString();
13419 let encodedMIN1Last3 = ((encodedImsi[CSIM_IMSI_M_MIN1_BYTE + 1] & 0x03) << 8) +
13420 (encodedImsi[CSIM_IMSI_M_MIN1_BYTE] & 0xff);
13421 let min1Last3 = this.decodeIMSIValue(encodedMIN1Last3, 3);
13423 return mcc + mnc + min2 + min1First3 + fourthDigit + min1Last3;
13424 },
13426 /**
13427 * Decode IMSI Helper function
13428 * See 3GPP2 C.S0005 section 2.3.1.1
13429 */
13430 decodeIMSIValue: function(encoded, length) {
13431 let offset = length === 3 ? 111 : 11;
13432 let value = encoded + offset;
13434 for (let base = 10, temp = value, i = 0; i < length; i++) {
13435 if (temp % 10 === 0) {
13436 value -= base;
13437 }
13438 temp = Math.floor(value / base);
13439 base = base * 10;
13440 }
13442 let s = value.toString();
13443 while (s.length < length) {
13444 s = "0" + s;
13445 }
13447 return s;
13448 },
13450 /**
13451 * Read CDMAHOME for CSIM.
13452 * See 3GPP2 C.S0023 Sec. 3.4.8.
13453 */
13454 readCDMAHome: function() {
13455 let ICCIOHelper = this.context.ICCIOHelper;
13457 function callback(options) {
13458 let Buf = this.context.Buf;
13459 let GsmPDUHelper = this.context.GsmPDUHelper;
13461 let strLen = Buf.readInt32();
13462 let tempOctet = GsmPDUHelper.readHexOctet();
13463 cdmaHomeSystemId.push(((GsmPDUHelper.readHexOctet() & 0x7f) << 8) | tempOctet);
13464 tempOctet = GsmPDUHelper.readHexOctet();
13465 cdmaHomeNetworkId.push(((GsmPDUHelper.readHexOctet() & 0xff) << 8) | tempOctet);
13467 // Consuming the last octet: band class.
13468 Buf.seekIncoming(Buf.PDU_HEX_OCTET_SIZE);
13470 Buf.readStringDelimiter(strLen);
13471 if (options.p1 < options.totalRecords) {
13472 ICCIOHelper.loadNextRecord(options);
13473 } else {
13474 if (DEBUG) {
13475 this.context.debug("CDMAHome system id: " +
13476 JSON.stringify(cdmaHomeSystemId));
13477 this.context.debug("CDMAHome network id: " +
13478 JSON.stringify(cdmaHomeNetworkId));
13479 }
13480 this.context.RIL.cdmaHome = {
13481 systemId: cdmaHomeSystemId,
13482 networkId: cdmaHomeNetworkId
13483 };
13484 }
13485 }
13487 let cdmaHomeSystemId = [], cdmaHomeNetworkId = [];
13488 ICCIOHelper.loadLinearFixedEF({fileId: ICC_EF_CSIM_CDMAHOME,
13489 callback: callback.bind(this)});
13490 },
13492 /**
13493 * Read CDMA Service Table.
13494 * See 3GPP2 C.S0023 Sec. 3.4.18
13495 */
13496 readCST: function() {
13497 function callback() {
13498 let Buf = this.context.Buf;
13499 let RIL = this.context.RIL;
13501 let strLen = Buf.readInt32();
13502 // Each octet is encoded into two chars.
13503 RIL.iccInfoPrivate.cst =
13504 this.context.GsmPDUHelper.readHexOctetArray(strLen / 2);
13505 Buf.readStringDelimiter(strLen);
13507 if (DEBUG) {
13508 let str = "";
13509 for (let i = 0; i < RIL.iccInfoPrivate.cst.length; i++) {
13510 str += RIL.iccInfoPrivate.cst[i] + ", ";
13511 }
13512 this.context.debug("CST: " + str);
13513 }
13515 if (this.context.ICCUtilsHelper.isICCServiceAvailable("SPN")) {
13516 if (DEBUG) this.context.debug("SPN: SPN is available");
13517 this.readSPN();
13518 }
13519 }
13520 this.context.ICCIOHelper.loadTransparentEF({
13521 fileId: ICC_EF_CSIM_CST,
13522 callback: callback.bind(this)
13523 });
13524 },
13526 readSPN: function() {
13527 function callback() {
13528 let Buf = this.context.Buf;
13529 let strLen = Buf.readInt32();
13530 let octetLen = strLen / 2;
13532 let GsmPDUHelper = this.context.GsmPDUHelper;
13533 let displayCondition = GsmPDUHelper.readHexOctet();
13534 let codingScheme = GsmPDUHelper.readHexOctet();
13535 // Skip one octet: language indicator.
13536 Buf.seekIncoming(Buf.PDU_HEX_OCTET_SIZE);
13537 let readLen = 3;
13539 // SPN String ends up with 0xff.
13540 let userDataBuffer = [];
13542 while (readLen < octetLen) {
13543 let octet = GsmPDUHelper.readHexOctet();
13544 readLen++;
13545 if (octet == 0xff) {
13546 break;
13547 }
13548 userDataBuffer.push(octet);
13549 }
13551 this.context.BitBufferHelper.startRead(userDataBuffer);
13553 let CdmaPDUHelper = this.context.CdmaPDUHelper;
13554 let msgLen;
13555 switch (CdmaPDUHelper.getCdmaMsgEncoding(codingScheme)) {
13556 case PDU_DCS_MSG_CODING_7BITS_ALPHABET:
13557 msgLen = Math.floor(userDataBuffer.length * 8 / 7);
13558 break;
13559 case PDU_DCS_MSG_CODING_8BITS_ALPHABET:
13560 msgLen = userDataBuffer.length;
13561 break;
13562 case PDU_DCS_MSG_CODING_16BITS_ALPHABET:
13563 msgLen = Math.floor(userDataBuffer.length / 2);
13564 break;
13565 }
13567 let RIL = this.context.RIL;
13568 RIL.iccInfo.spn = CdmaPDUHelper.decodeCdmaPDUMsg(codingScheme, null, msgLen);
13569 if (DEBUG) {
13570 this.context.debug("CDMA SPN: " + RIL.iccInfo.spn +
13571 ", Display condition: " + displayCondition);
13572 }
13573 RIL.iccInfoPrivate.spnDisplayCondition = displayCondition;
13574 Buf.seekIncoming((octetLen - readLen) * Buf.PDU_HEX_OCTET_SIZE);
13575 Buf.readStringDelimiter(strLen);
13576 }
13578 this.context.ICCIOHelper.loadTransparentEF({
13579 fileId: ICC_EF_CSIM_SPN,
13580 callback: callback.bind(this)
13581 });
13582 }
13583 };
13585 /**
13586 * Helper functions for ICC utilities.
13587 */
13588 function ICCUtilsHelperObject(aContext) {
13589 this.context = aContext;
13590 }
13591 ICCUtilsHelperObject.prototype = {
13592 context: null,
13594 /**
13595 * Get network names by using EF_OPL and EF_PNN
13596 *
13597 * @See 3GPP TS 31.102 sec. 4.2.58 and sec. 4.2.59 for USIM,
13598 * 3GPP TS 51.011 sec. 10.3.41 and sec. 10.3.42 for SIM.
13599 *
13600 * @param mcc The mobile country code of the network.
13601 * @param mnc The mobile network code of the network.
13602 * @param lac The location area code of the network.
13603 */
13604 getNetworkNameFromICC: function(mcc, mnc, lac) {
13605 let RIL = this.context.RIL;
13606 let iccInfoPriv = RIL.iccInfoPrivate;
13607 let iccInfo = RIL.iccInfo;
13608 let pnnEntry;
13610 if (!mcc || !mnc || !lac) {
13611 return null;
13612 }
13614 // We won't get network name if there is no PNN file.
13615 if (!iccInfoPriv.PNN) {
13616 return null;
13617 }
13619 if (!iccInfoPriv.OPL) {
13620 // When OPL is not present:
13621 // According to 3GPP TS 31.102 Sec. 4.2.58 and 3GPP TS 51.011 Sec. 10.3.41,
13622 // If EF_OPL is not present, the first record in this EF is used for the
13623 // default network name when registered to the HPLMN.
13624 // If we haven't get pnnEntry assigned, we should try to assign default
13625 // value to it.
13626 if (mcc == iccInfo.mcc && mnc == iccInfo.mnc) {
13627 pnnEntry = iccInfoPriv.PNN[0];
13628 }
13629 } else {
13630 // According to 3GPP TS 31.102 Sec. 4.2.59 and 3GPP TS 51.011 Sec. 10.3.42,
13631 // the ME shall use this EF_OPL in association with the EF_PNN in place
13632 // of any network name stored within the ME's internal list and any network
13633 // name received when registered to the PLMN.
13634 let length = iccInfoPriv.OPL ? iccInfoPriv.OPL.length : 0;
13635 for (let i = 0; i < length; i++) {
13636 let opl = iccInfoPriv.OPL[i];
13637 // Try to match the MCC/MNC.
13638 if (mcc != opl.mcc || mnc != opl.mnc) {
13639 continue;
13640 }
13641 // Try to match the location area code. If current local area code is
13642 // covered by lac range that specified in the OPL entry, use the PNN
13643 // that specified in the OPL entry.
13644 if ((opl.lacTacStart === 0x0 && opl.lacTacEnd == 0xFFFE) ||
13645 (opl.lacTacStart <= lac && opl.lacTacEnd >= lac)) {
13646 if (opl.pnnRecordId === 0) {
13647 // See 3GPP TS 31.102 Sec. 4.2.59 and 3GPP TS 51.011 Sec. 10.3.42,
13648 // A value of '00' indicates that the name is to be taken from other
13649 // sources.
13650 return null;
13651 }
13652 pnnEntry = iccInfoPriv.PNN[opl.pnnRecordId - 1];
13653 break;
13654 }
13655 }
13656 }
13658 if (!pnnEntry) {
13659 return null;
13660 }
13662 // Return a new object to avoid global variable, PNN, be modified by accident.
13663 return { fullName: pnnEntry.fullName || "",
13664 shortName: pnnEntry.shortName || "" };
13665 },
13667 /**
13668 * This will compute the spnDisplay field of the network.
13669 * See TS 22.101 Annex A and TS 51.011 10.3.11 for details.
13670 *
13671 * @return True if some of iccInfo is changed in by this function.
13672 */
13673 updateDisplayCondition: function() {
13674 let RIL = this.context.RIL;
13676 // If EFspn isn't existed in SIM or it haven't been read yet, we should
13677 // just set isDisplayNetworkNameRequired = true and
13678 // isDisplaySpnRequired = false
13679 let iccInfo = RIL.iccInfo;
13680 let iccInfoPriv = RIL.iccInfoPrivate;
13681 let displayCondition = iccInfoPriv.spnDisplayCondition;
13682 let origIsDisplayNetworkNameRequired = iccInfo.isDisplayNetworkNameRequired;
13683 let origIsDisplaySPNRequired = iccInfo.isDisplaySpnRequired;
13685 if (displayCondition === undefined) {
13686 iccInfo.isDisplayNetworkNameRequired = true;
13687 iccInfo.isDisplaySpnRequired = false;
13688 } else if (RIL._isCdma) {
13689 // CDMA family display rule.
13690 let cdmaHome = RIL.cdmaHome;
13691 let cell = RIL.voiceRegistrationState.cell;
13692 let sid = cell && cell.cdmaSystemId;
13693 let nid = cell && cell.cdmaNetworkId;
13695 iccInfo.isDisplayNetworkNameRequired = false;
13697 // If display condition is 0x0, we don't even need to check network id
13698 // or system id.
13699 if (displayCondition === 0x0) {
13700 iccInfo.isDisplaySpnRequired = false;
13701 } else {
13702 // CDMA SPN Display condition dosen't specify whenever network name is
13703 // reqired.
13704 if (!cdmaHome ||
13705 !cdmaHome.systemId ||
13706 cdmaHome.systemId.length === 0 ||
13707 cdmaHome.systemId.length != cdmaHome.networkId.length ||
13708 !sid || !nid) {
13709 // CDMA Home haven't been ready, or we haven't got the system id and
13710 // network id of the network we register to, assuming we are in home
13711 // network.
13712 iccInfo.isDisplaySpnRequired = true;
13713 } else {
13714 // Determine if we are registered in the home service area.
13715 // System ID and Network ID are described in 3GPP2 C.S0005 Sec. 2.6.5.2.
13716 let inHomeArea = false;
13717 for (let i = 0; i < cdmaHome.systemId.length; i++) {
13718 let homeSid = cdmaHome.systemId[i],
13719 homeNid = cdmaHome.networkId[i];
13720 if (homeSid === 0 || homeNid === 0 // Reserved system id/network id
13721 || homeSid != sid) {
13722 continue;
13723 }
13724 // According to 3GPP2 C.S0005 Sec. 2.6.5.2, NID number 65535 means
13725 // all networks in the system should be considered as home.
13726 if (homeNid == 65535 || homeNid == nid) {
13727 inHomeArea = true;
13728 break;
13729 }
13730 }
13731 iccInfo.isDisplaySpnRequired = inHomeArea;
13732 }
13733 }
13734 } else {
13735 // GSM family display rule.
13736 let operatorMnc = RIL.operator.mnc;
13737 let operatorMcc = RIL.operator.mcc;
13739 // First detect if we are on HPLMN or one of the PLMN
13740 // specified by the SIM card.
13741 let isOnMatchingPlmn = false;
13743 // If the current network is the one defined as mcc/mnc
13744 // in SIM card, it's okay.
13745 if (iccInfo.mcc == operatorMcc && iccInfo.mnc == operatorMnc) {
13746 isOnMatchingPlmn = true;
13747 }
13749 // Test to see if operator's mcc/mnc match mcc/mnc of PLMN.
13750 if (!isOnMatchingPlmn && iccInfoPriv.SPDI) {
13751 let iccSpdi = iccInfoPriv.SPDI; // PLMN list
13752 for (let plmn in iccSpdi) {
13753 let plmnMcc = iccSpdi[plmn].mcc;
13754 let plmnMnc = iccSpdi[plmn].mnc;
13755 isOnMatchingPlmn = (plmnMcc == operatorMcc) && (plmnMnc == operatorMnc);
13756 if (isOnMatchingPlmn) {
13757 break;
13758 }
13759 }
13760 }
13762 if (isOnMatchingPlmn) {
13763 // The first bit of display condition tells us if we should display
13764 // registered PLMN.
13765 if (DEBUG) {
13766 this.context.debug("PLMN is HPLMN or PLMN " + "is in PLMN list");
13767 }
13769 // TS 31.102 Sec. 4.2.66 and TS 51.011 Sec. 10.3.50
13770 // EF_SPDI contains a list of PLMNs in which the Service Provider Name
13771 // shall be displayed.
13772 iccInfo.isDisplaySpnRequired = true;
13773 iccInfo.isDisplayNetworkNameRequired = (displayCondition & 0x01) !== 0;
13774 } else {
13775 // The second bit of display condition tells us if we should display
13776 // registered PLMN.
13777 if (DEBUG) {
13778 this.context.debug("PLMN isn't HPLMN and PLMN isn't in PLMN list");
13779 }
13781 // We didn't found the requirement of displaying network name if
13782 // current PLMN isn't HPLMN nor one of PLMN in SPDI. So we keep
13783 // isDisplayNetworkNameRequired false.
13784 iccInfo.isDisplayNetworkNameRequired = false;
13785 iccInfo.isDisplaySpnRequired = (displayCondition & 0x02) === 0;
13786 }
13787 }
13789 if (DEBUG) {
13790 this.context.debug("isDisplayNetworkNameRequired = " +
13791 iccInfo.isDisplayNetworkNameRequired);
13792 this.context.debug("isDisplaySpnRequired = " + iccInfo.isDisplaySpnRequired);
13793 }
13795 return ((origIsDisplayNetworkNameRequired !== iccInfo.isDisplayNetworkNameRequired) ||
13796 (origIsDisplaySPNRequired !== iccInfo.isDisplaySpnRequired));
13797 },
13799 decodeSimTlvs: function(tlvsLen) {
13800 let GsmPDUHelper = this.context.GsmPDUHelper;
13802 let index = 0;
13803 let tlvs = [];
13804 while (index < tlvsLen) {
13805 let simTlv = {
13806 tag : GsmPDUHelper.readHexOctet(),
13807 length : GsmPDUHelper.readHexOctet(),
13808 };
13809 simTlv.value = GsmPDUHelper.readHexOctetArray(simTlv.length);
13810 tlvs.push(simTlv);
13811 index += simTlv.length + 2; // The length of 'tag' and 'length' field.
13812 }
13813 return tlvs;
13814 },
13816 /**
13817 * Parse those TLVs and convert it to an object.
13818 */
13819 parsePbrTlvs: function(pbrTlvs) {
13820 let pbr = {};
13821 for (let i = 0; i < pbrTlvs.length; i++) {
13822 let pbrTlv = pbrTlvs[i];
13823 let anrIndex = 0;
13824 for (let j = 0; j < pbrTlv.value.length; j++) {
13825 let tlv = pbrTlv.value[j];
13826 let tagName = USIM_TAG_NAME[tlv.tag];
13828 // ANR could have multiple files. We save it as anr0, anr1,...etc.
13829 if (tlv.tag == ICC_USIM_EFANR_TAG) {
13830 tagName += anrIndex;
13831 anrIndex++;
13832 }
13833 pbr[tagName] = tlv;
13834 pbr[tagName].fileType = pbrTlv.tag;
13835 pbr[tagName].fileId = (tlv.value[0] << 8) | tlv.value[1];
13836 pbr[tagName].sfi = tlv.value[2];
13838 // For Type 2, the order of files is in the same order in IAP.
13839 if (pbrTlv.tag == ICC_USIM_TYPE2_TAG) {
13840 pbr[tagName].indexInIAP = j;
13841 }
13842 }
13843 }
13845 return pbr;
13846 },
13848 /**
13849 * Update the ICC information to RadioInterfaceLayer.
13850 */
13851 handleICCInfoChange: function() {
13852 let RIL = this.context.RIL;
13853 RIL.iccInfo.rilMessageType = "iccinfochange";
13854 RIL.sendChromeMessage(RIL.iccInfo);
13855 },
13857 /**
13858 * Get whether specificed (U)SIM service is available.
13859 *
13860 * @param geckoService
13861 * Service name like "ADN", "BDN", etc.
13862 *
13863 * @return true if the service is enabled, false otherwise.
13864 */
13865 isICCServiceAvailable: function(geckoService) {
13866 let RIL = this.context.RIL;
13867 let serviceTable = RIL._isCdma ? RIL.iccInfoPrivate.cst:
13868 RIL.iccInfoPrivate.sst;
13869 let index, bitmask;
13870 if (RIL.appType == CARD_APPTYPE_SIM || RIL.appType == CARD_APPTYPE_RUIM) {
13871 /**
13872 * Service id is valid in 1..N, and 2 bits are used to code each service.
13873 *
13874 * +----+-- --+----+----+
13875 * | b8 | ... | b2 | b1 |
13876 * +----+-- --+----+----+
13877 *
13878 * b1 = 0, service not allocated.
13879 * 1, service allocated.
13880 * b2 = 0, service not activatd.
13881 * 1, service allocated.
13882 *
13883 * @see 3GPP TS 51.011 10.3.7.
13884 */
13885 let simService;
13886 if (RIL.appType == CARD_APPTYPE_SIM) {
13887 simService = GECKO_ICC_SERVICES.sim[geckoService];
13888 } else {
13889 simService = GECKO_ICC_SERVICES.ruim[geckoService];
13890 }
13891 if (!simService) {
13892 return false;
13893 }
13894 simService -= 1;
13895 index = Math.floor(simService / 4);
13896 bitmask = 2 << ((simService % 4) << 1);
13897 } else if (RIL.appType == CARD_APPTYPE_USIM) {
13898 /**
13899 * Service id is valid in 1..N, and 1 bit is used to code each service.
13900 *
13901 * +----+-- --+----+----+
13902 * | b8 | ... | b2 | b1 |
13903 * +----+-- --+----+----+
13904 *
13905 * b1 = 0, service not avaiable.
13906 * 1, service available.
13907 * b2 = 0, service not avaiable.
13908 * 1, service available.
13909 *
13910 * @see 3GPP TS 31.102 4.2.8.
13911 */
13912 let usimService = GECKO_ICC_SERVICES.usim[geckoService];
13913 if (!usimService) {
13914 return false;
13915 }
13916 usimService -= 1;
13917 index = Math.floor(usimService / 8);
13918 bitmask = 1 << ((usimService % 8) << 0);
13919 }
13921 return (serviceTable !== null) &&
13922 (index < serviceTable.length) &&
13923 ((serviceTable[index] & bitmask) !== 0);
13924 },
13926 /**
13927 * Check if the string is of GSM default 7-bit coded alphabets with bit 8
13928 * set to 0.
13929 *
13930 * @param str String to be checked.
13931 */
13932 isGsm8BitAlphabet: function(str) {
13933 if (!str) {
13934 return false;
13935 }
13937 const langTable = PDU_NL_LOCKING_SHIFT_TABLES[PDU_NL_IDENTIFIER_DEFAULT];
13938 const langShiftTable = PDU_NL_SINGLE_SHIFT_TABLES[PDU_NL_IDENTIFIER_DEFAULT];
13940 for (let i = 0; i < str.length; i++) {
13941 let c = str.charAt(i);
13942 let octet = langTable.indexOf(c);
13943 if (octet == -1) {
13944 octet = langShiftTable.indexOf(c);
13945 if (octet == -1) {
13946 return false;
13947 }
13948 }
13949 }
13951 return true;
13952 },
13954 /**
13955 * Parse MCC/MNC from IMSI. If there is no available value for the length of
13956 * mnc, it will use the data in MCC table to parse.
13957 *
13958 * @param imsi
13959 * The imsi of icc.
13960 * @param mncLength [optional]
13961 * The length of mnc.
13962 *
13963 * @return An object contains the parsing result of mcc and mnc.
13964 * Or null if any error occurred.
13965 */
13966 parseMccMncFromImsi: function(imsi, mncLength) {
13967 if (!imsi) {
13968 return null;
13969 }
13971 // MCC is the first 3 digits of IMSI.
13972 let mcc = imsi.substr(0,3);
13973 if (!mncLength) {
13974 // Check the MCC table to decide the length of MNC.
13975 let index = MCC_TABLE_FOR_MNC_LENGTH_IS_3.indexOf(mcc);
13976 mncLength = (index !== -1) ? 3 : 2;
13977 }
13978 let mnc = imsi.substr(3, mncLength);
13979 if (DEBUG) {
13980 this.context.debug("IMSI: " + imsi + " MCC: " + mcc + " MNC: " + mnc);
13981 }
13983 return { mcc: mcc, mnc: mnc};
13984 },
13985 };
13987 /**
13988 * Helper for ICC Contacts.
13989 */
13990 function ICCContactHelperObject(aContext) {
13991 this.context = aContext;
13992 }
13993 ICCContactHelperObject.prototype = {
13994 context: null,
13996 /**
13997 * Helper function to check DF_PHONEBOOK.
13998 */
13999 hasDfPhoneBook: function(appType) {
14000 switch (appType) {
14001 case CARD_APPTYPE_SIM:
14002 return false;
14003 case CARD_APPTYPE_USIM:
14004 return true;
14005 case CARD_APPTYPE_RUIM:
14006 let ICCUtilsHelper = this.context.ICCUtilsHelper;
14007 return ICCUtilsHelper.isICCServiceAvailable("ENHANCED_PHONEBOOK");
14008 default:
14009 return false;
14010 }
14011 },
14013 /**
14014 * Helper function to read ICC contacts.
14015 *
14016 * @param appType One of CARD_APPTYPE_*.
14017 * @param contactType "adn" or "fdn".
14018 * @param onsuccess Callback to be called when success.
14019 * @param onerror Callback to be called when error.
14020 */
14021 readICCContacts: function(appType, contactType, onsuccess, onerror) {
14022 let ICCRecordHelper = this.context.ICCRecordHelper;
14024 switch (contactType) {
14025 case "adn":
14026 if (!this.hasDfPhoneBook(appType)) {
14027 ICCRecordHelper.readADNLike(ICC_EF_ADN, onsuccess, onerror);
14028 } else {
14029 this.readUSimContacts(onsuccess, onerror);
14030 }
14031 break;
14032 case "fdn":
14033 ICCRecordHelper.readADNLike(ICC_EF_FDN, onsuccess, onerror);
14034 break;
14035 default:
14036 if (DEBUG) {
14037 this.context.debug("Unsupported contactType :" + contactType);
14038 }
14039 onerror(CONTACT_ERR_CONTACT_TYPE_NOT_SUPPORTED);
14040 break;
14041 }
14042 },
14044 /**
14045 * Helper function to find free contact record.
14046 *
14047 * @param appType One of CARD_APPTYPE_*.
14048 * @param contactType "adn" or "fdn".
14049 * @param onsuccess Callback to be called when success.
14050 * @param onerror Callback to be called when error.
14051 */
14052 findFreeICCContact: function(appType, contactType, onsuccess, onerror) {
14053 let ICCRecordHelper = this.context.ICCRecordHelper;
14055 switch (contactType) {
14056 case "adn":
14057 if (!this.hasDfPhoneBook(appType)) {
14058 ICCRecordHelper.findFreeRecordId(ICC_EF_ADN, onsuccess.bind(null, 0), onerror);
14059 } else {
14060 let gotPbrCb = function gotPbrCb(pbrs) {
14061 this.findUSimFreeADNRecordId(pbrs, onsuccess, onerror);
14062 }.bind(this);
14064 ICCRecordHelper.readPBR(gotPbrCb, onerror);
14065 }
14066 break;
14067 case "fdn":
14068 ICCRecordHelper.findFreeRecordId(ICC_EF_FDN, onsuccess.bind(null, 0), onerror);
14069 break;
14070 default:
14071 if (DEBUG) {
14072 this.context.debug("Unsupported contactType :" + contactType);
14073 }
14074 onerror(CONTACT_ERR_CONTACT_TYPE_NOT_SUPPORTED);
14075 break;
14076 }
14077 },
14079 /**
14080 * Find free ADN record id in USIM.
14081 *
14082 * @param pbrs All Phonebook Reference Files read.
14083 * @param onsuccess Callback to be called when success.
14084 * @param onerror Callback to be called when error.
14085 */
14086 findUSimFreeADNRecordId: function(pbrs, onsuccess, onerror) {
14087 let ICCRecordHelper = this.context.ICCRecordHelper;
14089 (function findFreeRecordId(pbrIndex) {
14090 if (pbrIndex >= pbrs.length) {
14091 if (DEBUG) {
14092 this.context.debug(CONTACT_ERR_NO_FREE_RECORD_FOUND);
14093 }
14094 onerror(CONTACT_ERR_NO_FREE_RECORD_FOUND);
14095 return;
14096 }
14098 let pbr = pbrs[pbrIndex];
14099 ICCRecordHelper.findFreeRecordId(
14100 pbr.adn.fileId,
14101 onsuccess.bind(this, pbrIndex),
14102 findFreeRecordId.bind(null, pbrIndex + 1));
14103 })(0);
14104 },
14106 /**
14107 * Helper function to add a new ICC contact.
14108 *
14109 * @param appType One of CARD_APPTYPE_*.
14110 * @param contactType "adn" or "fdn".
14111 * @param contact The contact will be added.
14112 * @param pin2 PIN2 is required for FDN.
14113 * @param onsuccess Callback to be called when success.
14114 * @param onerror Callback to be called when error.
14115 */
14116 addICCContact: function(appType, contactType, contact, pin2, onsuccess, onerror) {
14117 let foundFreeCb = (function foundFreeCb(pbrIndex, recordId) {
14118 contact.pbrIndex = pbrIndex;
14119 contact.recordId = recordId;
14120 this.updateICCContact(appType, contactType, contact, pin2, onsuccess, onerror);
14121 }).bind(this);
14123 // Find free record first.
14124 this.findFreeICCContact(appType, contactType, foundFreeCb, onerror);
14125 },
14127 /**
14128 * Helper function to update ICC contact.
14129 *
14130 * @param appType One of CARD_APPTYPE_*.
14131 * @param contactType "adn" or "fdn".
14132 * @param contact The contact will be updated.
14133 * @param pin2 PIN2 is required for FDN.
14134 * @param onsuccess Callback to be called when success.
14135 * @param onerror Callback to be called when error.
14136 */
14137 updateICCContact: function(appType, contactType, contact, pin2, onsuccess, onerror) {
14138 let ICCRecordHelper = this.context.ICCRecordHelper;
14140 switch (contactType) {
14141 case "adn":
14142 if (!this.hasDfPhoneBook(appType)) {
14143 ICCRecordHelper.updateADNLike(ICC_EF_ADN, contact, null, onsuccess, onerror);
14144 } else {
14145 this.updateUSimContact(contact, onsuccess, onerror);
14146 }
14147 break;
14148 case "fdn":
14149 if (!pin2) {
14150 onerror(GECKO_ERROR_SIM_PIN2);
14151 return;
14152 }
14153 ICCRecordHelper.updateADNLike(ICC_EF_FDN, contact, pin2, onsuccess, onerror);
14154 break;
14155 default:
14156 if (DEBUG) {
14157 this.context.debug("Unsupported contactType :" + contactType);
14158 }
14159 onerror(CONTACT_ERR_CONTACT_TYPE_NOT_SUPPORTED);
14160 break;
14161 }
14162 },
14164 /**
14165 * Read contacts from USIM.
14166 *
14167 * @param onsuccess Callback to be called when success.
14168 * @param onerror Callback to be called when error.
14169 */
14170 readUSimContacts: function(onsuccess, onerror) {
14171 let gotPbrCb = function gotPbrCb(pbrs) {
14172 this.readAllPhonebookSets(pbrs, onsuccess, onerror);
14173 }.bind(this);
14175 this.context.ICCRecordHelper.readPBR(gotPbrCb, onerror);
14176 },
14178 /**
14179 * Read all Phonebook sets.
14180 *
14181 * @param pbrs All Phonebook Reference Files read.
14182 * @param onsuccess Callback to be called when success.
14183 * @param onerror Callback to be called when error.
14184 */
14185 readAllPhonebookSets: function(pbrs, onsuccess, onerror) {
14186 let allContacts = [], pbrIndex = 0;
14187 let readPhonebook = function readPhonebook(contacts) {
14188 if (contacts) {
14189 allContacts = allContacts.concat(contacts);
14190 }
14192 let cLen = contacts ? contacts.length : 0;
14193 for (let i = 0; i < cLen; i++) {
14194 contacts[i].pbrIndex = pbrIndex;
14195 }
14197 pbrIndex++;
14198 if (pbrIndex >= pbrs.length) {
14199 if (onsuccess) {
14200 onsuccess(allContacts);
14201 }
14202 return;
14203 }
14205 this.readPhonebookSet(pbrs[pbrIndex], readPhonebook, onerror);
14206 }.bind(this);
14208 this.readPhonebookSet(pbrs[pbrIndex], readPhonebook, onerror);
14209 },
14211 /**
14212 * Read from Phonebook Reference File.
14213 *
14214 * @param pbr Phonebook Reference File to be read.
14215 * @param onsuccess Callback to be called when success.
14216 * @param onerror Callback to be called when error.
14217 */
14218 readPhonebookSet: function(pbr, onsuccess, onerror) {
14219 let gotAdnCb = function gotAdnCb(contacts) {
14220 this.readSupportedPBRFields(pbr, contacts, onsuccess, onerror);
14221 }.bind(this);
14223 this.context.ICCRecordHelper.readADNLike(pbr.adn.fileId, gotAdnCb, onerror);
14224 },
14226 /**
14227 * Read supported Phonebook fields.
14228 *
14229 * @param pbr Phone Book Reference file.
14230 * @param contacts Contacts stored on ICC.
14231 * @param onsuccess Callback to be called when success.
14232 * @param onerror Callback to be called when error.
14233 */
14234 readSupportedPBRFields: function(pbr, contacts, onsuccess, onerror) {
14235 let fieldIndex = 0;
14236 (function readField() {
14237 let field = USIM_PBR_FIELDS[fieldIndex];
14238 fieldIndex += 1;
14239 if (!field) {
14240 if (onsuccess) {
14241 onsuccess(contacts);
14242 }
14243 return;
14244 }
14246 this.readPhonebookField(pbr, contacts, field, readField.bind(this), onerror);
14247 }).call(this);
14248 },
14250 /**
14251 * Read Phonebook field.
14252 *
14253 * @param pbr The phonebook reference file.
14254 * @param contacts Contacts stored on ICC.
14255 * @param field Phonebook field to be retrieved.
14256 * @param onsuccess Callback to be called when success.
14257 * @param onerror Callback to be called when error.
14258 */
14259 readPhonebookField: function(pbr, contacts, field, onsuccess, onerror) {
14260 if (!pbr[field]) {
14261 if (onsuccess) {
14262 onsuccess(contacts);
14263 }
14264 return;
14265 }
14267 (function doReadContactField(n) {
14268 if (n >= contacts.length) {
14269 // All contact's fields are read.
14270 if (onsuccess) {
14271 onsuccess(contacts);
14272 }
14273 return;
14274 }
14276 // get n-th contact's field.
14277 this.readContactField(pbr, contacts[n], field,
14278 doReadContactField.bind(this, n + 1), onerror);
14279 }).call(this, 0);
14280 },
14282 /**
14283 * Read contact's field from USIM.
14284 *
14285 * @param pbr The phonebook reference file.
14286 * @param contact The contact needs to get field.
14287 * @param field Phonebook field to be retrieved.
14288 * @param onsuccess Callback to be called when success.
14289 * @param onerror Callback to be called when error.
14290 */
14291 readContactField: function(pbr, contact, field, onsuccess, onerror) {
14292 let gotRecordIdCb = function gotRecordIdCb(recordId) {
14293 if (recordId == 0xff) {
14294 if (onsuccess) {
14295 onsuccess();
14296 }
14297 return;
14298 }
14300 let fileId = pbr[field].fileId;
14301 let fileType = pbr[field].fileType;
14302 let gotFieldCb = function gotFieldCb(value) {
14303 if (value) {
14304 // Move anr0 anr1,.. into anr[].
14305 if (field.startsWith(USIM_PBR_ANR)) {
14306 if (!contact[USIM_PBR_ANR]) {
14307 contact[USIM_PBR_ANR] = [];
14308 }
14309 contact[USIM_PBR_ANR].push(value);
14310 } else {
14311 contact[field] = value;
14312 }
14313 }
14315 if (onsuccess) {
14316 onsuccess();
14317 }
14318 }.bind(this);
14320 let ICCRecordHelper = this.context.ICCRecordHelper;
14321 // Detect EF to be read, for anr, it could have anr0, anr1,...
14322 let ef = field.startsWith(USIM_PBR_ANR) ? USIM_PBR_ANR : field;
14323 switch (ef) {
14324 case USIM_PBR_EMAIL:
14325 ICCRecordHelper.readEmail(fileId, fileType, recordId, gotFieldCb, onerror);
14326 break;
14327 case USIM_PBR_ANR:
14328 ICCRecordHelper.readANR(fileId, fileType, recordId, gotFieldCb, onerror);
14329 break;
14330 default:
14331 if (DEBUG) {
14332 this.context.debug("Unsupported field :" + field);
14333 }
14334 onerror(CONTACT_ERR_FIELD_NOT_SUPPORTED);
14335 break;
14336 }
14337 }.bind(this);
14339 this.getContactFieldRecordId(pbr, contact, field, gotRecordIdCb, onerror);
14340 },
14342 /**
14343 * Get the recordId.
14344 *
14345 * If the fileType of field is ICC_USIM_TYPE1_TAG, use corresponding ADN recordId.
14346 * otherwise get the recordId from IAP.
14347 *
14348 * @see TS 131.102, clause 4.4.2.2
14349 *
14350 * @param pbr The phonebook reference file.
14351 * @param contact The contact will be updated.
14352 * @param onsuccess Callback to be called when success.
14353 * @param onerror Callback to be called when error.
14354 */
14355 getContactFieldRecordId: function(pbr, contact, field, onsuccess, onerror) {
14356 if (pbr[field].fileType == ICC_USIM_TYPE1_TAG) {
14357 // If the file type is ICC_USIM_TYPE1_TAG, use corresponding ADN recordId.
14358 if (onsuccess) {
14359 onsuccess(contact.recordId);
14360 }
14361 } else if (pbr[field].fileType == ICC_USIM_TYPE2_TAG) {
14362 // If the file type is ICC_USIM_TYPE2_TAG, the recordId shall be got from IAP.
14363 let gotIapCb = function gotIapCb(iap) {
14364 let indexInIAP = pbr[field].indexInIAP;
14365 let recordId = iap[indexInIAP];
14367 if (onsuccess) {
14368 onsuccess(recordId);
14369 }
14370 }.bind(this);
14372 this.context.ICCRecordHelper.readIAP(pbr.iap.fileId, contact.recordId,
14373 gotIapCb, onerror);
14374 } else {
14375 if (DEBUG) {
14376 this.context.debug("USIM PBR files in Type 3 format are not supported.");
14377 }
14378 onerror(CONTACT_ERR_REQUEST_NOT_SUPPORTED);
14379 }
14380 },
14382 /**
14383 * Update USIM contact.
14384 *
14385 * @param contact The contact will be updated.
14386 * @param onsuccess Callback to be called when success.
14387 * @param onerror Callback to be called when error.
14388 */
14389 updateUSimContact: function(contact, onsuccess, onerror) {
14390 let gotPbrCb = function gotPbrCb(pbrs) {
14391 let pbr = pbrs[contact.pbrIndex];
14392 if (!pbr) {
14393 if (DEBUG) {
14394 this.context.debug(CONTACT_ERR_CANNOT_ACCESS_PHONEBOOK);
14395 }
14396 onerror(CONTACT_ERR_CANNOT_ACCESS_PHONEBOOK);
14397 return;
14398 }
14399 this.updatePhonebookSet(pbr, contact, onsuccess, onerror);
14400 }.bind(this);
14402 this.context.ICCRecordHelper.readPBR(gotPbrCb, onerror);
14403 },
14405 /**
14406 * Update fields in Phonebook Reference File.
14407 *
14408 * @param pbr Phonebook Reference File to be read.
14409 * @param onsuccess Callback to be called when success.
14410 * @param onerror Callback to be called when error.
14411 */
14412 updatePhonebookSet: function(pbr, contact, onsuccess, onerror) {
14413 let updateAdnCb = function() {
14414 this.updateSupportedPBRFields(pbr, contact, onsuccess, onerror);
14415 }.bind(this);
14417 this.context.ICCRecordHelper.updateADNLike(pbr.adn.fileId, contact, null,
14418 updateAdnCb, onerror);
14419 },
14421 /**
14422 * Update supported Phonebook fields.
14423 *
14424 * @param pbr Phone Book Reference file.
14425 * @param contact Contact to be updated.
14426 * @param onsuccess Callback to be called when success.
14427 * @param onerror Callback to be called when error.
14428 */
14429 updateSupportedPBRFields: function(pbr, contact, onsuccess, onerror) {
14430 let fieldIndex = 0;
14431 (function updateField() {
14432 let field = USIM_PBR_FIELDS[fieldIndex];
14433 fieldIndex += 1;
14434 if (!field) {
14435 if (onsuccess) {
14436 onsuccess();
14437 }
14438 return;
14439 }
14441 // Check if PBR has this field.
14442 if (!pbr[field]) {
14443 updateField.call(this);
14444 return;
14445 }
14447 this.updateContactField(pbr, contact, field, updateField.bind(this), onerror);
14448 }).call(this);
14449 },
14451 /**
14452 * Update contact's field from USIM.
14453 *
14454 * @param pbr The phonebook reference file.
14455 * @param contact The contact needs to be updated.
14456 * @param field Phonebook field to be updated.
14457 * @param onsuccess Callback to be called when success.
14458 * @param onerror Callback to be called when error.
14459 */
14460 updateContactField: function(pbr, contact, field, onsuccess, onerror) {
14461 if (pbr[field].fileType === ICC_USIM_TYPE1_TAG) {
14462 this.updateContactFieldType1(pbr, contact, field, onsuccess, onerror);
14463 } else if (pbr[field].fileType === ICC_USIM_TYPE2_TAG) {
14464 this.updateContactFieldType2(pbr, contact, field, onsuccess, onerror);
14465 } else {
14466 if (DEBUG) {
14467 this.context.debug("USIM PBR files in Type 3 format are not supported.");
14468 }
14469 onerror(CONTACT_ERR_REQUEST_NOT_SUPPORTED);
14470 }
14471 },
14473 /**
14474 * Update Type 1 USIM contact fields.
14475 *
14476 * @param pbr The phonebook reference file.
14477 * @param contact The contact needs to be updated.
14478 * @param field Phonebook field to be updated.
14479 * @param onsuccess Callback to be called when success.
14480 * @param onerror Callback to be called when error.
14481 */
14482 updateContactFieldType1: function(pbr, contact, field, onsuccess, onerror) {
14483 let ICCRecordHelper = this.context.ICCRecordHelper;
14485 if (field === USIM_PBR_EMAIL) {
14486 ICCRecordHelper.updateEmail(pbr, contact.recordId, contact.email, null, onsuccess, onerror);
14487 } else if (field === USIM_PBR_ANR0) {
14488 let anr = Array.isArray(contact.anr) ? contact.anr[0] : null;
14489 ICCRecordHelper.updateANR(pbr, contact.recordId, anr, null, onsuccess, onerror);
14490 } else {
14491 if (DEBUG) {
14492 this.context.debug("Unsupported field :" + field);
14493 }
14494 onerror(CONTACT_ERR_FIELD_NOT_SUPPORTED);
14495 }
14496 },
14498 /**
14499 * Update Type 2 USIM contact fields.
14500 *
14501 * @param pbr The phonebook reference file.
14502 * @param contact The contact needs to be updated.
14503 * @param field Phonebook field to be updated.
14504 * @param onsuccess Callback to be called when success.
14505 * @param onerror Callback to be called when error.
14506 */
14507 updateContactFieldType2: function(pbr, contact, field, onsuccess, onerror) {
14508 let ICCRecordHelper = this.context.ICCRecordHelper;
14510 // Case 1 : EF_IAP[adnRecordId] doesn't have a value(0xff)
14511 // Find a free recordId for EF_field
14512 // Update field with that free recordId.
14513 // Update IAP.
14514 //
14515 // Case 2: EF_IAP[adnRecordId] has a value
14516 // update EF_field[iap[field.indexInIAP]]
14518 let gotIapCb = function gotIapCb(iap) {
14519 let recordId = iap[pbr[field].indexInIAP];
14520 if (recordId === 0xff) {
14521 // If the value in IAP[index] is 0xff, which means the contact stored on
14522 // the SIM doesn't have the additional attribute (email or anr).
14523 // So if the contact to be updated doesn't have the attribute either,
14524 // we don't have to update it.
14525 if ((field === USIM_PBR_EMAIL && contact.email) ||
14526 (field === USIM_PBR_ANR0 &&
14527 (Array.isArray(contact.anr) && contact.anr[0]))) {
14528 // Case 1.
14529 this.addContactFieldType2(pbr, contact, field, onsuccess, onerror);
14530 } else {
14531 if (onsuccess) {
14532 onsuccess();
14533 }
14534 }
14535 return;
14536 }
14538 // Case 2.
14539 if (field === USIM_PBR_EMAIL) {
14540 ICCRecordHelper.updateEmail(pbr, recordId, contact.email, contact.recordId, onsuccess, onerror);
14541 } else if (field === USIM_PBR_ANR0) {
14542 let anr = Array.isArray(contact.anr) ? contact.anr[0] : null;
14543 ICCRecordHelper.updateANR(pbr, recordId, anr, contact.recordId, onsuccess, onerror);
14544 } else {
14545 if (DEBUG) {
14546 this.context.debug("Unsupported field :" + field);
14547 }
14548 onerror(CONTACT_ERR_FIELD_NOT_SUPPORTED);
14549 }
14551 }.bind(this);
14553 ICCRecordHelper.readIAP(pbr.iap.fileId, contact.recordId, gotIapCb, onerror);
14554 },
14556 /**
14557 * Add Type 2 USIM contact fields.
14558 *
14559 * @param pbr The phonebook reference file.
14560 * @param contact The contact needs to be updated.
14561 * @param field Phonebook field to be updated.
14562 * @param onsuccess Callback to be called when success.
14563 * @param onerror Callback to be called when error.
14564 */
14565 addContactFieldType2: function(pbr, contact, field, onsuccess, onerror) {
14566 let ICCRecordHelper = this.context.ICCRecordHelper;
14568 let successCb = function successCb(recordId) {
14569 let updateCb = function updateCb() {
14570 this.updateContactFieldIndexInIAP(pbr, contact.recordId, field, recordId, onsuccess, onerror);
14571 }.bind(this);
14573 if (field === USIM_PBR_EMAIL) {
14574 ICCRecordHelper.updateEmail(pbr, recordId, contact.email, contact.recordId, updateCb, onerror);
14575 } else if (field === USIM_PBR_ANR0) {
14576 ICCRecordHelper.updateANR(pbr, recordId, contact.anr[0], contact.recordId, updateCb, onerror);
14577 }
14578 }.bind(this);
14580 let errorCb = function errorCb(errorMsg) {
14581 if (DEBUG) {
14582 this.context.debug(errorMsg + " USIM field " + field);
14583 }
14584 onerror(errorMsg);
14585 }.bind(this);
14587 ICCRecordHelper.findFreeRecordId(pbr[field].fileId, successCb, errorCb);
14588 },
14590 /**
14591 * Update IAP value.
14592 *
14593 * @param pbr The phonebook reference file.
14594 * @param recordNumber The record identifier of EF_IAP.
14595 * @param field Phonebook field.
14596 * @param value The value of 'field' in IAP.
14597 * @param onsuccess Callback to be called when success.
14598 * @param onerror Callback to be called when error.
14599 *
14600 */
14601 updateContactFieldIndexInIAP: function(pbr, recordNumber, field, value, onsuccess, onerror) {
14602 let ICCRecordHelper = this.context.ICCRecordHelper;
14604 let gotIAPCb = function gotIAPCb(iap) {
14605 iap[pbr[field].indexInIAP] = value;
14606 ICCRecordHelper.updateIAP(pbr.iap.fileId, recordNumber, iap, onsuccess, onerror);
14607 }.bind(this);
14608 ICCRecordHelper.readIAP(pbr.iap.fileId, recordNumber, gotIAPCb, onerror);
14609 },
14610 };
14612 /**
14613 * Global stuff.
14614 */
14616 function Context(aClientId) {
14617 this.clientId = aClientId;
14619 this.Buf = new BufObject(this);
14620 this.Buf.init();
14622 this.RIL = new RilObject(this);
14623 this.RIL.initRILState();
14624 }
14625 Context.prototype = {
14626 clientId: null,
14627 Buf: null,
14628 RIL: null,
14630 debug: function(aMessage) {
14631 GLOBAL.debug("[" + this.clientId + "] " + aMessage);
14632 }
14633 };
14635 (function() {
14636 let lazySymbols = [
14637 "BerTlvHelper", "BitBufferHelper", "CdmaPDUHelper",
14638 "ComprehensionTlvHelper", "GsmPDUHelper", "ICCContactHelper",
14639 "ICCFileHelper", "ICCIOHelper", "ICCPDUHelper", "ICCRecordHelper",
14640 "ICCUtilsHelper", "RuimRecordHelper", "SimRecordHelper",
14641 "StkCommandParamsFactory", "StkProactiveCmdHelper",
14642 ];
14644 for (let i = 0; i < lazySymbols.length; i++) {
14645 let symbol = lazySymbols[i];
14646 Object.defineProperty(Context.prototype, symbol, {
14647 get: function() {
14648 let real = new GLOBAL[symbol + "Object"](this);
14649 Object.defineProperty(this, symbol, {
14650 value: real,
14651 enumerable: true
14652 });
14653 return real;
14654 },
14655 configurable: true,
14656 enumerable: true
14657 });
14658 }
14659 })();
14661 let ContextPool = {
14662 _contexts: [],
14664 handleRilMessage: function(aClientId, aUint8Array) {
14665 let context = this._contexts[aClientId];
14666 context.Buf.processIncoming(aUint8Array);
14667 },
14669 handleChromeMessage: function(aMessage) {
14670 let clientId = aMessage.rilMessageClientId;
14671 if (clientId != null) {
14672 let context = this._contexts[clientId];
14673 context.RIL.handleChromeMessage(aMessage);
14674 return;
14675 }
14677 if (DEBUG) debug("Received global chrome message " + JSON.stringify(aMessage));
14678 let method = this[aMessage.rilMessageType];
14679 if (typeof method != "function") {
14680 if (DEBUG) {
14681 debug("Don't know what to do");
14682 }
14683 return;
14684 }
14685 method.call(this, aMessage);
14686 },
14688 setInitialOptions: function(aOptions) {
14689 DEBUG = DEBUG_WORKER || aOptions.debug;
14690 RIL_EMERGENCY_NUMBERS = aOptions.rilEmergencyNumbers;
14691 RIL_CELLBROADCAST_DISABLED = aOptions.cellBroadcastDisabled;
14692 RIL_CLIR_MODE = aOptions.clirMode;
14694 let quirks = aOptions.quirks;
14695 RILQUIRKS_CALLSTATE_EXTRA_UINT32 = quirks.callstateExtraUint32;
14696 RILQUIRKS_V5_LEGACY = quirks.v5Legacy;
14697 RILQUIRKS_REQUEST_USE_DIAL_EMERGENCY_CALL = quirks.requestUseDialEmergencyCall;
14698 RILQUIRKS_SIM_APP_STATE_EXTRA_FIELDS = quirks.simAppStateExtraFields;
14699 RILQUIRKS_EXTRA_UINT32_2ND_CALL = quirks.extraUint2ndCall;
14700 RILQUIRKS_HAVE_QUERY_ICC_LOCK_RETRY_COUNT = quirks.haveQueryIccLockRetryCount;
14701 RILQUIRKS_SEND_STK_PROFILE_DOWNLOAD = quirks.sendStkProfileDownload;
14702 RILQUIRKS_DATA_REGISTRATION_ON_DEMAND = quirks.dataRegistrationOnDemand;
14703 },
14705 registerClient: function(aOptions) {
14706 let clientId = aOptions.clientId;
14707 this._contexts[clientId] = new Context(clientId);
14708 },
14709 };
14711 function onRILMessage(aClientId, aUint8Array) {
14712 ContextPool.handleRilMessage(aClientId, aUint8Array);
14713 }
14715 onmessage = function onmessage(event) {
14716 ContextPool.handleChromeMessage(event.data);
14717 };
14719 onerror = function onerror(event) {
14720 if (DEBUG) debug("onerror" + event.message + "\n");
14721 };