summaryrefslogtreecommitdiffstats
path: root/drivers/rilmodem/phonebook.c
diff options
context:
space:
mode:
authorTony Espy <espy@canonical.com>2015-10-13 18:07:53 +0200
committerDenis Kenzior <denkenz@gmail.com>2015-10-13 17:38:45 -0500
commite918a6b222ace2ae034b6b2a2474bda457dfc619 (patch)
tree3e41881b1cb212e7f3102276027c9e5a5320eae2 /drivers/rilmodem/phonebook.c
parent9c2af753c0ca7e344019e33911bc590f35f81b12 (diff)
downloadofono-e918a6b222ace2ae034b6b2a2474bda457dfc619.tar.bz2
rilmodem: driver for Android modems
Driver for modems that are accessed through the Android Radio Interface Layer (RIL) for telephony, using the gril library. The driver is almost feature complete with some exceptions, being CBS and SAT the most prominent. Co-authored-by: Tony Espy <espy@canonical.com> Co-authored-by: Ricardo Salveti de Araujo <ricardo.salveti@canonical.com> Co-authored-by: Alfonso Sanchez-Beato <alfonso.sanchez-beato@canonical.com> Co-authored-by: Islam Amer <islam.amer@jollamobile.com> Co-authored-by: Jussi Kangas <jussi.kangas@tieto.com> Co-authored-by: Juho Hämäläinen <juho.hamalainen@tieto.com> Co-authored-by: Petri Takalokastari <petri.takalokastari@oss.tieto.com> Co-authored-by: Jarko Poutiainen <Jarko.Poutiainen@oss.tieto.com> Co-authored-by: Tommi Kenakkala <tommi.kenakkala@oss.tieto.com> Co-authored-by: Miia Leinonen <miia.leinonen@oss.tieto.com> Co-authored-by: Martti Piirainen <martti.piirainen@canonical.com> Co-authored-by: You-Sheng Yang <vicamo.yang@canonical.com>
Diffstat (limited to 'drivers/rilmodem/phonebook.c')
-rw-r--r--drivers/rilmodem/phonebook.c1055
1 files changed, 1055 insertions, 0 deletions
diff --git a/drivers/rilmodem/phonebook.c b/drivers/rilmodem/phonebook.c
new file mode 100644
index 00000000..c3f1c0b7
--- /dev/null
+++ b/drivers/rilmodem/phonebook.c
@@ -0,0 +1,1055 @@
+/*
+ *
+ * oFono - Open Source Telephony - RIL Modem Support
+ *
+ * Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
+ * Copyright (C) ST-Ericsson SA 2010.
+ * Copyright (C) 2008-2011 Intel Corporation. All rights reserved.
+ * Copyright (C) 2013 Jolla Ltd
+ * Contact: Jussi Kangas <jussi.kangas@tieto.com>
+ * Copyright (C) 2014 Canonical Ltd
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#define _GNU_SOURCE
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <stdint.h>
+
+#include <glib.h>
+
+#include <ofono.h>
+#include <ofono/log.h>
+#include <ofono/modem.h>
+#include <ofono/phonebook.h>
+#include <sim.h>
+#include <simfs.h>
+#include <util.h>
+
+#include "gril.h"
+#include "simutil.h"
+#include "common.h"
+
+#include "rilmodem.h"
+
+#define UNUSED 0xFF
+
+#define EXT1_CP_SUBADDRESS 1
+#define EXT1_ADDITIONAL_DATA 2
+
+/* TON (Type Of Number) See TS 24.008 */
+#define TON_MASK 0x70
+#define TON_INTERNATIONAL 0x10
+
+enum constructed_tag {
+ TYPE_1_TAG = 0xA8,
+ TYPE_2_TAG = 0xA9,
+ TYPE_3_TAG = 0xAA
+};
+
+enum file_type_tag {
+ TYPE_ADN = 0xC0,
+ TYPE_IAP = 0xC1,
+ TYPE_EXT1 = 0xC2,
+ TYPE_SNE = 0xC3,
+ TYPE_ANR = 0xC4,
+ TYPE_PBC = 0xC5,
+ TYPE_GPR = 0xC6,
+ TYPE_AAS = 0xC7,
+ TYPE_GAS = 0xC8,
+ TYPE_UID = 0xC9,
+ TYPE_EMAIL = 0xCA,
+ TYPE_CCP1 = 0xCB
+};
+
+struct pb_file_info {
+ enum constructed_tag pbr_type;
+ int file_id;
+ enum file_type_tag file_type;
+ int file_length;
+ int record_length;
+};
+
+struct record_to_read {
+ int file_id;
+ enum file_type_tag type_tag;
+ int record_length;
+ int record;
+ int adn_idx;
+ gboolean anr_ext; /* Is it an EXT1 record for ANR? */
+ gboolean set_by_iap; /* Type 2 file? */
+};
+
+struct phonebook_entry {
+ int entry;
+ char *name;
+ char *number;
+ char *email;
+ char *anr;
+ char *sne;
+};
+
+unsigned char sim_path[] = { 0x3F, 0x00, 0x7F, 0x10 };
+unsigned char usim_path[] = { 0x3F, 0x00, 0x7F, 0x10, 0x5F, 0x3A };
+
+/*
+ * Table for BCD to utf8 conversion. See table 4.4 in TS 31.102.
+ * BCD 0x0C indicates pause before sending following digits as DTMF tones.
+ * BCD 0x0D is a wildcard that means "any digit". These values are mapped to
+ * ',' and '?', following the Android/iPhone convention for the first and Nokia
+ * convention for the second (only OEM that I have seen that supports this
+ * feature). BCD 0x0E is reserved, we convert it to 'r'.
+ */
+static const char digit_to_utf8[] = "0123456789*#,?r\0";
+
+/* One of these for each record in EF_PBR */
+struct pb_ref_rec {
+ GSList *pb_files; /* File ids to read (pb_file_info nodes) */
+ GSList *pb_next; /* Next file info to read */
+ GSList *pending_records; /* List of record_to_read */
+ GSList *next_record; /* Next record_to_read to process */
+ GTree *phonebook; /* Container of phonebook_entry structures */
+};
+
+struct pb_data {
+ GSList *pb_refs;
+ GSList *pb_ref_next;
+ struct ofono_sim *sim;
+ struct ofono_sim_context *sim_context;
+ const unsigned char *df_path;
+ size_t df_size;
+};
+
+static void read_info_cb(int ok, unsigned char file_status,
+ int total_length, int record_length,
+ void *userdata);
+
+static gint comp_int(gconstpointer a, gconstpointer b)
+{
+ int a_val = GPOINTER_TO_INT(a);
+ int b_val = GPOINTER_TO_INT(b);
+
+ return a_val - b_val;
+}
+
+static const struct pb_file_info *
+ext1_info(const GSList *pb_files)
+{
+ const GSList *l;
+ for (l = pb_files; l; l = l->next) {
+ const struct pb_file_info *f_info = l->data;
+ if (f_info->file_type == TYPE_EXT1)
+ return f_info;
+ }
+
+ return NULL;
+}
+
+static struct phonebook_entry *handle_adn(size_t len, const unsigned char *msg,
+ struct pb_ref_rec *ref, int adn_idx)
+{
+ unsigned name_length = len - 14;
+ unsigned number_start = name_length;
+ unsigned number_length;
+ unsigned extension_record = UNUSED;
+ unsigned i, prefix;
+ char *number = NULL;
+ char *name = sim_string_to_utf8(msg, name_length);
+ struct phonebook_entry *new_entry;
+
+ /* Length contains also TON & NPI */
+ number_length = msg[number_start];
+
+ if (number_length != UNUSED && number_length != 0) {
+ number_length--;
+ /* '+' + number + terminator */
+ number = g_try_malloc0(2 * number_length + 2);
+
+ if (number) {
+ prefix = 0;
+
+ if ((msg[number_start + 1] & TON_MASK)
+ == TON_INTERNATIONAL) {
+ number[0] = '+';
+ prefix = 1;
+ }
+
+ for (i = 0; i < number_length; i++) {
+
+ number[2 * i + prefix] =
+ digit_to_utf8[msg[number_start + 2 + i]
+ & 0x0f];
+ number[2 * i + 1 + prefix] =
+ digit_to_utf8[msg[number_start + 2 + i]
+ >> 4];
+ }
+
+ extension_record = msg[len - 1];
+ }
+ }
+
+ DBG("ADN name %s, number %s ", name, number);
+ DBG("number length %d extension_record %d",
+ 2 * number_length, extension_record);
+
+ if ((name == NULL || *name == '\0') && number == NULL)
+ goto end;
+
+ new_entry = g_try_malloc0(sizeof(*new_entry));
+ if (new_entry == NULL) {
+ ofono_error("%s: out of memory", __func__);
+ goto end;
+ }
+
+ new_entry->name = name;
+ new_entry->number = number;
+
+ DBG("Creating PB entry %d with", adn_idx);
+ DBG("name %s and number %s", new_entry->name, new_entry->number);
+
+ g_tree_insert(ref->phonebook, GINT_TO_POINTER(adn_idx), new_entry);
+
+ if (extension_record != UNUSED) {
+ struct record_to_read *ext_rec =
+ g_try_malloc0(sizeof(*ext_rec));
+ const struct pb_file_info *f_info = ext1_info(ref->pb_files);
+
+ if (ext_rec && f_info) {
+ ext_rec->file_id = f_info->file_id;
+ ext_rec->type_tag = TYPE_EXT1;
+ ext_rec->record_length = f_info->record_length;
+ ext_rec->record = extension_record;
+ ext_rec->adn_idx = adn_idx;
+
+ ref->pending_records =
+ g_slist_prepend(ref->pending_records, ext_rec);
+ }
+ }
+
+ return new_entry;
+
+end:
+ g_free(name);
+ g_free(number);
+
+ return NULL;
+}
+
+static void handle_iap(size_t len, const unsigned char *msg,
+ struct pb_ref_rec *ref,
+ const struct record_to_read *rec_data)
+{
+ GSList *l;
+ size_t i = 0;
+
+ for (l = ref->pb_files; l; l = l->next) {
+ struct pb_file_info *f_info = l->data;
+ if (f_info->pbr_type == TYPE_2_TAG) {
+ if (i >= len) {
+ ofono_error("%s: EF_IAP record too small",
+ __func__);
+ return;
+ }
+ if (msg[i] != UNUSED) {
+ struct record_to_read *new_rec =
+ g_try_malloc0(sizeof(*new_rec));
+ if (new_rec == NULL) {
+ ofono_error("%s: OOM", __func__);
+ return;
+ }
+ DBG("type 0x%X record %d",
+ f_info->file_type, msg[i]);
+
+ new_rec->file_id = f_info->file_id;
+ new_rec->type_tag = f_info->file_type;
+ new_rec->record_length = f_info->record_length;
+ new_rec->record = msg[i];
+ new_rec->adn_idx = rec_data->adn_idx;
+ new_rec->anr_ext = FALSE;
+ new_rec->set_by_iap = TRUE;
+
+ ref->pending_records =
+ g_slist_prepend(ref->pending_records,
+ new_rec);
+ }
+ ++i;
+ }
+ }
+}
+
+static void handle_sne(size_t len, const unsigned char *msg,
+ struct pb_ref_rec *ref,
+ const struct record_to_read *rec_data)
+{
+ char *sne;
+
+ /* There are additional fields for type 2 files */
+ if (rec_data->set_by_iap)
+ len -= 2;
+
+ sne = sim_string_to_utf8(msg, len);
+
+ if (sne && *sne != '\0') {
+ struct phonebook_entry *entry;
+
+ entry = g_tree_lookup(ref->phonebook,
+ GINT_TO_POINTER(rec_data->adn_idx));
+ if (entry) {
+ /* If one already exists, delete it */
+ if (entry->sne)
+ g_free(entry->sne);
+
+ DBG("Adding SNE %s to %d", sne, rec_data->adn_idx);
+ DBG("name %s", entry->name);
+
+ entry->sne = sne;
+ } else {
+ g_free(sne);
+ }
+ } else {
+ g_free(sne);
+ }
+}
+
+static void handle_anr(size_t len,
+ const unsigned char *msg,
+ struct pb_ref_rec *ref,
+ const struct record_to_read *rec_data)
+{
+ unsigned number_length;
+ unsigned extension_record;
+ unsigned aas_record;
+ unsigned i, prefix;
+ char *anr;
+ struct phonebook_entry *entry;
+
+ if (len < 15) {
+ ofono_error("%s: bad EF_ANR record size", __func__);
+ return;
+ }
+
+ aas_record = msg[0];
+ if (aas_record == UNUSED)
+ return;
+
+ DBG("ANR %d", aas_record);
+
+ /* Length contains also TON & NPI */
+ number_length = msg[1];
+ if (number_length < 2)
+ return;
+
+ number_length--;
+ /* '+' + number + terminator */
+ anr = g_try_malloc0(2 * number_length + 2);
+ if (anr == NULL)
+ return;
+
+ prefix = 0;
+ if ((msg[2] & TON_MASK) == TON_INTERNATIONAL) {
+ anr[0] = '+';
+ prefix = 1;
+ }
+
+ for (i = 0; i < number_length; i++) {
+ anr[2 * i + prefix] = digit_to_utf8[msg[3 + i] & 0x0f];
+ anr[2 * i + 1 + prefix] = digit_to_utf8[msg[3 + i] >> 4];
+ }
+
+ entry = g_tree_lookup(ref->phonebook,
+ GINT_TO_POINTER(rec_data->adn_idx));
+ if (entry == NULL) {
+ g_free(anr);
+ return;
+ }
+
+ /* If one already exists, delete it */
+ if (entry->anr)
+ g_free(entry->anr);
+
+ DBG("Adding ANR %s to %d", anr, rec_data->adn_idx);
+ DBG("name %s", entry->name);
+
+ entry->anr = anr;
+
+ extension_record = msg[14];
+
+ DBG("ANR to entry %d number %s number length %d",
+ rec_data->adn_idx, anr, number_length);
+ DBG("extension_record %d aas %d", extension_record, aas_record);
+
+ if (extension_record != UNUSED) {
+ struct record_to_read *ext_rec =
+ g_try_malloc0(sizeof(*ext_rec));
+ const struct pb_file_info *f_info = ext1_info(ref->pb_files);
+
+ if (ext_rec && f_info) {
+ ext_rec->file_id = f_info->file_id;
+ ext_rec->type_tag = TYPE_EXT1;
+ ext_rec->record_length = f_info->record_length;
+ ext_rec->record = extension_record;
+ ext_rec->adn_idx = rec_data->adn_idx;
+ ext_rec->anr_ext = TRUE;
+
+ ref->pending_records =
+ g_slist_prepend(ref->pending_records, ext_rec);
+ }
+ }
+}
+
+static void handle_email(size_t len, const unsigned char *msg,
+ struct pb_ref_rec *ref,
+ const struct record_to_read *rec_data)
+{
+ char *email;
+ struct phonebook_entry *entry;
+
+ /* There are additional fields for type 2 files */
+ if (rec_data->set_by_iap)
+ len -= 2;
+
+ email = sim_string_to_utf8(msg, len);
+ if (email == NULL || *email == '\0') {
+ g_free(email);
+ return;
+ }
+
+ entry = g_tree_lookup(ref->phonebook,
+ GINT_TO_POINTER(rec_data->adn_idx));
+ if (entry == NULL) {
+ g_free(email);
+ return;
+ }
+
+ /* if one already exists, delete it */
+ if (entry->email)
+ g_free(entry->email);
+
+ DBG("Adding email to entry %d", rec_data->adn_idx);
+ DBG("name %s", entry->name);
+
+ entry->email = email;
+}
+
+static void handle_ext1(size_t len, const unsigned char *msg,
+ struct pb_ref_rec *ref,
+ const struct record_to_read *rec_data)
+{
+ unsigned number_length, i, next_extension_record;
+ struct phonebook_entry *entry;
+ char *ext_number;
+
+ if (len < 13) {
+ ofono_error("%s: bad EF_EXT1 record size", __func__);
+ return;
+ }
+
+ /* Check if there is more extension data */
+ next_extension_record = msg[12];
+ if (next_extension_record != UNUSED) {
+ struct record_to_read *ext_rec =
+ g_try_malloc0(sizeof(*ext_rec));
+ const struct pb_file_info *f_info = ext1_info(ref->pb_files);
+
+ if (ext_rec && f_info) {
+ DBG("next_extension_record %d", next_extension_record);
+
+ ext_rec->file_id = f_info->file_id;
+ ext_rec->record_length = f_info->record_length;
+ ext_rec->type_tag = TYPE_EXT1;
+ ext_rec->record = next_extension_record;
+ ext_rec->adn_idx = rec_data->adn_idx;
+ ext_rec->anr_ext = rec_data->anr_ext;
+
+ ref->pending_records =
+ g_slist_prepend(ref->pending_records, ext_rec);
+ }
+ }
+
+ if (msg[0] != EXT1_ADDITIONAL_DATA) {
+ DBG("EXT1 record with subaddress ignored");
+ return;
+ }
+
+ number_length = msg[1];
+ ext_number = g_try_malloc0(2 * number_length + 1);
+ if (ext_number == NULL)
+ return;
+
+ for (i = 0; i < number_length; i++) {
+ ext_number[2 * i] = digit_to_utf8[msg[2 + i] & 0x0f];
+ ext_number[2 * i + 1] = digit_to_utf8[msg[2 + i] >> 4];
+ }
+
+ DBG("Number extension %s", ext_number);
+ DBG("number length %d", number_length);
+
+ DBG("Looking for ADN entry %d", rec_data->adn_idx);
+ entry = g_tree_lookup(ref->phonebook,
+ GINT_TO_POINTER(rec_data->adn_idx));
+ if (entry == NULL) {
+ g_free(ext_number);
+ return;
+ }
+
+ if (rec_data->anr_ext) {
+ char *anr = entry->anr;
+ entry->anr = g_strconcat(anr, ext_number, NULL);
+ g_free(anr);
+ } else {
+ char *number = entry->number;
+ entry->number = g_strconcat(number, ext_number, NULL);
+ g_free(number);
+ }
+
+ g_free(ext_number);
+}
+
+static const char *file_tag_to_string(enum file_type_tag tag)
+{
+ switch (tag) {
+ case TYPE_ADN: return "ADN";
+ case TYPE_IAP: return "IAP";
+ case TYPE_EXT1: return "EXT1";
+ case TYPE_SNE: return "SNE";
+ case TYPE_ANR: return "ANR";
+ case TYPE_PBC: return "PBC";
+ case TYPE_GPR: return "GPR";
+ case TYPE_AAS: return "AAS";
+ case TYPE_GAS: return "GAS";
+ case TYPE_UID: return "UID";
+ case TYPE_EMAIL: return "EMAIL";
+ case TYPE_CCP1: return "CCP1";
+ default: return "<UNKNOWN>";
+ }
+}
+
+static void decode_read_response(const struct record_to_read *rec_data,
+ const unsigned char *msg, size_t len,
+ struct pb_ref_rec *ref)
+{
+ DBG("Decoding %s type record", file_tag_to_string(rec_data->type_tag));
+ switch (rec_data->type_tag) {
+ case TYPE_IAP:
+ handle_iap(len, msg, ref, rec_data);
+ break;
+ case TYPE_SNE:
+ handle_sne(len, msg, ref, rec_data);
+ break;
+ case TYPE_ANR:
+ handle_anr(len, msg, ref, rec_data);
+ break;
+ case TYPE_EMAIL:
+ handle_email(len, msg, ref, rec_data);
+ break;
+ case TYPE_EXT1:
+ handle_ext1(len, msg, ref, rec_data);
+ break;
+ default:
+ DBG("Skipping type");
+ break;
+ }
+}
+
+static gboolean export_entry(gpointer key, gpointer value, gpointer data)
+{
+ struct ofono_phonebook *pb = data;
+ struct phonebook_entry *entry = value;
+
+ ofono_phonebook_entry(pb, -1,
+ entry->number, -1,
+ entry->name, -1,
+ NULL,
+ entry->anr, -1,
+ entry->sne,
+ entry->email,
+ NULL, NULL);
+
+ g_free(entry->name);
+ g_free(entry->number);
+ g_free(entry->email);
+ g_free(entry->anr);
+ g_free(entry->sne);
+ g_free(entry);
+
+ return FALSE;
+}
+
+static void export_and_return(gboolean ok, struct cb_data *cbd)
+{
+ struct ofono_phonebook *pb = cbd->user;
+ ofono_phonebook_cb_t cb = cbd->cb;
+ struct pb_data *pbd = ofono_phonebook_get_data(pb);
+ GSList *l;
+
+ DBG("phonebook fully read");
+
+ for (l = pbd->pb_refs; l != NULL; l = l->next) {
+ struct pb_ref_rec *ref = l->data;
+
+ g_tree_foreach(ref->phonebook, export_entry, pb);
+ g_tree_destroy(ref->phonebook);
+ g_slist_free_full(ref->pending_records, g_free);
+ g_slist_free_full(ref->pb_files, g_free);
+ }
+
+ g_slist_free_full(pbd->pb_refs, g_free);
+ pbd->pb_refs = NULL;
+
+ if (ok)
+ CALLBACK_WITH_SUCCESS(cb, cbd->data);
+ else
+ CALLBACK_WITH_FAILURE(cb, cbd->data);
+
+ g_free(cbd);
+}
+
+static void read_record_cb(int ok, int total_length, int record,
+ const unsigned char *data,
+ int record_length, void *userdata)
+{
+ struct cb_data *cbd = userdata;
+ struct ofono_phonebook *pb = cbd->user;
+ struct pb_data *pbd = ofono_phonebook_get_data(pb);
+ struct pb_ref_rec *ref = pbd->pb_ref_next->data;
+ struct record_to_read *rec;
+
+ if (!ok) {
+ ofono_error("%s: error %d", __func__, ok);
+ export_and_return(FALSE, cbd);
+ return;
+ }
+
+ DBG("ok %d; total_length %d; record %d; record_length %d",
+ ok, total_length, record, record_length);
+
+ rec = ref->next_record->data;
+
+ /* This call might add elements to pending_records */
+ decode_read_response(rec, data, record_length, ref);
+
+ ref->pending_records = g_slist_remove(ref->pending_records, rec);
+ g_free(rec);
+
+ if (ref->pending_records) {
+ struct record_to_read *rec;
+
+ ref->next_record = ref->pending_records;
+ rec = ref->next_record->data;
+
+ ofono_sim_read_record(pbd->sim_context, rec->file_id,
+ OFONO_SIM_FILE_STRUCTURE_FIXED,
+ rec->record,
+ rec->record_length,
+ pbd->df_path, pbd->df_size,
+ read_record_cb, cbd);
+ } else {
+ /* Read files from next EF_PBR record, if any */
+
+ pbd->pb_ref_next = pbd->pb_ref_next->next;
+ if (pbd->pb_ref_next == NULL) {
+ export_and_return(TRUE, cbd);
+ } else {
+ struct pb_ref_rec *ref;
+
+ DBG("Next EFpbr record");
+
+ ref = pbd->pb_ref_next->data;
+
+ if (!ref->pb_files) {
+ export_and_return(TRUE, cbd);
+ } else {
+ struct pb_file_info *file_info;
+
+ ref->pb_next = ref->pb_files;
+ file_info = ref->pb_files->data;
+
+ ofono_sim_read_info(pbd->sim_context,
+ file_info->file_id,
+ OFONO_SIM_FILE_STRUCTURE_FIXED,
+ pbd->df_path, pbd->df_size,
+ read_info_cb, cbd);
+ }
+ }
+ }
+}
+
+static void pb_adn_cb(int ok, int total_length, int record,
+ const unsigned char *data,
+ int record_length, void *userdata)
+{
+ struct cb_data *cbd = userdata;
+ struct ofono_phonebook *pb = cbd->user;
+ struct pb_data *pbd = ofono_phonebook_get_data(pb);
+ struct pb_ref_rec *ref = pbd->pb_ref_next->data;
+ GSList *l;
+
+ if (!ok) {
+ ofono_error("%s: error %d", __func__, ok);
+ export_and_return(FALSE, cbd);
+ return;
+ }
+
+ DBG("ok %d; total_length %d; record %d; record_length %d",
+ ok, total_length, record, record_length);
+
+ if (handle_adn(record_length, data, ref, record) != NULL) {
+ /* Add type 1 records */
+ for (l = ref->pb_files; l; l = l->next) {
+ const struct pb_file_info *f_info = l->data;
+ struct record_to_read *ext_rec;
+
+ if (f_info->pbr_type == TYPE_1_TAG &&
+ f_info->file_type != TYPE_ADN) {
+ ext_rec = g_try_malloc0(sizeof(*ext_rec));
+ if (ext_rec == NULL)
+ break;
+
+ ext_rec->file_id = f_info->file_id;
+ ext_rec->type_tag = f_info->file_type;
+ ext_rec->record_length = f_info->record_length;
+ ext_rec->record = record;
+ ext_rec->adn_idx = record;
+
+ ref->pending_records =
+ g_slist_prepend(ref->pending_records,
+ ext_rec);
+ }
+ }
+ }
+
+ if (record*record_length >= total_length) {
+ DBG("All ADN records read: reading additional files");
+
+ if (ref->pending_records) {
+ struct record_to_read *rec;
+
+ ref->next_record = ref->pending_records;
+ rec = ref->next_record->data;
+
+ ofono_sim_read_record(pbd->sim_context, rec->file_id,
+ OFONO_SIM_FILE_STRUCTURE_FIXED,
+ rec->record,
+ rec->record_length,
+ pbd->df_path, pbd->df_size,
+ read_record_cb, cbd);
+ } else {
+ export_and_return(TRUE, cbd);
+ }
+ }
+}
+
+static void read_info_cb(int ok, unsigned char file_status,
+ int total_length, int record_length,
+ void *userdata)
+{
+ struct cb_data *cbd = userdata;
+ struct ofono_phonebook *pb = cbd->user;
+ struct pb_data *pbd = ofono_phonebook_get_data(pb);
+ struct pb_file_info *file_info;
+ struct pb_ref_rec *ref = pbd->pb_ref_next->data;
+
+ file_info = ref->pb_next->data;
+ ref->pb_next = ref->pb_next->next;
+
+ if (ok) {
+ file_info->record_length = record_length;
+ file_info->file_length = total_length;
+
+ DBG("file id %x record length %d total_length %d",
+ file_info->file_id, record_length, total_length);
+ } else {
+ ofono_warn("%s: %x not found", __func__, file_info->file_id);
+ ref->pb_files = g_slist_remove(ref->pb_files, file_info);
+ g_free(file_info);
+ }
+
+ if (ref->pb_next == NULL) {
+ if (ref->pb_files == NULL) {
+ ofono_warn("%s: no phonebook on SIM", __func__);
+ export_and_return(FALSE, cbd);
+ return;
+ }
+
+ /* Read full contents of the master file */
+ file_info = ref->pb_files->data;
+
+ ofono_sim_read_path(pbd->sim_context, file_info->file_id,
+ OFONO_SIM_FILE_STRUCTURE_FIXED,
+ pbd->df_path, pbd->df_size,
+ pb_adn_cb, cbd);
+ } else {
+ file_info = ref->pb_next->data;
+
+ ofono_sim_read_info(pbd->sim_context, file_info->file_id,
+ OFONO_SIM_FILE_STRUCTURE_FIXED,
+ pbd->df_path, pbd->df_size,
+ read_info_cb, cbd);
+ }
+}
+
+static void start_sim_app_read(struct cb_data *cbd)
+{
+ struct ofono_phonebook *pb = cbd->user;
+ struct pb_data *pbd = ofono_phonebook_get_data(pb);
+ struct pb_ref_rec *ref_rec;
+ struct pb_file_info *f_info;
+ struct pb_file_info *f_ext1;
+
+ pbd->df_path = sim_path;
+ pbd->df_size = sizeof(sim_path);
+
+ ref_rec = g_try_malloc0(sizeof(*ref_rec));
+ if (ref_rec == NULL) {
+ ofono_error("%s: OOM", __func__);
+ export_and_return(FALSE, cbd);
+ return;
+ }
+
+ ref_rec->phonebook = g_tree_new(comp_int);
+
+ /* Only EF_ADN and EF_EXT1 read for SIM */
+
+ f_info = g_try_malloc0(sizeof(*f_info));
+ if (f_info == NULL) {
+ ofono_error("%s: OOM", __func__);
+ export_and_return(FALSE, cbd);
+ return;
+ }
+
+ f_info->file_id = SIM_EFADN_FILEID;
+ f_info->pbr_type = TYPE_1_TAG;
+ f_info->file_type = TYPE_ADN;
+ ref_rec->pb_files = g_slist_append(ref_rec->pb_files, f_info);
+
+ f_ext1 = g_try_malloc0(sizeof(*f_ext1));
+ if (f_ext1 == NULL) {
+ ofono_error("%s: OOM", __func__);
+ export_and_return(FALSE, cbd);
+ return;
+ }
+
+ f_ext1->file_id = SIM_EFEXT1_FILEID;
+ f_ext1->pbr_type = TYPE_3_TAG;
+ f_ext1->file_type = TYPE_EXT1;
+ ref_rec->pb_files = g_slist_append(ref_rec->pb_files, f_ext1);
+
+ pbd->pb_refs = g_slist_append(pbd->pb_refs, ref_rec);
+ pbd->pb_ref_next = pbd->pb_refs;
+
+ ref_rec->pb_next = ref_rec->pb_files;
+
+ /* Start reading process for MF */
+ ofono_sim_read_info(pbd->sim_context, f_info->file_id,
+ OFONO_SIM_FILE_STRUCTURE_FIXED,
+ pbd->df_path, pbd->df_size,
+ read_info_cb, cbd);
+}
+
+static void pb_reference_data_cb(int ok, int total_length, int record,
+ const unsigned char *sdata,
+ int record_length, void *userdata)
+{
+ struct cb_data *cbd = userdata;
+ struct ofono_phonebook *pb = cbd->user;
+ struct pb_data *pbd = ofono_phonebook_get_data(pb);
+ const unsigned char *ptr = sdata;
+ gboolean finished = FALSE;
+ struct pb_ref_rec *ref_rec;
+
+ DBG("total_length %d record %d record_length %d",
+ total_length, record, record_length);
+
+ if (!ok) {
+ /* We migh have a SIM instead of USIM application: try that */
+ DBG("%s: error %d, trying SIM files", __func__, ok);
+ start_sim_app_read(cbd);
+ return;
+ }
+
+ ref_rec = g_try_malloc0(sizeof(*ref_rec));
+ if (ref_rec == NULL) {
+ ofono_error("%s: OOM", __func__);
+ export_and_return(FALSE, cbd);
+ return;
+ }
+
+ ref_rec->phonebook = g_tree_new(comp_int);
+
+ while (ptr < sdata + record_length && finished == FALSE) {
+ int typelen, file_id, i;
+ enum constructed_tag pbr_type = *ptr;
+
+ switch (pbr_type) {
+ case TYPE_1_TAG:
+ case TYPE_2_TAG:
+ case TYPE_3_TAG:
+ typelen = *(ptr + 1);
+ DBG("File type=%02X, len=%d", *ptr, typelen);
+ ptr += 2;
+ i = 0;
+
+ while (i < typelen) {
+ struct pb_file_info *file_info =
+ g_try_new0(struct pb_file_info, 1);
+ if (!file_info) {
+ ofono_error("%s: OOM", __func__);
+ export_and_return(FALSE, cbd);
+ return;
+ }
+
+ file_id = (ptr[i + 2] << 8) + ptr[i + 3];
+
+ DBG("creating file info for File type=%02X",
+ ptr[i]);
+ DBG("File ID=%04X", file_id);
+
+ file_info->pbr_type = pbr_type;
+ file_info->file_type = ptr[i];
+ file_info->file_id = file_id;
+ /* Keep order, important for type 2 files */
+ ref_rec->pb_files =
+ g_slist_append(ref_rec->pb_files,
+ file_info);
+ i += ptr[i + 1] + 2;
+ }
+
+ ptr += typelen;
+ break;
+ default:
+ DBG("All handled %02x", *ptr);
+ finished = TRUE;
+ break;
+ }
+ }
+
+ pbd->pb_refs = g_slist_append(pbd->pb_refs, ref_rec);
+
+ if (record*record_length >= total_length) {
+ struct pb_ref_rec *ref;
+ struct pb_file_info *file_info;
+
+ DBG("All EFpbr records read");
+
+ pbd->pb_ref_next = pbd->pb_refs;
+ ref = pbd->pb_ref_next->data;
+
+ if (ref->pb_files == NULL) {
+ ofono_error("%s: no files to read", __func__);
+ export_and_return(FALSE, cbd);
+ return;
+ }
+
+ ref->pb_next = ref->pb_files;
+ file_info = ref->pb_files->data;
+
+ /* Start reading process for first EF_PBR entry */
+
+ ofono_sim_read_info(pbd->sim_context, file_info->file_id,
+ OFONO_SIM_FILE_STRUCTURE_FIXED,
+ pbd->df_path, pbd->df_size,
+ read_info_cb, cbd);
+ }
+}
+
+static void ril_export_entries(struct ofono_phonebook *pb,
+ const char *storage,
+ ofono_phonebook_cb_t cb, void *data)
+{
+ struct pb_data *pbd = ofono_phonebook_get_data(pb);
+ struct cb_data *cbd;
+
+ DBG("Storage %s", storage);
+
+ /* Only for SIM memory */
+ if (strcmp(storage, "SM") != 0) {
+ CALLBACK_WITH_FAILURE(cb, data);
+ return;
+ }
+
+ cbd = cb_data_new(cb, data, pb);
+
+ /* Assume USIM, change in case EF_PBR is not present */
+ pbd->df_path = usim_path;
+ pbd->df_size = sizeof(usim_path);
+
+ ofono_sim_read(pbd->sim_context, SIM_EFPBR_FILEID,
+ OFONO_SIM_FILE_STRUCTURE_FIXED,
+ pb_reference_data_cb, cbd);
+}
+
+static gboolean ril_delayed_register(gpointer user_data)
+{
+ struct ofono_phonebook *pb = user_data;
+
+ ofono_phonebook_register(pb);
+ return FALSE;
+}
+
+static int ril_phonebook_probe(struct ofono_phonebook *pb,
+ unsigned int vendor, void *user)
+{
+ struct ofono_modem *modem = user;
+ struct pb_data *pd = g_try_new0(struct pb_data, 1);
+ if (pd == NULL)
+ return -ENOMEM;
+
+ pd->sim = __ofono_atom_find(OFONO_ATOM_TYPE_SIM, modem);
+ if (pd->sim == NULL)
+ return -ENOENT;
+
+ pd->sim_context = ofono_sim_context_create(pd->sim);
+ if (pd->sim_context == NULL)
+ return -ENOENT;
+
+ ofono_phonebook_set_data(pb, pd);
+
+ g_idle_add(ril_delayed_register, pb);
+
+ return 0;
+}
+
+static void ril_phonebook_remove(struct ofono_phonebook *pb)
+{
+ struct pb_data *pbd = ofono_phonebook_get_data(pb);
+
+ ofono_phonebook_set_data(pb, NULL);
+ ofono_sim_context_free(pbd->sim_context);
+
+ g_free(pbd);
+}
+
+static struct ofono_phonebook_driver driver = {
+ .name = RILMODEM,
+ .probe = ril_phonebook_probe,
+ .remove = ril_phonebook_remove,
+ .export_entries = ril_export_entries
+};
+
+void ril_phonebook_init(void)
+{
+ ofono_phonebook_driver_register(&driver);
+}
+
+void ril_phonebook_exit(void)
+{
+ ofono_phonebook_driver_unregister(&driver);
+}