Read EF-SPDI and use it for SPN display.
authorAndrzej Zaborowski <andrew.zaborowski@intel.com>
Sun, 21 Jun 2009 01:40:48 +0000 (03:40 +0200)
committerDenis Kenzior <denkenz@gmail.com>
Mon, 22 Jun 2009 18:22:14 +0000 (13:22 -0500)
When the operator is one of those listed in EF-SPDI then we need to
treat it like a HPLMN in deciding whether the SPN or PLMN name should
be displayed.

src/network.c
src/sim.c
src/sim.h

index a437f0d..43fa2ab 100644 (file)
@@ -328,6 +328,7 @@ static char *get_operator_display_name(struct ofono_modem *modem)
        const char *plmn;
        char *name = netreg->display_name;
        int len = sizeof(netreg->display_name);
+       int home_or_spdi;
 
        /* The name displayed to user depends on whether we're in a home
         * PLMN or roaming and on configuration bits from the SIM, all
@@ -345,7 +346,11 @@ static char *get_operator_display_name(struct ofono_modem *modem)
 
        plmn = netreg->current_operator->name;
 
-       if (netreg->status == NETWORK_REGISTRATION_STATUS_REGISTERED)
+       home_or_spdi =
+               (netreg->status == NETWORK_REGISTRATION_STATUS_REGISTERED) ||
+               ofono_operator_in_spdi(modem, netreg->current_operator);
+
+       if (home_or_spdi)
                if (netreg->flags & NETWORK_REGISTRATION_FLAG_HOME_SHOW_PLMN)
                        /* Case 1 */
                        snprintf(name, len, "%s (%s)", netreg->spname, plmn);
index a8b7d5f..572f6a8 100644 (file)
--- a/src/sim.c
+++ b/src/sim.c
@@ -53,6 +53,8 @@ struct sim_manager_data {
 
        GSList *update_spn_notify;
 
+       GSList *spdi;
+
        int own_numbers_num;
        int own_numbers_size;
        int own_numbers_current;
@@ -80,6 +82,44 @@ static char **get_own_numbers(GSList *own_numbers)
        return ret;
 }
 
+/* Parse ASN.1 Basic Encoding Rules TLVs per ISO/IEC 7816 */
+static const guint8 *ber_tlv_find_by_tag(const guint8 *pdu, guint8 in_tag,
+                                               int in_len, int *out_len)
+{
+       guint8 tag;
+       int len;
+       const guint8 *end = pdu + in_len;
+
+       do {
+               while (pdu < end && (*pdu == 0x00 || *pdu == 0xff))
+                       pdu ++;
+               if (pdu == end)
+                       break;
+
+               tag = *pdu ++;
+               if (!(0x1f & ~tag))
+                       while (pdu < end && (*pdu ++ & 0x80));
+               if (pdu == end)
+                       break;
+
+               for (len = 0; pdu + 1 < end && (*pdu & 0x80);
+                               len = (len | (*pdu ++ & 0x7f)) << 7);
+               if (*pdu & 0x80)
+                       break;
+               len |= *pdu ++;
+
+               if (tag == in_tag && pdu + len <= end) {
+                       if (out_len)
+                               *out_len = len;
+                       return pdu;
+               }
+
+               pdu += len;
+       } while (pdu < end);
+
+       return NULL;
+}
+
 static struct sim_manager_data *sim_manager_create()
 {
        return g_try_new0(struct sim_manager_data, 1);
@@ -105,6 +145,12 @@ static void sim_manager_destroy(gpointer userdata)
                g_free(data->spn);
                data->spn = NULL;
        }
+
+       if (data->spdi) {
+               g_slist_foreach(data->spdi, (GFunc)g_free, NULL);
+               g_slist_free(data->spdi);
+               data->spdi = NULL;
+       }
 }
 
 static DBusMessage *sim_get_properties(DBusConnection *conn,
@@ -156,6 +202,7 @@ static GDBusSignalTable sim_manager_signals[] = { { } };
 enum sim_fileids {
        SIM_EFMSISDN_FILEID = 0x6f40,
        SIM_EFSPN_FILEID = 0x6f46,
+       SIM_EFSPDI_FILEID = 0x6fcd,
 };
 
 #define SIM_EFSPN_DC_HOME_PLMN_BIT 0x1
@@ -338,6 +385,123 @@ static gboolean sim_retrieve_own_number(void *user_data)
        return FALSE;
 }
 
