mirror of git://git.sysmocom.de/ofono
1056 lines
25 KiB
C
1056 lines
25 KiB
C
/*
|
|
*
|
|
* 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);
|
|
}
|