Bug 1122376 - Support read SIM contact dialling number exceed 20 digits. r=echen
authorJohn Dai <jdai@mozilla.com>
Sun, 20 Sep 2015 19:48:00 +0200
changeset 263564 23bd00cf364f196882e0ba81e78ee4d94792dde0
parent 263563 52fda291bb7ed48b3ff68e198ad30fd03a9ba071
child 263565 211edf7e53aa460bcf63909ed882b3735565a0d3
push id65352
push userkwierso@gmail.com
push dateMon, 21 Sep 2015 16:52:59 +0000
treeherdermozilla-inbound@ee92eb117e92 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersechen
bugs1122376
milestone43.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1122376 - Support read SIM contact dialling number exceed 20 digits. r=echen
dom/icc/tests/marionette/test_icc_contact_read.js
dom/system/gonk/ril_consts.js
dom/system/gonk/ril_worker.js
dom/system/gonk/tests/test_ril_worker_icc_ICCRecordHelper.js
--- a/dom/icc/tests/marionette/test_icc_contact_read.js
+++ b/dom/icc/tests/marionette/test_icc_contact_read.js
@@ -4,18 +4,19 @@
 MARIONETTE_TIMEOUT = 60000;
 MARIONETTE_HEAD_JS = "head.js";
 
 function testReadContacts(aIcc, aType) {
   log("testReadContacts: type=" + aType);
   let iccId = aIcc.iccInfo.iccid;
   return aIcc.readContacts(aType)
     .then((aResult) => {
+
       is(Array.isArray(aResult), true);
-      is(aResult.length, 4, "Check contact number.");
+      is(aResult.length, 6, "Check contact number.");
 
       // Alpha Id(Encoded with GSM 8 bit): "Mozilla", Dialling Number: 15555218201
       is(aResult[0].name[0], "Mozilla");
       is(aResult[0].tel[0].value, "15555218201");
       is(aResult[0].id, iccId + "1");
 
       // Alpha Id(Encoded with UCS2 0x80: "Saßê\u9ec3", Dialling Number: 15555218202
       is(aResult[1].name[0], "Saßê黃");
@@ -26,16 +27,28 @@ function testReadContacts(aIcc, aType) {
       is(aResult[2].name[0], "Fire 火");
       is(aResult[2].tel[0].value, "15555218203");
       is(aResult[2].id, iccId + "3");
 
       // Alpha Id(Encoded with UCS2 0x82): "Huang \u9ec3", Dialling Number: 15555218204
       is(aResult[3].name[0], "Huang 黃");
       is(aResult[3].tel[0].value, "15555218204");
       is(aResult[3].id, iccId + "4");
+
+      // Alpha Id(Encoded with GSM 8 bit): "Contact001",
+      // Dialling Number: 9988776655443322110001234567890123456789
+      is(aResult[4].name[0], "Contact001");
+      is(aResult[4].tel[0].value, "9988776655443322110001234567890123456789");
+      is(aResult[4].id, iccId + "5");
+
+      // Alpha Id(Encoded with GSM 8 bit): "Contact002",
+      // Dialling Number: 0123456789012345678999887766554433221100
+      is(aResult[5].name[0], "Contact002");
+      is(aResult[5].tel[0].value, "0123456789012345678999887766554433221100");
+      is(aResult[5].id, iccId + "6");
     }, (aError) => {
       ok(false, "Cannot get " + aType + " contacts");
     });
 }
 
 
 // Start tests
 startTestCommon(function() {
--- a/dom/system/gonk/ril_consts.js
+++ b/dom/system/gonk/ril_consts.js
@@ -569,16 +569,22 @@ this.EFSMS_STATUS_TO_BE_SENT = 0x07;
 // See TS 151.011 clause 10.5.1 EF_ADN.
 this.ADN_FOOTER_SIZE_BYTES = 14;
 // Maximum size of BCD numbers in ADN.
 // See TS 151.011 clause 10.5.1 EF_ADN, 'Length of BCD number/SSC contents'.
 this.ADN_MAX_BCD_NUMBER_BYTES = 11;
 // Maximum digits of the Dialling Number in ADN.
 // See TS 151.011 clause 10.5.1 EF_ADN, 'Dialling Number'.
 this.ADN_MAX_NUMBER_DIGITS = 20;
+// Maximum size of BCD numbers in EXT.
+// See TS 151.011 clause 10.5.10 EF_EXT1, 'Extension data'.
+this.EXT_MAX_BCD_NUMBER_BYTES = 10;
+// Maximum digits of the Dialling Number in EXT.
+// See TS 151.011 clause 10.5.10 EF_EXT1, 'Extension data'.
+this.EXT_MAX_NUMBER_DIGITS = 20;
 
 // READ_RECORD mode,  TS 102.221
 this.READ_RECORD_ABSOLUTE_MODE = 4;
 
 // TS 102.221 Table 11.2, return FCP template
 this.GET_RESPONSE_FCP_TEMPLATE = 4;
 
 // GET_RESPONSE mandatory response size for EF, see TS 51.011 clause 9,
@@ -1257,35 +1263,40 @@ this.STK_SUPPORTED_TERMINAL_PROFILE = [
  */
 this.GECKO_ICC_SERVICES = {
   // @see 3GPP TS 51.011 10.3.7 (SIM).
   sim: {
     ADN: 2,
     FDN: 3,
     PLMNSEL: 7,
     MSISDN: 9,
+    EXT1: 10,
+    EXT2: 11,
     CBMI: 14,
     GID1: 15,
     SPN: 17,
     SDN: 18,
+    EXT3: 19,
     DATA_DOWNLOAD_SMS_CB: 25,
     DATA_DOWNLOAD_SMS_PP: 26,
     CBMIR: 30,
     BDN: 31,
     IMG: 39,
     PNN: 51,
     OPL: 52,
     MDN: 53,
     MWIS: 54,
     SPDI: 56
   },
   // @see 3GPP TS 31.102 4.2.8 (USIM).
   usim: {
     FDN: 2,
+    EXT2: 3,
     SDN: 4,
+    EXT3: 5,
     BDN: 6,
     CBMI: 15,
     CBMIR: 16,
     GID1: 17,
     SPN: 19,
     MSISDN: 21,
     IMG: 22,
     DATA_DOWNLOAD_SMS_PP: 28,
@@ -1295,18 +1306,21 @@ this.GECKO_ICC_SERVICES = {
     MDN: 47,
     MWIS: 48,
     SPDI: 51
   },
   // @see 3GPP2 C.S0023-D 3.4.18 (RUIM).
   ruim: {
     FDN: 3,
     ENHANCED_PHONEBOOK: 6,
+    EXT1: 10,
+    EXT2: 11,
     SPN: 17,
-    SDN: 18
+    SDN: 18,
+    EXT3: 19,
   },
   // @see B.3.1.1 CPHS Information in CPHS Phase 2:
   // Indicates which of the CPHS 'optional' data-fields are present in the SIM card:
   //   EF_CPHS_CSP, EF_CPHS_SST, EF_CPHS_MBN, EF_CPHS_ONSF, EF_CPHS_INFO_NUM
   // Note: Mandatory EFs are: (B.3.1 Enhanced SIM Requirements)
   //   EF_CPHS_CFF, EF_CPHS_VMI, EF_CPHS_ONS, EF_CPHS_INFO
   cphs: {
     CSP: 1,
--- a/dom/system/gonk/ril_worker.js
+++ b/dom/system/gonk/ril_worker.js
@@ -11103,16 +11103,19 @@ ICCFileHelperObject.prototype = {
   /**
    * This function handles EFs for SIM.
    */
   getSimEFPath: function(fileId) {
     switch (fileId) {
       case ICC_EF_FDN:
       case ICC_EF_MSISDN:
       case ICC_EF_SMS:
+      case ICC_EF_EXT1:
+      case ICC_EF_EXT2:
+      case ICC_EF_EXT3:
         return EF_PATH_MF_SIM + EF_PATH_DF_TELECOM;
       case ICC_EF_AD:
       case ICC_EF_MBDN:
       case ICC_EF_MWIS:
       case ICC_EF_PLMNsel:
       case ICC_EF_SPN:
       case ICC_EF_SPDI:
       case ICC_EF_SST:
@@ -11172,16 +11175,19 @@ ICCFileHelperObject.prototype = {
   getRuimEFPath: function(fileId) {
     switch(fileId) {
       case ICC_EF_CSIM_IMSI_M:
       case ICC_EF_CSIM_CDMAHOME:
       case ICC_EF_CSIM_CST:
       case ICC_EF_CSIM_SPN:
         return EF_PATH_MF_SIM + EF_PATH_DF_CDMA;
       case ICC_EF_FDN:
+      case ICC_EF_EXT1:
+      case ICC_EF_EXT2:
+      case ICC_EF_EXT3:
         return EF_PATH_MF_SIM + EF_PATH_DF_TELECOM;
       default:
         return null;
     }
   },
 
   /**
    * Helper function for getting the pathId for the specific ICC record
@@ -11577,44 +11583,62 @@ ICCRecordHelperObject.prototype = {
       fileId: ICC_EF_ICCID,
       callback: callback.bind(this)
     });
   },
 
   /**
    * Read ICC ADN like EF, i.e. EF_ADN, EF_FDN.
    *
-   * @param fileId      EF id of the ADN or FDN.
+   * @param fileId      EF id of the ADN, FDN or SDN.
+   * @param extFileId   EF id of the EXT.
    * @param onsuccess   Callback to be called when success.
    * @param onerror     Callback to be called when error.
    */
-  readADNLike: function(fileId, onsuccess, onerror) {
+  readADNLike: function(fileId, extFileId, onsuccess, onerror) {
     let ICCIOHelper = this.context.ICCIOHelper;
 
     function callback(options) {
-      let contact =
-        this.context.ICCPDUHelper.readAlphaIdDiallingNumber(options.recordSize);
-      if (contact) {
-        contact.recordId = options.p1;
-        contacts.push(contact);
-      }
-
-      if (options.p1 < options.totalRecords) {
-        ICCIOHelper.loadNextRecord(options);
-      } else {
+      let loadNextContactRecord = () => {
+        if (options.p1 < options.totalRecords) {
+          ICCIOHelper.loadNextRecord(options);
+          return;
+        }
         if (DEBUG) {
           for (let i = 0; i < contacts.length; i++) {
             this.context.debug("contact [" + i + "] " +
                                JSON.stringify(contacts[i]));
           }
         }
         if (onsuccess) {
           onsuccess(contacts);
         }
-      }
+      };
+
+      let contact =
+        this.context.ICCPDUHelper.readAlphaIdDiallingNumber(options.recordSize);
+      if (contact) {
+        let record = {
+          recordId: options.p1,
+          alphaId: contact.alphaId,
+          number: contact.number
+        };
+        contacts.push(record);
+
+        if (extFileId && contact.extRecordNumber != 0xff) {
+          this.readExtension(extFileId, contact.extRecordNumber, (number) => {
+            if (number) {
+              record.number += number;
+            }
+            loadNextContactRecord();
+          }, () => loadNextContactRecord());
+          return;
+        }
+      }
+      loadNextContactRecord();
     }
 
     let contacts = [];
     ICCIOHelper.loadLinearFixedEF({fileId: fileId,
                                    callback: callback.bind(this),
                                    onerror: onerror});
   },
 
@@ -12079,16 +12103,74 @@ ICCRecordHelperObject.prototype = {
 
     // Start searching free records from the possible one.
     let recordNumber = this._freeRecordIds[fileId] || 1;
     ICCIOHelper.loadLinearFixedEF({fileId: fileId,
                                    recordNumber: recordNumber,
                                    callback: callback.bind(this),
                                    onerror: onerror});
   },
+
+  /**
+   * Read Extension Number from TS 151.011 clause 10.5.10, TS 31.102, clause 4.4.2.4
+   *
+   * @param fileId        EF Extension id
+   * @param recordNumber  The number of the record shall be loaded.
+   * @param onsuccess     Callback to be called when success.
+   * @param onerror       Callback to be called when error.
+   */
+  readExtension: function(fileId, recordNumber, onsuccess, onerror) {
+    let callback = (options) => {
+      let Buf = this.context.Buf;
+      let length = Buf.readInt32();
+      let recordType = this.context.GsmPDUHelper.readHexOctet();
+      let number = "";
+
+      // TS 31.102, clause 4.4.2.4 EFEXT1
+      // Case 1, Extension1 record is additional data
+      if (recordType & 0x02) {
+        let numLen = this.context.GsmPDUHelper.readHexOctet();
+        if (numLen != 0xff) {
+          if (numLen > EXT_MAX_BCD_NUMBER_BYTES) {
+            if (DEBUG) {
+              this.context.debug(
+              "Error: invalid length of BCD number/SSC contents - " + numLen);
+            }
+            // +1 to skip Identifier
+            Buf.seekIncoming((EXT_MAX_BCD_NUMBER_BYTES + 1) * Buf.PDU_HEX_OCTET_SIZE);
+            Buf.readStringDelimiter(length);
+            onerror();
+            return;
+          }
+
+          number = this.context.GsmPDUHelper.readSwappedNibbleExtendedBcdString(numLen);
+          if (DEBUG) this.context.debug("Contact Extension Number: "+ number);
+          Buf.seekIncoming((EXT_MAX_BCD_NUMBER_BYTES - numLen) * Buf.PDU_HEX_OCTET_SIZE);
+        } else {
+          Buf.seekIncoming(EXT_MAX_BCD_NUMBER_BYTES * Buf.PDU_HEX_OCTET_SIZE);
+        }
+      } else {
+        // Don't support Case 2, Extension1 record is Called Party Subaddress.
+        // +1 skip numLen
+        Buf.seekIncoming((EXT_MAX_BCD_NUMBER_BYTES + 1) * Buf.PDU_HEX_OCTET_SIZE);
+      }
+
+      // Skip Identifier
+      Buf.seekIncoming(Buf.PDU_HEX_OCTET_SIZE);
+      Buf.readStringDelimiter(length);
+      onsuccess(number);
+    }
+
+    this.context.ICCIOHelper.loadLinearFixedEF({
+      fileId: fileId,
+      recordNumber: recordNumber,
+      callback: callback,
+      onerror: onerror
+    });
+  },
 };
 
 /**
  * Helper for (U)SIM Records.
  */
 function SimRecordHelperObject(aContext) {
   this.context = aContext;
 }
@@ -13989,35 +14071,41 @@ ICCContactHelperObject.prototype = {
    */
   readICCContacts: function(appType, contactType, onsuccess, onerror) {
     let ICCRecordHelper = this.context.ICCRecordHelper;
     let ICCUtilsHelper = this.context.ICCUtilsHelper;
 
     switch (contactType) {
       case GECKO_CARDCONTACT_TYPE_ADN:
         if (!this.hasDfPhoneBook(appType)) {
-          ICCRecordHelper.readADNLike(ICC_EF_ADN, onsuccess, onerror);
+          ICCRecordHelper.readADNLike(ICC_EF_ADN,
+            (ICCUtilsHelper.isICCServiceAvailable("EXT1")) ? ICC_EF_EXT1 : null,
+            onsuccess, onerror);
         } else {
           this.readUSimContacts(onsuccess, onerror);
         }
         break;
       case GECKO_CARDCONTACT_TYPE_FDN:
         if (!ICCUtilsHelper.isICCServiceAvailable("FDN")) {
           onerror(CONTACT_ERR_CONTACT_TYPE_NOT_SUPPORTED);
           break;
         }
-        ICCRecordHelper.readADNLike(ICC_EF_FDN, onsuccess, onerror);
+        ICCRecordHelper.readADNLike(ICC_EF_FDN,
+          (ICCUtilsHelper.isICCServiceAvailable("EXT2")) ? ICC_EF_EXT2 : null,
+          onsuccess, onerror);
         break;
       case GECKO_CARDCONTACT_TYPE_SDN:
         if (!ICCUtilsHelper.isICCServiceAvailable("SDN")) {
           onerror(CONTACT_ERR_CONTACT_TYPE_NOT_SUPPORTED);
           break;
         }
 
-        ICCRecordHelper.readADNLike(ICC_EF_SDN, onsuccess, onerror);
+        ICCRecordHelper.readADNLike(ICC_EF_SDN,
+          (ICCUtilsHelper.isICCServiceAvailable("EXT3")) ? ICC_EF_EXT3 : null,
+          onsuccess, onerror);
         break;
       default:
         if (DEBUG) {
           this.context.debug("Unsupported contactType :" + contactType);
         }
         onerror(CONTACT_ERR_CONTACT_TYPE_NOT_SUPPORTED);
         break;
     }
@@ -14219,21 +14307,23 @@ ICCContactHelperObject.prototype = {
   /**
    * Read from Phonebook Reference File.
    *
    * @param pbr           Phonebook Reference File to be read.
    * @param onsuccess     Callback to be called when success.
    * @param onerror       Callback to be called when error.
    */
   readPhonebookSet: function(pbr, onsuccess, onerror) {
+    let ICCRecordHelper = this.context.ICCRecordHelper;
     let gotAdnCb = function gotAdnCb(contacts) {
       this.readSupportedPBRFields(pbr, contacts, onsuccess, onerror);
     }.bind(this);
 
-    this.context.ICCRecordHelper.readADNLike(pbr.adn.fileId, gotAdnCb, onerror);
+    ICCRecordHelper.readADNLike(pbr.adn.fileId,
+      (pbr.ext1) ? pbr.ext1.fileId : null, gotAdnCb, onerror);
   },
 
   /**
    * Read supported Phonebook fields.
    *
    * @param pbr         Phone Book Reference file.
    * @param contacts    Contacts stored on ICC.
    * @param onsuccess   Callback to be called when success.
--- a/dom/system/gonk/tests/test_ril_worker_icc_ICCRecordHelper.js
+++ b/dom/system/gonk/tests/test_ril_worker_icc_ICCRecordHelper.js
@@ -490,16 +490,84 @@ add_test(function test_update_iap() {
     };
     recordHelper.updateIAP(fileId, recordNumber, expectedIAP);
   }
 
   do_test([1, 2]);
 });
 
 /**
+ * Verify ICCRecordHelper.readADNLike.
+ */
+add_test(function test_read_adn_like() {
+  const RECORD_SIZE = 0x20;
+
+  let worker = newUint8Worker();
+  let context = worker.ContextPool._contexts[0];
+  let helper = context.GsmPDUHelper;
+  let record = context.ICCRecordHelper;
+  let buf = context.Buf;
+  let io = context.ICCIOHelper;
+  let ril = context.RIL;
+
+  function do_test(extFileId, rawEF, expectedExtRecordNumber, expectedNumber) {
+    io.loadLinearFixedEF = function fakeLoadLinearFixedEF(options) {
+      // Write data size
+      buf.writeInt32(rawEF.length * 2);
+
+      // Write adn
+      for (let i = 0; i < rawEF.length; i += 2) {
+        helper.writeHexOctet(parseInt(rawEF.substr(i, 2), 16));
+      }
+
+      // Write string delimiter
+      buf.writeStringDelimiter(rawEF.length * 2);
+
+      options.p1 = 1;
+      options.recordSize = RECORD_SIZE;
+      options.totalRecords = 1;
+      if (options.callback) {
+        options.callback(options);
+      }
+    };
+
+    record.readExtension = function(fileId, recordNumber, onsuccess, onerror) {
+      onsuccess("1234");
+    }
+
+    let successCb = function successCb(contacts) {
+      ok(contacts[0].number == expectedNumber);
+    };
+
+    let errorCb = function errorCb(errorMsg) {
+      do_print("Reading ADNLike failed, msg = " + errorMsg);
+      ok(false);
+    };
+
+    record.readADNLike(ICC_EF_ADN, extFileId, successCb, errorCb);
+  }
+
+  ril.appType = CARD_APPTYPE_SIM;
+  // Valid extension
+  do_test(ICC_EF_EXT1,"436f6e74616374303031ffffffffffffffff0b8199887766554433221100ff01",
+          0x01,"998877665544332211001234");
+  // Empty extension
+  do_test(ICC_EF_EXT1,"436f6e74616374303031ffffffffffffffff0b8199887766554433221100ffff",
+          0xff, "99887766554433221100");
+  // Unsupport extension
+  do_test(null,"436f6e74616374303031ffffffffffffffff0b8199887766554433221100ffff",
+          0xff, "99887766554433221100");
+  // Empty dialling number contact
+  do_test(null,"436f6e74616374303031ffffffffffffffffffffffffffffffffffffffffffff",
+          0xff, null);
+
+  run_next_test();
+});
+
+/**
  * Verify ICCRecordHelper.updateADNLike.
  */
 add_test(function test_update_adn_like() {
   let worker = newUint8Worker();
   let context = worker.ContextPool._contexts[0];
   let ril = context.RIL;
   let record = context.ICCRecordHelper;
   let io = context.ICCIOHelper;
@@ -716,8 +784,65 @@ add_test(function test_handling_iccid() 
   do_test("986800B2909090001519", "8986002090909005191");
   // Invalid value 0xA at low nibbile.
   do_test("986800A2909090001519", "8986002090909005191");
   // Valid ICCID.
   do_test("98101430121181157002", "89014103211118510720");
 
   run_next_test();
 });
+
+/**
+ *  Verify ICCRecordHelper.readExtension
+ */
+add_test(function test_read_extension() {
+  let worker = newUint8Worker();
+  let context = worker.ContextPool._contexts[0];
+  let helper = context.GsmPDUHelper;
+  let record = context.ICCRecordHelper;
+  let buf = context.Buf;
+  let io = context.ICCIOHelper;
+
+  function do_test(rawExtension, expectedExtensionNumber) {
+    io.loadLinearFixedEF = function fakeLoadLinearFixedEF(options) {
+      // Write data size
+      buf.writeInt32(rawExtension.length * 2);
+
+      // Write ext
+      for (let i = 0; i < rawExtension.length; i += 2) {
+        helper.writeHexOctet(parseInt(rawExtension.substr(i, 2), 16));
+      }
+
+      // Write string delimiter
+      buf.writeStringDelimiter(rawExtension.length);
+
+      if (options.callback) {
+        options.callback(options);
+      }
+    };
+
+    let successCb = function successCb(number) {
+      do_print("extension number:" + number);
+      equal(number, expectedExtensionNumber);
+    };
+
+    let errorCb = function errorCb() {
+      ok(expectedExtensionNumber == null);
+    };
+
+    record.readExtension(0x6f4a, 1, successCb, errorCb);
+  }
+
+  // Test unsupported record type 0x01
+  do_test("010a10325476981032547698ff", "");
+  // Test invalid length 0xc1
+  do_test("020c10325476981032547698ff", null);
+  // Test extension chain which we don't support
+  do_test("020a1032547698103254769802", "01234567890123456789");
+  // Test valid Extension
+  do_test("020a10325476981032547698ff", "01234567890123456789");
+  // Test valid Extension
+  do_test("0209103254769810325476ffff", "012345678901234567");
+  // Test empty Extension
+  do_test("02ffffffffffffffffffffffff", "");
+
+  run_next_test();
+});