+struct spdi_operator {
+       char mcc[OFONO_MAX_MCC_LENGTH + 1];
+       char mnc[OFONO_MAX_MNC_LENGTH + 1];
+};
+
+static struct spdi_operator *spdi_operator_alloc(const guint8 *bcd)
+{
+       struct spdi_operator *spdi = g_new0(struct spdi_operator, 1);
+       char *mcc = spdi->mcc;
+       char *mnc = spdi->mnc;
+       guint8 digit;
+
+       digit = (bcd[0] >> 0) & 0xf;
+       if (digit != 0xf)
+               *mcc ++ = '0' + digit;
+       digit = (bcd[0] >> 4) & 0xf;
+       if (digit != 0xf)
+               *mcc ++ = '0' + digit;
+       digit = (bcd[1] >> 0) & 0xf;
+       if (digit != 0xf)
+               *mcc ++ = '0' + digit;
+       digit = (bcd[2] >> 0) & 0xf;
+       if (digit != 0xf)
+               *mnc ++ = '0' + digit;
+       digit = (bcd[2] >> 4) & 0xf;
+       if (digit != 0xf)
+               *mnc ++ = '0' + digit;
+       digit = (bcd[1] >> 4) & 0xf;
+       if (digit != 0xf)
+               *mnc ++ = '0' + digit;
+
+       return spdi;
+}
+
+static gint spdi_operator_compare(gconstpointer a, gconstpointer b)
+{
+       const struct spdi_operator *opa = a;
+       const struct spdi_operator *opb = b;
+
+       return strcmp(opa->mcc, opb->mcc) ?: strcmp(opa->mnc, opb->mnc);
+}
+
+int ofono_operator_in_spdi(struct ofono_modem *modem,
+                               const struct ofono_network_operator *op)
+{
+       struct sim_manager_data *sim = modem->sim_manager;
+       struct spdi_operator spdi_op;
+
+       if (!sim)
+               return FALSE;
+
+       g_strlcpy(spdi_op.mcc, op->mcc, sizeof(spdi_op.mcc));
+       g_strlcpy(spdi_op.mnc, op->mnc, sizeof(spdi_op.mnc));
+
+       return !!g_slist_find_custom(sim->spdi,
+                       &spdi_op, spdi_operator_compare);
+}
+
+static void sim_spdi_read_cb(const struct ofono_error *error,
+                               const unsigned char *spdidata,
+                               int length, void *data)
+{
+       struct ofono_modem *modem = data;
+       struct sim_manager_data *sim = modem->sim_manager;
+       const guint8 *plmn_list;
+       struct spdi_operator *spdi;
+       GSList *l;
+
+       if (error->type != OFONO_ERROR_TYPE_NO_ERROR || length <= 5)
+               return;
+
+       plmn_list = ber_tlv_find_by_tag(spdidata, 0x80, length, &length);
+       if (!plmn_list) {
+               ofono_debug("Couldn't parse the EF-SPDI contents as a TLV");
+               return;
+       }
+
+       for (length /= 3; length --; plmn_list += 3) {
+               if ((plmn_list[0] & plmn_list[1] & plmn_list[2]) == 0xff)
+                       continue;
+
+               sim->spdi = g_slist_insert_sorted(sim->spdi,
+                               spdi_operator_alloc(plmn_list),
+                               spdi_operator_compare);
+       }
+
+       if (sim->spdi)
+               for (l = sim->update_spn_notify; l; l = l->next)
+                       sim_spn_notify(modem, l->data);
+}
+
+static void sim_spdi_info_cb(const struct ofono_error *error, int length,
+                               enum ofono_sim_file_structure structure,
+                               int dummy, void *data)
+{
+       struct ofono_modem *modem = data;
+       struct sim_manager_data *sim = modem->sim_manager;
+
+       if (error->type != OFONO_ERROR_TYPE_NO_ERROR || length <= 5 ||
+                       structure != OFONO_SIM_FILE_STRUCTURE_TRANSPARENT)
+               return;
+
+       sim->ops->read_file_transparent(modem, SIM_EFSPDI_FILEID, 0, length,
+                                       sim_spdi_read_cb, modem);
+}
+
+static gboolean sim_retrieve_spdi(void *user_data)
+{
+       struct ofono_modem *modem = user_data;
+       struct sim_manager_data *sim = modem->sim_manager;
+
+       sim->ops->read_file_info(modem, SIM_EFSPDI_FILEID,
+                                       sim_spdi_info_cb, modem);
+
+       return FALSE;
+}
+
 static void initialize_sim_manager(struct ofono_modem *modem)
 {
        DBusConnection *conn = dbus_gsm_connection();
@@ -367,6 +531,9 @@ static void initialize_sim_manager(struct ofono_modem *modem)
 
        if (modem->sim_manager->ops->read_file_linear)
                g_timeout_add(0, sim_retrieve_own_number, modem);
+
+       if (modem->sim_manager->ops->read_file_transparent)
+               g_timeout_add(0, sim_retrieve_spdi, modem);
 }
 
 int ofono_sim_manager_register(struct ofono_modem *modem,
index e0b471d..9456934 100644 (file)
--- a/src/sim.h
+++ b/src/sim.h
@@ -28,3 +28,6 @@ int ofono_spn_update_notify_register(struct ofono_modem *modem,
                update_spn_cb cb);
 int ofono_spn_update_notify_unregister(struct ofono_modem *modem,
                update_spn_cb cb);
+
+int ofono_operator_in_spdi(struct ofono_modem *modem,
+                               const struct ofono_network_operator *op);