mirror of git://git.sysmocom.de/ofono
1710 lines
39 KiB
C
1710 lines
39 KiB
C
/*
|
|
*
|
|
* oFono - Open Source Telephony
|
|
*
|
|
* Copyright (C) 2011 ST-Ericsson AB.
|
|
* Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
|
|
*
|
|
* 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
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <errno.h>
|
|
|
|
#include <glib.h>
|
|
|
|
#include <gisi/message.h>
|
|
#include <gisi/client.h>
|
|
#include <gisi/iter.h>
|
|
|
|
#include <ofono/log.h>
|
|
#include <ofono/modem.h>
|
|
#include <ofono/sim.h>
|
|
|
|
#include "simutil.h"
|
|
#include "isimodem.h"
|
|
#include "isiutil.h"
|
|
#include "sim.h"
|
|
#include "uicc.h"
|
|
#include "uicc-util.h"
|
|
#include "debug.h"
|
|
|
|
/* File info parameters */
|
|
#define FCP_TEMPLATE 0x62
|
|
#define FCP_FILE_SIZE 0x80
|
|
#define FCP_FILE_DESC 0x82
|
|
#define FCP_FILE_ID 0x83
|
|
#define FCP_FILE_LIFECYCLE 0x8A
|
|
#define FCP_FILE_SECURITY_ARR 0x8B
|
|
#define FCP_FILE_SECURITY_COMPACT 0x8C
|
|
#define FCP_FILE_SECURITY_EXPANDED 0xAB
|
|
#define FCP_PIN_STATUS 0xC6
|
|
#define SIM_EFARR_FILEID 0x6f06
|
|
#define MAX_SIM_APPS 10
|
|
#define MAX_IMSI_LENGTH 15
|
|
|
|
enum uicc_flag {
|
|
UICC_FLAG_APP_STARTED = 1 << 0,
|
|
UICC_FLAG_PIN_STATE_RECEIVED = 1 << 1,
|
|
UICC_FLAG_PASSWD_REQUIRED = 1 << 2,
|
|
};
|
|
|
|
static GHashTable *g_modems;
|
|
|
|
struct file_info {
|
|
int fileid;
|
|
int length;
|
|
int structure;
|
|
int record_length;
|
|
uint8_t access[3];
|
|
uint8_t file_status;
|
|
};
|
|
|
|
static const struct file_info static_file_info[] = {
|
|
{ SIM_EFSPN_FILEID, 17, 0, 0, { 0x0e, 0xff, 0xee }, 1 },
|
|
{ SIM_EF_ICCID_FILEID, 10, 0, 10, { 0x0f, 0xff, 0xee }, 1 },
|
|
{ SIM_EFPL_FILEID, 1, 0, 1, { 0x0f, 0xff, 0xff }, 1 },
|
|
{ SIM_EFLI_FILEID, 1, 0, 1, { 0x0f, 0xff, 0xff }, 1 },
|
|
{ SIM_EFMSISDN_FILEID, 28, 1, 28, { 0x01, 0xff, 0xee }, 1 },
|
|
{ SIM_EFAD_FILEID, 20, 0, 20, { 0x0e, 0xff, 0xee }, 1 },
|
|
{ SIM_EFPHASE_FILEID, 1, 0, 1, { 0x0e, 0xff, 0xee }, 1 },
|
|
{ SIM_EFPNN_FILEID, 4 * 18, 1, 18, { 0x0e, 0xff, 0xee }, 1 },
|
|
{ SIM_EFOPL_FILEID, 4 * 24, 1, 24, { 0x0e, 0xff, 0xee }, 1 },
|
|
{ SIM_EFMBI_FILEID, 5, 1, 5, { 0x0e, 0xff, 0xee }, 1 },
|
|
{ SIM_EFMWIS_FILEID, 6, 1, 6, { 0x01, 0xff, 0xee }, 1 },
|
|
{ SIM_EFSPDI_FILEID, 64, 0, 64, { 0x0e, 0xff, 0xee }, 1 },
|
|
{ SIM_EFECC_FILEID, 5 * 3, 0, 3, { 0x0e, 0xff, 0xee }, 1 },
|
|
{ SIM_EFCBMIR_FILEID, 8 * 4, 0, 4, { 0x01, 0xff, 0xee }, 1 },
|
|
{ SIM_EFCBMI_FILEID, 8 * 2, 0, 2, { 0x01, 0xff, 0xee }, 1 },
|
|
{ SIM_EFCBMID_FILEID, 8 * 2, 0, 2, { 0x01, 0xff, 0x11 }, 1 },
|
|
{ SIM_EFSMSP_FILEID, 56, 1, 56, { 0x01, 0xff, 0xee }, 1 },
|
|
{ SIM_EFIMSI_FILEID, 9, 0, 9, { 0x0e, 0xff, 0xee }, 1 },
|
|
};
|
|
|
|
static gboolean check_resp(const GIsiMessage *msg, uint8_t msgid, uint8_t service)
|
|
{
|
|
uint8_t type;
|
|
uint8_t cause;
|
|
|
|
if (g_isi_msg_error(msg) < 0) {
|
|
DBG("Error: %s", g_isi_msg_strerror(msg));
|
|
return FALSE;
|
|
}
|
|
|
|
if (g_isi_msg_id(msg) != msgid) {
|
|
DBG("Unexpected msg: %s",
|
|
sim_message_id_name(g_isi_msg_id(msg)));
|
|
return FALSE;
|
|
}
|
|
|
|
if (!g_isi_msg_data_get_byte(msg, 1, &cause) ||
|
|
cause != UICC_STATUS_OK) {
|
|
DBG("Request failed: %s", uicc_status_name(cause));
|
|
return FALSE;
|
|
}
|
|
|
|
if (!g_isi_msg_data_get_byte(msg, 0, &type) || type != service) {
|
|
DBG("Unexpected service: 0x%02X (0x%02X)", type, service);
|
|
return FALSE;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
struct uicc_file_info_cb_data {
|
|
void *cb;
|
|
void *data;
|
|
void *user;
|
|
struct ofono_sim *sim;
|
|
};
|
|
|
|
static gboolean decode_uicc_usim_type(GIsiSubBlockIter *iter, uint16_t *length,
|
|
uint16_t *file_id,
|
|
uint16_t *record_length,
|
|
uint8_t *records, uint8_t *structure)
|
|
{
|
|
uint8_t fcp = 0;
|
|
uint8_t desc = 0;
|
|
uint8_t coding = 0;
|
|
uint8_t fcp_len = 0;
|
|
uint8_t read = 0;
|
|
uint8_t item_len = 0;
|
|
|
|
if (!g_isi_sb_iter_get_byte(iter, &fcp, 8))
|
|
return FALSE;
|
|
|
|
if (fcp != FCP_TEMPLATE)
|
|
return FALSE;
|
|
|
|
if (!g_isi_sb_iter_get_byte(iter, &fcp_len, 9))
|
|
return FALSE;
|
|
|
|
for (read = 0; read < fcp_len; read += item_len + 2) {
|
|
|
|
uint8_t id;
|
|
|
|
if (!g_isi_sb_iter_get_byte(iter, &id, read + 10))
|
|
return FALSE;
|
|
|
|
if (!g_isi_sb_iter_get_byte(iter, &item_len, read + 11))
|
|
return FALSE;
|
|
|
|
switch (id) {
|
|
case FCP_FILE_SIZE:
|
|
|
|
if (item_len != 2)
|
|
return FALSE;
|
|
|
|
if (!g_isi_sb_iter_get_word(iter, length, read + 10 + 2))
|
|
return FALSE;
|
|
|
|
break;
|
|
|
|
case FCP_FILE_ID:
|
|
|
|
if (item_len != 2)
|
|
return FALSE;
|
|
|
|
if (!g_isi_sb_iter_get_word(iter, file_id, read + 10 + 2))
|
|
return FALSE;
|
|
|
|
break;
|
|
|
|
case FCP_FILE_DESC:
|
|
|
|
if (item_len < 2)
|
|
return FALSE;
|
|
|
|
if (!g_isi_sb_iter_get_byte(iter, &desc, read + 10 + 2))
|
|
return FALSE;
|
|
|
|
if (!g_isi_sb_iter_get_byte(iter, &coding, read + 10 + 3))
|
|
return FALSE;
|
|
|
|
if (item_len < 4)
|
|
break;
|
|
|
|
if (!g_isi_sb_iter_get_word(iter, record_length,
|
|
read + 10 + 4))
|
|
return FALSE;
|
|
|
|
if (!g_isi_sb_iter_get_byte(iter, records, read + 10 + 6))
|
|
return FALSE;
|
|
|
|
break;
|
|
|
|
/*
|
|
* Not implemented, using static access rules
|
|
* as these are used only for cacheing See
|
|
* ETSI TS 102 221, ch 11.1.1.4.7 and Annexes
|
|
* E, F and G.
|
|
*/
|
|
case FCP_FILE_SECURITY_ARR:
|
|
case FCP_FILE_SECURITY_COMPACT:
|
|
case FCP_FILE_SECURITY_EXPANDED:
|
|
case FCP_FILE_LIFECYCLE:
|
|
default:
|
|
DBG("FCP id %02X not supported", id);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ((desc & 7) == 1)
|
|
*structure = OFONO_SIM_FILE_STRUCTURE_TRANSPARENT;
|
|
else if ((desc & 7) == 2)
|
|
*structure = OFONO_SIM_FILE_STRUCTURE_FIXED;
|
|
else if ((desc & 7) == 6)
|
|
*structure = OFONO_SIM_FILE_STRUCTURE_CYCLIC;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void uicc_file_info_resp_cb(const GIsiMessage *msg, void *opaque)
|
|
{
|
|
struct uicc_file_info_cb_data *cbd = opaque;
|
|
struct uicc_sim_data *sd = ofono_sim_get_data(cbd->sim);
|
|
struct file_info const *info = cbd->user;
|
|
ofono_sim_file_info_cb_t cb = cbd->cb;
|
|
|
|
GIsiSubBlockIter iter;
|
|
|
|
uint16_t length = 0;
|
|
uint16_t record_length = 0;
|
|
uint8_t structure = 0xFF;
|
|
uint8_t records = 0;
|
|
uint16_t file_id = 0;
|
|
uint8_t access[3] = {0, 0, 0};
|
|
uint8_t item_len = 0;
|
|
|
|
uint8_t message_id = 0;
|
|
uint8_t service_type = 0;
|
|
uint8_t status = 0;
|
|
uint8_t details = 0;
|
|
uint8_t num_subblocks = 0;
|
|
uint8_t file_status = 1;
|
|
|
|
message_id = g_isi_msg_id(msg);
|
|
|
|
DBG("uicc_file_info_resp_cb: msg_id=%d, msg len=%zu", message_id,
|
|
g_isi_msg_data_len(msg));
|
|
|
|
if (message_id != UICC_APPL_CMD_RESP)
|
|
goto error;
|
|
|
|
if (!g_isi_msg_data_get_byte(msg, 0, &service_type) ||
|
|
!g_isi_msg_data_get_byte(msg, 1, &status) ||
|
|
!g_isi_msg_data_get_byte(msg, 2, &details) ||
|
|
!g_isi_msg_data_get_byte(msg, 5, &num_subblocks))
|
|
goto error;
|
|
|
|
DBG("%s, service %s, status %s, details %s, nm_sb %d",
|
|
uicc_message_id_name(message_id),
|
|
uicc_service_type_name(service_type),
|
|
uicc_status_name(status), uicc_details_name(details),
|
|
num_subblocks);
|
|
|
|
if (info) {
|
|
access[0] = info->access[0];
|
|
access[1] = info->access[1];
|
|
access[2] = info->access[2];
|
|
file_status = info->file_status;
|
|
}
|
|
|
|
for (g_isi_sb_iter_init_full(&iter, msg, 6, TRUE, num_subblocks);
|
|
g_isi_sb_iter_is_valid(&iter);
|
|
g_isi_sb_iter_next(&iter)) {
|
|
|
|
uint8_t sb_id = g_isi_sb_iter_get_id(&iter);
|
|
|
|
DBG("Subblock %s", uicc_subblock_name(sb_id));
|
|
|
|
if (sb_id != UICC_SB_FCI)
|
|
continue;
|
|
|
|
DBG("Decoding UICC_SB_FCI");
|
|
|
|
switch (sd->app_type) {
|
|
case UICC_APPL_TYPE_UICC_USIM:
|
|
DBG("UICC_APPL_TYPE_UICC_USIM");
|
|
|
|
if (!decode_uicc_usim_type(&iter, &length, &file_id,
|
|
&record_length,
|
|
&records,
|
|
&structure))
|
|
goto error;
|
|
|
|
break;
|
|
|
|
case UICC_APPL_TYPE_ICC_SIM:
|
|
DBG("UICC_APPL_TYPE_ICC_SIM");
|
|
|
|
if (!g_isi_sb_iter_get_word(&iter, &length, 10))
|
|
goto error;
|
|
|
|
if (!g_isi_sb_iter_get_word(&iter, &file_id, 12))
|
|
goto error;
|
|
|
|
if (!g_isi_sb_iter_get_byte(&iter, &access[0], 16))
|
|
goto error;
|
|
|
|
if (!g_isi_sb_iter_get_byte(&iter, &access[0], 17))
|
|
goto error;
|
|
|
|
if (!g_isi_sb_iter_get_byte(&iter, &access[0], 18))
|
|
goto error;
|
|
|
|
if (!g_isi_sb_iter_get_byte(&iter, &item_len, 20))
|
|
goto error;
|
|
|
|
if (!g_isi_sb_iter_get_byte(&iter, &structure, 21))
|
|
goto error;
|
|
|
|
if (item_len == 2) {
|
|
uint8_t byte;
|
|
|
|
if (!g_isi_sb_iter_get_byte(&iter, &byte, 22))
|
|
goto error;
|
|
|
|
record_length = byte;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
DBG("Application type %d not supported", sd->app_type);
|
|
break;
|
|
}
|
|
|
|
DBG("fileid=%04X, filelen=%d, records=%d, reclen=%d, structure=%d",
|
|
file_id, length, records, record_length, structure);
|
|
|
|
CALLBACK_WITH_SUCCESS(cb, length, structure, record_length,
|
|
access, file_status, cbd->data);
|
|
return;
|
|
}
|
|
|
|
error:
|
|
DBG("Error reading file info");
|
|
CALLBACK_WITH_FAILURE(cb, -1, -1, -1, NULL, 0, cbd->data);
|
|
}
|
|
|
|
static gboolean send_uicc_read_file_info(GIsiClient *client, uint8_t app_id,
|
|
int fileid, uint8_t df_len,
|
|
int mf_path, int df1_path,
|
|
int df2_path,
|
|
GIsiNotifyFunc notify, void *data,
|
|
GDestroyNotify destroy)
|
|
{
|
|
const uint8_t msg[] = {
|
|
UICC_APPL_CMD_REQ,
|
|
UICC_APPL_FILE_INFO, /* Service type */
|
|
app_id,
|
|
UICC_SESSION_ID_NOT_USED,
|
|
0, 0, /* Filler */
|
|
1, /* Number of subblocks */
|
|
ISI_16BIT(UICC_SB_APPL_PATH),
|
|
ISI_16BIT(16), /* Subblock length */
|
|
ISI_16BIT(fileid),
|
|
uicc_get_sfi(fileid), /* Elementary file short file id */
|
|
0, /* Filler */
|
|
df_len,
|
|
0, /* Filler */
|
|
ISI_16BIT(mf_path),
|
|
ISI_16BIT(df1_path),
|
|
ISI_16BIT(df2_path),
|
|
};
|
|
|
|
return g_isi_client_send(client, msg, sizeof(msg), notify, data, destroy);
|
|
}
|
|
|
|
static void uicc_read_file_info(struct ofono_sim *sim, int fileid,
|
|
const unsigned char *path,
|
|
unsigned int path_len,
|
|
ofono_sim_file_info_cb_t cb, void *data)
|
|
{
|
|
struct uicc_sim_data *sd = ofono_sim_get_data(sim);
|
|
struct uicc_file_info_cb_data *cbd;
|
|
|
|
/* Prepare for static file info used for access rights */
|
|
int i;
|
|
int N = sizeof(static_file_info) / sizeof(static_file_info[0]);
|
|
int mf_path = 0;
|
|
int df1_path = 0;
|
|
int df2_path = 0;
|
|
uint8_t df_len = 0;
|
|
|
|
cbd = g_try_new0(struct uicc_file_info_cb_data, 1);
|
|
if (!cbd)
|
|
goto error;
|
|
|
|
cbd->cb = cb;
|
|
cbd->data = data;
|
|
cbd->sim = sim;
|
|
cbd->user = NULL;
|
|
|
|
DBG("File info for ID=%04X app id %d", fileid, sd->app_id);
|
|
|
|
for (i = 0; i < N; i++) {
|
|
if (fileid == static_file_info[i].fileid) {
|
|
cbd->user = (void *) &static_file_info[i];
|
|
break;
|
|
}
|
|
}
|
|
|
|
DBG("File info for ID=%04X: %p", fileid, cbd->user);
|
|
|
|
if (!uicc_get_fileid_path(sd, &mf_path, &df1_path, &df2_path,
|
|
&df_len, fileid))
|
|
goto error;
|
|
|
|
if (send_uicc_read_file_info(sd->client, sd->app_id, fileid, df_len,
|
|
mf_path, df1_path, df2_path,
|
|
uicc_file_info_resp_cb,
|
|
cbd, g_free))
|
|
return;
|
|
|
|
error:
|
|
CALLBACK_WITH_FAILURE(cb, -1, -1, -1, NULL, 0, data);
|
|
g_free(cbd);
|
|
}
|
|
|
|
static void uicc_read_file_transp_resp_cb(const GIsiMessage *msg, void *opaque)
|
|
{
|
|
struct isi_cb_data *cbd = opaque;
|
|
ofono_sim_read_cb_t cb = cbd->cb;
|
|
GIsiSubBlockIter iter;
|
|
|
|
uint32_t filelen = 0;
|
|
uint8_t *filedata = NULL;
|
|
uint8_t num_sb = 0;
|
|
|
|
DBG("");
|
|
|
|
if (!check_resp(msg, UICC_APPL_CMD_RESP, UICC_APPL_READ_TRANSPARENT))
|
|
goto error;
|
|
|
|
if (!g_isi_msg_data_get_byte(msg, 5, &num_sb))
|
|
goto error;
|
|
|
|
for (g_isi_sb_iter_init_full(&iter, msg, 6, TRUE, num_sb);
|
|
g_isi_sb_iter_is_valid(&iter);
|
|
g_isi_sb_iter_next(&iter)) {
|
|
|
|
int sb_id = g_isi_sb_iter_get_id(&iter);
|
|
|
|
DBG("Subblock %s", uicc_subblock_name(sb_id));
|
|
|
|
if (sb_id != UICC_SB_FILE_DATA)
|
|
continue;
|
|
|
|
if (!g_isi_sb_iter_get_dword(&iter, &filelen, 4))
|
|
goto error;
|
|
|
|
if (!g_isi_sb_iter_get_struct(&iter, (void **) &filedata,
|
|
filelen, 8))
|
|
goto error;
|
|
|
|
DBG("Transparent EF read: 1st byte %02x, len %d",
|
|
filedata[0], filelen);
|
|
CALLBACK_WITH_SUCCESS(cb, filedata, filelen, cbd->data);
|
|
return;
|
|
}
|
|
|
|
error:
|
|
DBG("Error reading transparent EF");
|
|
CALLBACK_WITH_FAILURE(cb, NULL, 0, cbd->data);
|
|
}
|
|
|
|
static gboolean send_uicc_read_file_transparent(GIsiClient *client,
|
|
uint8_t app_id, uint8_t client_id,
|
|
int fileid, uint8_t df_len,
|
|
int mf_path, int df1_path,
|
|
int df2_path,
|
|
GIsiNotifyFunc notify,
|
|
void *data,
|
|
GDestroyNotify destroy)
|
|
{
|
|
const uint8_t msg[] = {
|
|
UICC_APPL_CMD_REQ,
|
|
UICC_APPL_READ_TRANSPARENT,
|
|
app_id,
|
|
UICC_SESSION_ID_NOT_USED,
|
|
0, 0, /* Filler */
|
|
3, /* Number of subblocks */
|
|
ISI_16BIT(UICC_SB_CLIENT),
|
|
ISI_16BIT(8), /* Subblock length*/
|
|
0, 0, 0, /* Filler */
|
|
client_id,
|
|
ISI_16BIT(UICC_SB_TRANSPARENT),
|
|
ISI_16BIT(8), /* Subblock length */
|
|
ISI_16BIT(0), /* File offset */
|
|
ISI_16BIT(0), /* Data amount (0=all) */
|
|
ISI_16BIT(UICC_SB_APPL_PATH),
|
|
ISI_16BIT(16), /* Subblock length */
|
|
ISI_16BIT(fileid),
|
|
uicc_get_sfi(fileid), /* Elementary file short file id */
|
|
0, /* Filler */
|
|
df_len,
|
|
0,
|
|
ISI_16BIT(mf_path),
|
|
ISI_16BIT(df1_path),
|
|
ISI_16BIT(df2_path),
|
|
};
|
|
|
|
return g_isi_client_send(client, msg, sizeof(msg), notify, data, destroy);
|
|
}
|
|
|
|
static void uicc_read_file_transparent(struct ofono_sim *sim, int fileid,
|
|
int start, int length,
|
|
const unsigned char *path,
|
|
unsigned int path_len,
|
|
ofono_sim_read_cb_t cb, void *data)
|
|
{
|
|
struct uicc_sim_data *sd = ofono_sim_get_data(sim);
|
|
struct isi_cb_data *cbd = isi_cb_data_new(sim, cb, data);
|
|
int mf_path = 0;
|
|
int df1_path = 0;
|
|
int df2_path = 0;
|
|
uint8_t df_len = 0;
|
|
|
|
if (!cbd || !sd)
|
|
goto error;
|
|
|
|
DBG("File ID=%04X, client %d, AID %d", fileid, sd->client_id,
|
|
sd->app_id);
|
|
|
|
if (!uicc_get_fileid_path(sd, &mf_path, &df1_path,
|
|
&df2_path, &df_len, fileid))
|
|
goto error;
|
|
|
|
if (send_uicc_read_file_transparent(sd->client, sd->app_id, sd->client_id,
|
|
fileid, df_len, mf_path,
|
|
df1_path, df2_path,
|
|
uicc_read_file_transp_resp_cb,
|
|
cbd, g_free))
|
|
return;
|
|
|
|
error:
|
|
DBG("Read file transparent failed");
|
|
CALLBACK_WITH_FAILURE(cb, NULL, 0, data);
|
|
g_free(cbd);
|
|
}
|
|
|
|
static void read_file_linear_resp(const GIsiMessage *msg, void *opaque)
|
|
{
|
|
struct isi_cb_data *cbd = opaque;
|
|
ofono_sim_read_cb_t cb = cbd->cb;
|
|
GIsiSubBlockIter iter;
|
|
uint8_t num_sb = 0;
|
|
uint8_t *filedata = NULL;
|
|
uint32_t filelen = 0;
|
|
|
|
DBG("");
|
|
|
|
if (!check_resp(msg, UICC_APPL_CMD_RESP, UICC_APPL_READ_LINEAR_FIXED))
|
|
goto error;
|
|
|
|
if (!g_isi_msg_data_get_byte(msg, 5, &num_sb))
|
|
goto error;
|
|
|
|
for (g_isi_sb_iter_init_full(&iter, msg, 6, TRUE, num_sb);
|
|
g_isi_sb_iter_is_valid(&iter);
|
|
g_isi_sb_iter_next(&iter)) {
|
|
|
|
uint8_t sb_id = g_isi_sb_iter_get_id(&iter);
|
|
|
|
DBG("Subblock %s", uicc_subblock_name(sb_id));
|
|
|
|
if (sb_id != UICC_SB_FILE_DATA)
|
|
continue;
|
|
|
|
if (!g_isi_sb_iter_get_dword(&iter, &filelen, 4))
|
|
goto error;
|
|
|
|
if (!g_isi_sb_iter_get_struct(&iter, (void **) &filedata,
|
|
filelen, 8))
|
|
goto error;
|
|
|
|
DBG("Linear fixed EF read: 1st byte %02x, len %d", filedata[0],
|
|
filelen);
|
|
|
|
CALLBACK_WITH_SUCCESS(cb, filedata, filelen, cbd->data);
|
|
return;
|
|
}
|
|
|
|
error:
|
|
CALLBACK_WITH_FAILURE(cb, NULL, 0, cbd->data);
|
|
}
|
|
|
|
static gboolean send_uicc_read_file_linear(GIsiClient *client, uint8_t app_id,
|
|
uint8_t client_id,
|
|
int fileid, int record,
|
|
int rec_length,
|
|
unsigned char df_len,
|
|
int mf_path, int df1_path,
|
|
int df2_path,
|
|
GIsiNotifyFunc notify,
|
|
void *data,
|
|
GDestroyNotify destroy)
|
|
{
|
|
const uint8_t msg[] = {
|
|
UICC_APPL_CMD_REQ,
|
|
UICC_APPL_READ_LINEAR_FIXED,
|
|
app_id,
|
|
UICC_SESSION_ID_NOT_USED,
|
|
0, 0, /* Filler */
|
|
3, /* Number of subblocks */
|
|
ISI_16BIT(UICC_SB_CLIENT),
|
|
ISI_16BIT(8), /*Subblock length */
|
|
0, 0, 0, /* Filler */
|
|
client_id,
|
|
ISI_16BIT(UICC_SB_LINEAR_FIXED),
|
|
ISI_16BIT(8), /*Subblock length */
|
|
record,
|
|
0, /* Record offset */
|
|
rec_length & 0xff, /*Data amount (0=all)*/
|
|
0,
|
|
ISI_16BIT(UICC_SB_APPL_PATH),
|
|
ISI_16BIT(16), /* Subblock length */
|
|
ISI_16BIT(fileid),
|
|
uicc_get_sfi(fileid), /* Elementary file short file id */
|
|
0, /* Filler */
|
|
df_len,
|
|
0,
|
|
ISI_16BIT(mf_path),
|
|
ISI_16BIT(df1_path),
|
|
ISI_16BIT(df2_path),
|
|
};
|
|
|
|
return g_isi_client_send(client, msg, sizeof(msg), notify, data, destroy);
|
|
}
|
|
|
|
static void uicc_read_file_linear(struct ofono_sim *sim, int fileid, int record,
|
|
int rec_length,
|
|
const unsigned char *path,
|
|
unsigned int path_len,
|
|
ofono_sim_read_cb_t cb, void *data)
|
|
{
|
|
struct uicc_sim_data *sd = ofono_sim_get_data(sim);
|
|
struct isi_cb_data *cbd = isi_cb_data_new(sim, cb, data);
|
|
int mf_path = 0;
|
|
int df1_path = 0;
|
|
int df2_path = 0;
|
|
uint8_t df_len = 0;
|
|
|
|
if (!sd || !cbd)
|
|
goto error;
|
|
|
|
DBG("File ID=%04X, record %d, client %d AID %d", fileid, record,
|
|
sd->client_id, sd->app_id);
|
|
|
|
if (!uicc_get_fileid_path(sd, &mf_path, &df1_path, &df2_path,
|
|
&df_len, fileid))
|
|
goto error;
|
|
|
|
if (send_uicc_read_file_linear(sd->client, sd->app_id, sd->client_id,
|
|
fileid, record, rec_length, df_len,
|
|
mf_path, df1_path, df2_path,
|
|
read_file_linear_resp, cbd, g_free))
|
|
return;
|
|
|
|
error:
|
|
CALLBACK_WITH_FAILURE(cb, NULL, 0, data);
|
|
g_free(cbd);
|
|
}
|
|
|
|
static void uicc_read_file_cyclic(struct ofono_sim *sim, int fileid,
|
|
int record, int length,
|
|
const unsigned char *path,
|
|
unsigned int path_len,
|
|
ofono_sim_read_cb_t cb, void *data)
|
|
{
|
|
DBG("Not implemented");
|
|
CALLBACK_WITH_FAILURE(cb, NULL, 0, data);
|
|
}
|
|
|
|
static void uicc_write_file_transparent(struct ofono_sim *sim, int fileid,
|
|
int start, int length,
|
|
const unsigned char *value,
|
|
const unsigned char *path,
|
|
unsigned int path_len,
|
|
ofono_sim_write_cb_t cb, void *data)
|
|
{
|
|
DBG("Not implemented");
|
|
CALLBACK_WITH_FAILURE(cb, data);
|
|
}
|
|
|
|
static void uicc_write_file_linear(struct ofono_sim *sim, int fileid, int record,
|
|
int length, const unsigned char *value,
|
|
const unsigned char *path,
|
|
unsigned int path_len,
|
|
ofono_sim_write_cb_t cb, void *data)
|
|
{
|
|
DBG("Not implemented");
|
|
CALLBACK_WITH_FAILURE(cb, data);
|
|
}
|
|
|
|
static void uicc_write_file_cyclic(struct ofono_sim *sim, int fileid,
|
|
int length,
|
|
const unsigned char *value,
|
|
const unsigned char *path,
|
|
unsigned int path_len,
|
|
ofono_sim_write_cb_t cb, void *data)
|
|
{
|
|
DBG("Not implemented");
|
|
CALLBACK_WITH_FAILURE(cb, data);
|
|
}
|
|
|
|
static gboolean decode_imsi(uint8_t *data, int len, char *imsi)
|
|
{
|
|
int i = 1; /* Skip first byte, the length field */
|
|
int j = 0;
|
|
|
|
if (data == NULL || len == 0)
|
|
return FALSE;
|
|
|
|
if (data[0] != 8 || data[0] > len)
|
|
return FALSE;
|
|
|
|
/* Ignore low-order semi-octet of the first byte */
|
|
imsi[j] = ((data[i] & 0xF0) >> 4) + '0';
|
|
|
|
for (i++, j++; i - 1 < data[0] && j < MAX_IMSI_LENGTH; i++) {
|
|
char nibble;
|
|
|
|
imsi[j++] = (data[i] & 0x0F) + '0';
|
|
nibble = (data[i] & 0xF0) >> 4;
|
|
|
|
if (nibble != 0x0F)
|
|
imsi[j++] = nibble + '0';
|
|
}
|
|
|
|
imsi[j] = '\0';
|
|
return TRUE;
|
|
}
|
|
|
|
static void uicc_read_imsi_resp(const GIsiMessage *msg, void *opaque)
|
|
{
|
|
struct isi_cb_data *cbd = opaque;
|
|
ofono_sim_imsi_cb_t cb = cbd->cb;
|
|
GIsiSubBlockIter iter;
|
|
|
|
uint32_t filelen = 0;
|
|
uint8_t *filedata = NULL;
|
|
uint8_t num_sb = 0;
|
|
|
|
char imsi[MAX_IMSI_LENGTH + 1] = { 0 };
|
|
|
|
DBG("");
|
|
|
|
if (!check_resp(msg, UICC_APPL_CMD_RESP, UICC_APPL_READ_TRANSPARENT))
|
|
goto error;
|
|
|
|
if (!g_isi_msg_data_get_byte(msg, 5, &num_sb))
|
|
goto error;
|
|
|
|
for (g_isi_sb_iter_init_full(&iter, msg, 6, TRUE, num_sb);
|
|
g_isi_sb_iter_is_valid(&iter);
|
|
g_isi_sb_iter_next(&iter)) {
|
|
|
|
int sb_id = g_isi_sb_iter_get_id(&iter);
|
|
|
|
DBG("Subblock %s", uicc_subblock_name(sb_id));
|
|
|
|
if (sb_id != UICC_SB_FILE_DATA)
|
|
continue;
|
|
|
|
if (!g_isi_sb_iter_get_dword(&iter, &filelen, 4))
|
|
goto error;
|
|
|
|
if (!g_isi_sb_iter_get_struct(&iter, (void **) &filedata,
|
|
filelen, 8))
|
|
goto error;
|
|
|
|
DBG("Transparent EF read: 1st byte %02x, len %d",
|
|
filedata[0], filelen);
|
|
|
|
if (!decode_imsi(filedata, filelen, imsi))
|
|
goto error;
|
|
|
|
DBG("IMSI %s", imsi);
|
|
CALLBACK_WITH_SUCCESS(cb, imsi, cbd->data);
|
|
return;
|
|
}
|
|
|
|
error:
|
|
CALLBACK_WITH_FAILURE(cb, NULL, cbd->data);
|
|
}
|
|
|
|
static void uicc_read_imsi(struct ofono_sim *sim, ofono_sim_imsi_cb_t cb,
|
|
void *data)
|
|
{
|
|
struct uicc_sim_data *sd = ofono_sim_get_data(sim);
|
|
struct isi_cb_data *cbd = isi_cb_data_new(sim, cb, data);
|
|
|
|
int mf_path = 0;
|
|
int df1_path = 0;
|
|
int df2_path = 0;
|
|
uint8_t df_len = 0;
|
|
|
|
if (!cbd)
|
|
goto error;
|
|
|
|
DBG("Client %d, AID %d", sd->client_id, sd->app_id);
|
|
|
|
if (!uicc_get_fileid_path(sd, &mf_path, &df1_path, &df2_path, &df_len,
|
|
SIM_EFIMSI_FILEID))
|
|
goto error;
|
|
|
|
if (send_uicc_read_file_transparent(sd->client, sd->app_id, sd->client_id,
|
|
SIM_EFIMSI_FILEID, df_len,
|
|
mf_path, df1_path, df2_path,
|
|
uicc_read_imsi_resp,
|
|
cbd, g_free))
|
|
return;
|
|
|
|
error:
|
|
CALLBACK_WITH_FAILURE(cb, NULL, data);
|
|
g_free(cbd);
|
|
}
|
|
|
|
static void uicc_query_passwd_state_resp(const GIsiMessage *msg, void *opaque)
|
|
{
|
|
struct isi_cb_data *cbd = opaque;
|
|
ofono_sim_passwd_cb_t cb = cbd->cb;
|
|
uint8_t type;
|
|
uint8_t cause;
|
|
|
|
DBG("");
|
|
|
|
if (g_isi_msg_error(msg) < 0) {
|
|
DBG("Error: %s", g_isi_msg_strerror(msg));
|
|
goto error;
|
|
}
|
|
|
|
if (g_isi_msg_id(msg) != UICC_PIN_RESP) {
|
|
DBG("Unexpected msg: %s", sim_message_id_name(g_isi_msg_id(msg)));
|
|
goto error;
|
|
}
|
|
|
|
if (!g_isi_msg_data_get_byte(msg, 0, &type) ||
|
|
type != UICC_PIN_PROMPT_VERIFY) {
|
|
DBG("Unexpected service: 0x%02X (0x%02X)", type,
|
|
UICC_PIN_PROMPT_VERIFY);
|
|
goto error;
|
|
}
|
|
|
|
if (!g_isi_msg_data_get_byte(msg, 1, &cause))
|
|
goto error;
|
|
|
|
DBG("Status: %d %s", cause, uicc_status_name(cause));
|
|
|
|
if (cause == UICC_STATUS_PIN_DISABLED) {
|
|
CALLBACK_WITH_SUCCESS(cb, OFONO_SIM_PASSWORD_NONE, cbd->data);
|
|
return;
|
|
}
|
|
|
|
DBG("Request failed or not implemented: %s", uicc_status_name(cause));
|
|
|
|
error:
|
|
CALLBACK_WITH_FAILURE(cb, -1, cbd->data);
|
|
}
|
|
|
|
static void uicc_query_passwd_state(struct ofono_sim *sim,
|
|
ofono_sim_passwd_cb_t cb, void *data)
|
|
{
|
|
struct uicc_sim_data *sd = ofono_sim_get_data(sim);
|
|
struct isi_cb_data *cbd = isi_cb_data_new(sim, cb, data);
|
|
|
|
const uint8_t req[] = {
|
|
UICC_PIN_REQ,
|
|
UICC_PIN_PROMPT_VERIFY,
|
|
sd->app_id,
|
|
0, 0, 0, /* Filler */
|
|
1, /* Number of subblocks */
|
|
ISI_16BIT(UICC_SB_PIN_REF),
|
|
ISI_16BIT(8), /*Sub block length*/
|
|
sd->pin1_id, /* Pin ID */
|
|
0, 0, 0, /* Filler */
|
|
};
|
|
|
|
DBG("");
|
|
|
|
if (g_isi_client_send(sd->client, req, sizeof(req),
|
|
uicc_query_passwd_state_resp, cbd, g_free))
|
|
return;
|
|
|
|
CALLBACK_WITH_FAILURE(cb, -1, data);
|
|
g_free(cbd);
|
|
}
|
|
|
|
static void uicc_send_passwd(struct ofono_sim *sim, const char *passwd,
|
|
ofono_sim_lock_unlock_cb_t cb, void *data)
|
|
{
|
|
DBG("Not implemented");
|
|
CALLBACK_WITH_FAILURE(cb, data);
|
|
}
|
|
|
|
static void uicc_query_pin_retries_resp(const GIsiMessage *msg, void *opaque)
|
|
{
|
|
struct isi_cb_data *cbd = opaque;
|
|
ofono_sim_pin_retries_cb_t cb = cbd->cb;
|
|
int retries[OFONO_SIM_PASSWORD_INVALID];
|
|
GIsiSubBlockIter iter;
|
|
|
|
uint8_t num_sb = 0;
|
|
uint8_t pins = 0;
|
|
uint8_t pina = 0;
|
|
uint8_t puka = 0;
|
|
|
|
DBG("");
|
|
|
|
if (!check_resp(msg, UICC_PIN_RESP, UICC_PIN_INFO))
|
|
goto error;
|
|
|
|
if (!g_isi_msg_data_get_byte(msg, 5, &num_sb))
|
|
goto error;
|
|
|
|
DBG("Subblock count %d", num_sb);
|
|
|
|
for (g_isi_sb_iter_init_full(&iter, msg, 6, TRUE, num_sb);
|
|
g_isi_sb_iter_is_valid(&iter);
|
|
g_isi_sb_iter_next(&iter)) {
|
|
|
|
uint8_t sb_id = g_isi_sb_iter_get_id(&iter);
|
|
|
|
DBG("Sub-block %s", uicc_subblock_name(sb_id));
|
|
|
|
if (sb_id != UICC_SB_PIN_INFO)
|
|
continue;
|
|
|
|
if (!g_isi_sb_iter_get_byte(&iter, &pins, 4))
|
|
goto error;
|
|
|
|
if (!g_isi_sb_iter_get_byte(&iter, &pina, 5))
|
|
goto error;
|
|
|
|
if (!g_isi_sb_iter_get_byte(&iter, &puka, 6))
|
|
goto error;
|
|
|
|
DBG("PIN status %X PIN Attrib %d PUK attrib %d", pins,
|
|
pina, puka);
|
|
|
|
retries[OFONO_SIM_PASSWORD_SIM_PIN] = pina;
|
|
retries[OFONO_SIM_PASSWORD_SIM_PUK] = puka;
|
|
|
|
CALLBACK_WITH_SUCCESS(cb, retries, cbd->data);
|
|
return;
|
|
}
|
|
|
|
error:
|
|
CALLBACK_WITH_FAILURE(cb, NULL, cbd->data);
|
|
}
|
|
|
|
static void uicc_query_pin_retries(struct ofono_sim *sim,
|
|
ofono_sim_pin_retries_cb_t cb,
|
|
void *data)
|
|
{
|
|
struct uicc_sim_data *sd = ofono_sim_get_data(sim);
|
|
struct isi_cb_data *cbd = isi_cb_data_new(sim, cb, data);
|
|
|
|
const uint8_t req[] = {
|
|
UICC_PIN_REQ,
|
|
UICC_PIN_INFO,
|
|
sd->app_id,
|
|
0, 0, 0, /* Filler */
|
|
1, /* Number of subblocks */
|
|
ISI_16BIT(UICC_SB_PIN_REF),
|
|
ISI_16BIT(8), /* Subblock length */
|
|
sd->pin1_id, /* Pin ID */
|
|
0, 0, 0, /* Filler */
|
|
};
|
|
|
|
DBG("");
|
|
|
|
if (g_isi_client_send(sd->client, req, sizeof(req),
|
|
uicc_query_pin_retries_resp, cbd, g_free))
|
|
return;
|
|
|
|
CALLBACK_WITH_FAILURE(cb, NULL, data);
|
|
g_free(cbd);
|
|
}
|
|
|
|
static void uicc_reset_passwd(struct ofono_sim *sim, const char *puk,
|
|
const char *passwd, ofono_sim_lock_unlock_cb_t cb,
|
|
void *data)
|
|
{
|
|
DBG("Not implemented");
|
|
CALLBACK_WITH_FAILURE(cb, data);
|
|
}
|
|
|
|
static void uicc_change_passwd(struct ofono_sim *sim,
|
|
enum ofono_sim_password_type passwd_type,
|
|
const char *old, const char *new,
|
|
ofono_sim_lock_unlock_cb_t cb, void *data)
|
|
{
|
|
DBG("Not implemented");
|
|
CALLBACK_WITH_FAILURE(cb, data);
|
|
}
|
|
|
|
static void uicc_lock(struct ofono_sim *sim, enum ofono_sim_password_type type,
|
|
int enable, const char *passwd,
|
|
ofono_sim_lock_unlock_cb_t cb, void *data)
|
|
{
|
|
DBG("Not implemented");
|
|
CALLBACK_WITH_FAILURE(cb, data);
|
|
}
|
|
|
|
static gboolean decode_fcp_pin_status(const GIsiSubBlockIter *iter, uint8_t read,
|
|
uint8_t *pin1, uint8_t *pin2)
|
|
{
|
|
uint8_t do_len;
|
|
uint8_t len;
|
|
uint8_t tag;
|
|
uint8_t id;
|
|
uint8_t tag_pos;
|
|
|
|
DBG("Decoding PIN status");
|
|
|
|
if (!g_isi_sb_iter_get_byte(iter, &do_len, read))
|
|
return FALSE;
|
|
|
|
tag_pos = read + 1 + do_len;
|
|
|
|
if (!g_isi_sb_iter_get_byte(iter, &tag, tag_pos))
|
|
return FALSE;
|
|
|
|
while (tag == 0x83) {
|
|
|
|
if (!g_isi_sb_iter_get_byte(iter, &len, tag_pos + 1))
|
|
return FALSE;
|
|
|
|
if (!g_isi_sb_iter_get_byte(iter, &id, tag_pos + 2))
|
|
return FALSE;
|
|
|
|
tag_pos += 2 + len;
|
|
|
|
if (!g_isi_sb_iter_get_byte(iter, &tag, tag_pos))
|
|
return FALSE;
|
|
|
|
DBG("PIN_len %d, PIN id %02x, PIN tag %02x", len, id, tag);
|
|
|
|
if (id >= 0x01 && id <= 0x08)
|
|
*pin1 = id;
|
|
else if (id >= 0x81 && id <= 0x88)
|
|
*pin2 = id;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean decode_fci_sb(const GIsiSubBlockIter *iter, int app_type,
|
|
uint8_t *pin1, uint8_t *pin2)
|
|
{
|
|
uint8_t fcp = 0;
|
|
uint8_t fcp_len = 0;
|
|
uint8_t read = 0;
|
|
uint8_t item_len = 0;
|
|
|
|
DBG("Decoding UICC_SB_FCI");
|
|
|
|
if (app_type != UICC_APPL_TYPE_UICC_USIM)
|
|
return FALSE;
|
|
|
|
if (!g_isi_sb_iter_get_byte(iter, &fcp, 8))
|
|
return FALSE;
|
|
|
|
if (fcp != FCP_TEMPLATE)
|
|
return FALSE;
|
|
|
|
if (!g_isi_sb_iter_get_byte(iter, &fcp_len, 9))
|
|
return FALSE;
|
|
|
|
for (read = 0; read < fcp_len; read += item_len + 2) {
|
|
uint8_t id;
|
|
|
|
if (!g_isi_sb_iter_get_byte(iter, &id, read + 10))
|
|
return FALSE;
|
|
|
|
if (!g_isi_sb_iter_get_byte(iter, &item_len, read + 11))
|
|
return FALSE;
|
|
|
|
if (id != FCP_PIN_STATUS)
|
|
continue;
|
|
|
|
if (!decode_fcp_pin_status(iter, read + 13, pin1, pin2))
|
|
return FALSE;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean decode_chv_sb(const GIsiSubBlockIter *iter, int app_type,
|
|
uint8_t *pin1, uint8_t *pin2)
|
|
{
|
|
uint8_t chv_id = 0;
|
|
uint8_t pin_id = 0;
|
|
|
|
DBG("Decoding UICC_SB_CHV");
|
|
|
|
if (app_type != UICC_APPL_TYPE_ICC_SIM)
|
|
return FALSE;
|
|
|
|
if (!g_isi_sb_iter_get_byte(iter, &chv_id, 4))
|
|
return FALSE;
|
|
|
|
if (!g_isi_sb_iter_get_byte(iter, &pin_id, 5))
|
|
return FALSE;
|
|
|
|
switch (chv_id) {
|
|
case 1:
|
|
*pin1 = pin_id;
|
|
break;
|
|
|
|
case 2:
|
|
*pin2 = pin_id;
|
|
break;
|
|
|
|
default:
|
|
return FALSE;
|
|
}
|
|
|
|
DBG("CHV=%d, pin_id=%2x, PIN1 %02x, PIN2 %02x", chv_id, pin_id, *pin1,
|
|
*pin2);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void uicc_application_activate_resp(const GIsiMessage *msg, void *opaque)
|
|
{
|
|
struct ofono_sim *sim = opaque;
|
|
struct uicc_sim_data *sd = ofono_sim_get_data(sim);
|
|
GIsiSubBlockIter iter;
|
|
uint8_t cause, num_sb;
|
|
|
|
DBG("");
|
|
|
|
if (g_isi_msg_error(msg) < 0) {
|
|
DBG("Error: %s", g_isi_msg_strerror(msg));
|
|
return;
|
|
}
|
|
|
|
if (g_isi_msg_id(msg) != UICC_APPLICATION_RESP) {
|
|
DBG("Unexpected msg: %s",
|
|
sim_message_id_name(g_isi_msg_id(msg)));
|
|
return;
|
|
}
|
|
|
|
if (!g_isi_msg_data_get_byte(msg, 1, &cause))
|
|
return;
|
|
|
|
if (cause != UICC_STATUS_OK && cause != UICC_STATUS_APPL_ACTIVE) {
|
|
DBG("TODO: handle application activation");
|
|
return;
|
|
}
|
|
|
|
if (!sd->uicc_app_started) {
|
|
sd->app_id = sd->trying_app_id;
|
|
sd->app_type = sd->trying_app_type;
|
|
sd->uicc_app_started = TRUE;
|
|
|
|
DBG("UICC application activated");
|
|
|
|
ofono_sim_inserted_notify(sim, TRUE);
|
|
ofono_sim_register(sim);
|
|
|
|
g_hash_table_remove_all(sd->app_table);
|
|
}
|
|
|
|
if (!g_isi_msg_data_get_byte(msg, 5, &num_sb))
|
|
return;
|
|
|
|
for (g_isi_sb_iter_init_full(&iter, msg, 6, TRUE, num_sb);
|
|
g_isi_sb_iter_is_valid(&iter);
|
|
g_isi_sb_iter_next(&iter)) {
|
|
|
|
uint8_t sb_id = g_isi_sb_iter_get_id(&iter);
|
|
|
|
DBG("Subblock %s", uicc_subblock_name(sb_id));
|
|
|
|
switch (sb_id) {
|
|
case UICC_SB_CLIENT:
|
|
|
|
if (!g_isi_sb_iter_get_byte(&iter, &sd->client_id, 7))
|
|
return;
|
|
|
|
DBG("Client id %d", sd->client_id);
|
|
break;
|
|
|
|
case UICC_SB_FCI:
|
|
|
|
if (!decode_fci_sb(&iter, sd->app_type, &sd->pin1_id,
|
|
&sd->pin2_id))
|
|
return;
|
|
|
|
DBG("PIN1 %02x, PIN2 %02x", sd->pin1_id, sd->pin2_id);
|
|
break;
|
|
|
|
case UICC_SB_CHV:
|
|
|
|
if (!decode_chv_sb(&iter, sd->app_type, &sd->pin1_id,
|
|
&sd->pin2_id))
|
|
return;
|
|
|
|
DBG("PIN1 %02x, PIN2 %02x", sd->pin1_id, sd->pin2_id);
|
|
break;
|
|
|
|
default:
|
|
DBG("Skipping sub-block: %s (%zu bytes)",
|
|
uicc_subblock_name(g_isi_sb_iter_get_id(&iter)),
|
|
g_isi_sb_iter_get_len(&iter));
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
static gboolean send_application_activate_req(GIsiClient *client,
|
|
uint8_t app_type,
|
|
uint8_t app_id,
|
|
GIsiNotifyFunc notify,
|
|
void *data,
|
|
GDestroyNotify destroy)
|
|
{
|
|
const uint8_t msg[] = {
|
|
UICC_APPLICATION_REQ,
|
|
UICC_APPL_HOST_ACTIVATE,
|
|
2, /* Number of subblocks */
|
|
ISI_16BIT(UICC_SB_APPLICATION),
|
|
ISI_16BIT(8), /* Subblock length */
|
|
0, 0, /* Filler */
|
|
app_type,
|
|
app_id,
|
|
ISI_16BIT(UICC_SB_APPL_INFO),
|
|
ISI_16BIT(8), /* Subblock length */
|
|
0, 0, 0, /* Filler */
|
|
/*
|
|
* Next field indicates whether the application
|
|
* initialization procedure will follow the activation
|
|
* or not
|
|
*/
|
|
UICC_APPL_START_UP_INIT_PROC,
|
|
};
|
|
|
|
DBG("App type %d, AID %d", app_type, app_id);
|
|
|
|
return g_isi_client_send(client, msg, sizeof(msg), notify, data, destroy);
|
|
}
|
|
|
|
static void uicc_application_list_resp(const GIsiMessage *msg, void *data)
|
|
{
|
|
struct ofono_sim *sim = data;
|
|
struct uicc_sim_data *sd = ofono_sim_get_data(sim);
|
|
GIsiSubBlockIter iter;
|
|
uint8_t num_sb;
|
|
struct uicc_sim_application *sim_app;
|
|
|
|
/* Throw away old app table */
|
|
g_hash_table_remove_all(sd->app_table);
|
|
|
|
if (!check_resp(msg, UICC_APPLICATION_RESP, UICC_APPL_LIST))
|
|
goto error;
|
|
|
|
if (!g_isi_msg_data_get_byte(msg, 5, &num_sb))
|
|
goto error;
|
|
|
|
/* Iterate through the application list */
|
|
for (g_isi_sb_iter_init_full(&iter, msg, 6, TRUE, num_sb);
|
|
g_isi_sb_iter_is_valid(&iter);
|
|
g_isi_sb_iter_next(&iter)) {
|
|
uint8_t app_type;
|
|
uint8_t app_id;
|
|
uint8_t app_status;
|
|
uint8_t app_len;
|
|
|
|
if (g_isi_sb_iter_get_id(&iter) != UICC_SB_APPL_DATA_OBJECT)
|
|
continue;
|
|
|
|
if (!g_isi_sb_iter_get_byte(&iter, &app_type, 6))
|
|
goto error;
|
|
|
|
if (!g_isi_sb_iter_get_byte(&iter, &app_id, 7))
|
|
goto error;
|
|
|
|
if (!g_isi_sb_iter_get_byte(&iter, &app_status, 8))
|
|
goto error;
|
|
|
|
if (!g_isi_sb_iter_get_byte(&iter, &app_len, 9))
|
|
goto error;
|
|
|
|
if (app_type != UICC_APPL_TYPE_ICC_SIM &&
|
|
app_type != UICC_APPL_TYPE_UICC_USIM)
|
|
continue;
|
|
|
|
sim_app = g_try_new0(struct uicc_sim_application, 1);
|
|
if (!sim_app) {
|
|
DBG("out of memory!");
|
|
goto error;
|
|
}
|
|
|
|
sim_app->type = app_type;
|
|
sim_app->id = app_id;
|
|
sim_app->status = app_status;
|
|
sim_app->length = app_len;
|
|
sim_app->sim = sd;
|
|
|
|
g_hash_table_replace(sd->app_table, &sim_app->id, sim_app);
|
|
}
|
|
|
|
if (!sd->uicc_app_started) {
|
|
GHashTableIter app_iter;
|
|
struct uicc_sim_application *app;
|
|
|
|
gpointer key;
|
|
gpointer value;
|
|
|
|
g_hash_table_iter_init(&app_iter, sd->app_table);
|
|
|
|
if (!g_hash_table_iter_next(&app_iter, &key, &value))
|
|
return;
|
|
|
|
app = value;
|
|
sd->trying_app_type = app->type;
|
|
sd->trying_app_id = app->id;
|
|
|
|
g_hash_table_remove(sd->app_table, &app->id);
|
|
|
|
if (!send_application_activate_req(sd->client, app->type, app->id,
|
|
uicc_application_activate_resp,
|
|
data, NULL)) {
|
|
DBG("Failed to activate: 0x%02X (type=0x%02X)",
|
|
app->id, app->type);
|
|
return;
|
|
}
|
|
}
|
|
return;
|
|
|
|
error:
|
|
DBG("Decoding application list failed");
|
|
|
|
g_isi_client_destroy(sd->client);
|
|
sd->client = NULL;
|
|
|
|
ofono_sim_remove(sim);
|
|
}
|
|
|
|
static void uicc_card_status_resp(const GIsiMessage *msg, void *data)
|
|
{
|
|
struct ofono_sim *sim = data;
|
|
struct uicc_sim_data *sd = ofono_sim_get_data(sim);
|
|
GIsiSubBlockIter iter;
|
|
uint8_t card_status = 0;
|
|
uint8_t num_sb = 0;
|
|
|
|
DBG("");
|
|
|
|
if (!sd->server_running)
|
|
return;
|
|
|
|
if (!check_resp(msg, UICC_CARD_RESP, UICC_CARD_STATUS_GET))
|
|
goto error;
|
|
|
|
if (!g_isi_msg_data_get_byte(msg, 1, &card_status))
|
|
goto error;
|
|
|
|
if (!g_isi_msg_data_get_byte(msg, 5, &num_sb))
|
|
goto error;
|
|
|
|
DBG("Subblock count %d", num_sb);
|
|
|
|
for (g_isi_sb_iter_init_full(&iter, msg, 6, TRUE, num_sb);
|
|
g_isi_sb_iter_is_valid(&iter);
|
|
g_isi_sb_iter_next(&iter)) {
|
|
|
|
if (g_isi_sb_iter_get_id(&iter) != UICC_SB_CARD_STATUS)
|
|
continue;
|
|
|
|
if (!g_isi_sb_iter_get_byte(&iter, &card_status, 7))
|
|
goto error;
|
|
|
|
DBG("card_status = 0x%X", card_status);
|
|
|
|
/* Check if card is ready */
|
|
if (card_status == 0x21) {
|
|
const uint8_t req[] = {
|
|
UICC_APPLICATION_REQ,
|
|
UICC_APPL_LIST,
|
|
0, /* Number of subblocks */
|
|
};
|
|
|
|
DBG("card is ready");
|
|
ofono_sim_inserted_notify(sim, TRUE);
|
|
|
|
if (g_isi_client_send(sd->client, req, sizeof(req),
|
|
uicc_application_list_resp,
|
|
data, NULL))
|
|
return;
|
|
|
|
DBG("Failed to query application list");
|
|
goto error;
|
|
|
|
} else {
|
|
DBG("card not ready");
|
|
ofono_sim_inserted_notify(sim, FALSE);
|
|
return;
|
|
}
|
|
}
|
|
|
|
error:
|
|
g_isi_client_destroy(sd->client);
|
|
sd->client = NULL;
|
|
|
|
ofono_sim_remove(sim);
|
|
}
|
|
|
|
static void uicc_card_status_req(struct ofono_sim *sim,
|
|
struct uicc_sim_data *sd)
|
|
{
|
|
const uint8_t req[] = {
|
|
UICC_CARD_REQ,
|
|
UICC_CARD_STATUS_GET,
|
|
0,
|
|
};
|
|
|
|
DBG("");
|
|
|
|
if (g_isi_client_send(sd->client, req, sizeof(req),
|
|
uicc_card_status_resp, sim, NULL))
|
|
return;
|
|
|
|
g_isi_client_destroy(sd->client);
|
|
sd->client = NULL;
|
|
|
|
ofono_sim_remove(sim);
|
|
}
|
|
|
|
static void uicc_card_ind_cb(const GIsiMessage *msg, void *data)
|
|
{
|
|
struct ofono_sim *sim = data;
|
|
struct uicc_sim_data *sd = ofono_sim_get_data(sim);
|
|
|
|
DBG("");
|
|
|
|
if (g_isi_msg_id(msg) != UICC_CARD_IND)
|
|
return;
|
|
|
|
/* We're not interested in card indications if server isn't running */
|
|
if (!sd->server_running)
|
|
return;
|
|
|
|
/* Request card status */
|
|
uicc_card_status_req(sim, sd);
|
|
}
|
|
|
|
static void uicc_status_resp(const GIsiMessage *msg, void *data)
|
|
{
|
|
struct ofono_sim *sim = data;
|
|
struct uicc_sim_data *sd = ofono_sim_get_data(sim);
|
|
uint8_t status = 0, server_status = 0;
|
|
gboolean server_running = FALSE;
|
|
|
|
if (!check_resp(msg, UICC_RESP, UICC_STATUS_GET))
|
|
goto error;
|
|
|
|
if (g_isi_msg_error(msg) < 0)
|
|
goto error;
|
|
|
|
if (!g_isi_msg_data_get_byte(msg, 1, &status) ||
|
|
!g_isi_msg_data_get_byte(msg, 3, &server_status))
|
|
goto error;
|
|
|
|
DBG("status=0x%X, server_status=0x%X", status, server_status);
|
|
|
|
if (status == UICC_STATUS_OK &&
|
|
server_status == UICC_STATUS_START_UP_COMPLETED) {
|
|
DBG("server is up!");
|
|
server_running = TRUE;
|
|
}
|
|
|
|
|
|
if (!server_running) {
|
|
sd->server_running = FALSE;
|
|
|
|
/* TODO: Remove SIM etc... */
|
|
return;
|
|
}
|
|
|
|
if (sd->server_running && server_running) {
|
|
DBG("Server status didn't change...");
|
|
return;
|
|
}
|
|
|
|
/* Server is running */
|
|
sd->server_running = TRUE;
|
|
|
|
/* Request card status */
|
|
uicc_card_status_req(sim, sd);
|
|
return;
|
|
|
|
error:
|
|
g_isi_client_destroy(sd->client);
|
|
sd->client = NULL;
|
|
|
|
ofono_sim_remove(sim);
|
|
}
|
|
|
|
static void uicc_ind_cb(const GIsiMessage *msg, void *data)
|
|
{
|
|
struct ofono_sim *sim = data;
|
|
struct uicc_sim_data *sd = ofono_sim_get_data(sim);
|
|
const uint8_t req[] = { UICC_REQ, UICC_STATUS_GET, 0 };
|
|
|
|
int msg_id = g_isi_msg_id(msg);
|
|
DBG("%s", uicc_message_id_name(msg_id));
|
|
|
|
if (msg_id != UICC_IND)
|
|
return;
|
|
|
|
/* Request status */
|
|
if (g_isi_client_send(sd->client, req, sizeof(req), uicc_status_resp,
|
|
data, NULL))
|
|
return;
|
|
|
|
DBG("status request failed!");
|
|
|
|
g_isi_client_destroy(sd->client);
|
|
sd->client = NULL;
|
|
ofono_sim_remove(sim);
|
|
}
|
|
|
|
static void uicc_reachable_cb(const GIsiMessage *msg, void *data)
|
|
{
|
|
struct ofono_sim *sim = data;
|
|
struct uicc_sim_data *sd = ofono_sim_get_data(sim);
|
|
|
|
const uint8_t req[] = {
|
|
UICC_REQ,
|
|
UICC_STATUS_GET,
|
|
0, /* Number of Sub Blocks (only from version 4.0) */
|
|
};
|
|
|
|
ISI_RESOURCE_DBG(msg);
|
|
|
|
if (g_isi_msg_error(msg) < 0)
|
|
goto error;
|
|
|
|
sd->version.major = g_isi_msg_version_major(msg);
|
|
sd->version.minor = g_isi_msg_version_minor(msg);
|
|
|
|
/* UICC server is reachable: request indications */
|
|
g_isi_client_ind_subscribe(sd->client, UICC_IND, uicc_ind_cb, sim);
|
|
g_isi_client_ind_subscribe(sd->client, UICC_CARD_IND, uicc_card_ind_cb,
|
|
sim);
|
|
|
|
/* Update status */
|
|
if (g_isi_client_send(sd->client, req,
|
|
sizeof(req) - ((sd->version.major < 4) ? 1 : 0),
|
|
uicc_status_resp, data, NULL))
|
|
return;
|
|
|
|
error:
|
|
g_isi_client_destroy(sd->client);
|
|
sd->client = NULL;
|
|
|
|
ofono_sim_remove(sim);
|
|
}
|
|
|
|
static void sim_app_destroy(gpointer p)
|
|
{
|
|
struct uicc_sim_application *app = p;
|
|
if (!app)
|
|
return;
|
|
|
|
g_free(app);
|
|
}
|
|
|
|
static int uicc_sim_probe(struct ofono_sim *sim, unsigned int vendor,
|
|
void *user)
|
|
{
|
|
GIsiModem *modem = user;
|
|
struct uicc_sim_data *sd;
|
|
|
|
sd = g_try_new0(struct uicc_sim_data, 1);
|
|
if (sd == NULL)
|
|
return -ENOMEM;
|
|
|
|
/* Create hash table for the UICC applications */
|
|
sd->app_table = g_hash_table_new_full(g_int_hash, g_int_equal, NULL,
|
|
sim_app_destroy);
|
|
if (sd->app_table == NULL) {
|
|
g_free(sd);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
sd->client = g_isi_client_create(modem, PN_UICC);
|
|
if (sd->client == NULL) {
|
|
g_hash_table_destroy(sd->app_table);
|
|
g_free(sd);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
g_hash_table_insert(g_modems, g_isi_client_modem(sd->client), sim);
|
|
|
|
sd->server_running = FALSE;
|
|
sd->uicc_app_started = FALSE;
|
|
sd->pin_state_received = FALSE;
|
|
sd->passwd_required = TRUE;
|
|
ofono_sim_set_data(sim, sd);
|
|
|
|
g_isi_client_verify(sd->client, uicc_reachable_cb, sim, NULL);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void uicc_sim_remove(struct ofono_sim *sim)
|
|
{
|
|
struct uicc_sim_data *data = ofono_sim_get_data(sim);
|
|
|
|
ofono_sim_set_data(sim, NULL);
|
|
|
|
if (data == NULL)
|
|
return;
|
|
|
|
g_hash_table_remove(g_modems, g_isi_client_modem(data->client));
|
|
|
|
g_hash_table_destroy(data->app_table);
|
|
g_isi_client_destroy(data->client);
|
|
g_free(data);
|
|
}
|
|
|
|
static const struct ofono_sim_driver driver = {
|
|
.name = "wgmodem2.5",
|
|
.probe = uicc_sim_probe,
|
|
.remove = uicc_sim_remove,
|
|
.read_file_info = uicc_read_file_info,
|
|
.read_file_transparent = uicc_read_file_transparent,
|
|
.read_file_linear = uicc_read_file_linear,
|
|
.read_file_cyclic = uicc_read_file_cyclic,
|
|
.write_file_transparent = uicc_write_file_transparent,
|
|
.write_file_linear = uicc_write_file_linear,
|
|
.write_file_cyclic = uicc_write_file_cyclic,
|
|
.read_imsi = uicc_read_imsi,
|
|
.query_passwd_state = uicc_query_passwd_state,
|
|
.send_passwd = uicc_send_passwd,
|
|
.query_pin_retries = uicc_query_pin_retries,
|
|
.reset_passwd = uicc_reset_passwd,
|
|
.change_passwd = uicc_change_passwd,
|
|
.lock = uicc_lock,
|
|
};
|
|
|
|
void isi_uicc_init(void)
|
|
{
|
|
g_modems = g_hash_table_new(g_direct_hash, g_direct_equal);
|
|
ofono_sim_driver_register(&driver);
|
|
}
|
|
|
|
void isi_uicc_exit(void)
|
|
{
|
|
g_hash_table_destroy(g_modems);
|
|
ofono_sim_driver_unregister(&driver);
|
|
}
|
|
|
|
gboolean isi_uicc_properties(GIsiModem *modem, int *app_id, int *app_type,
|
|
int *client_id)
|
|
{
|
|
struct ofono_sim *sim;
|
|
struct uicc_sim_data *sd;
|
|
|
|
sim = g_hash_table_lookup(g_modems, modem);
|
|
if (sim == NULL)
|
|
return FALSE;
|
|
|
|
sd = ofono_sim_get_data(sim);
|
|
if (sd == NULL)
|
|
return FALSE;
|
|
|
|
if (app_id != NULL)
|
|
*app_id = sd->app_id;
|
|
|
|
if (app_type != NULL)
|
|
*app_type = sd->app_type;
|
|
|
|
if (client_id != NULL)
|
|
*client_id = sd->client_id;
|
|
|
|
return TRUE;
|
|
}
|