We need to define a state to be used while the modem is being initialized, so that we forbid any operation on the modem on already exported interfaces, while there are interfaces pending to get exported. This Initializing state will also cover the state between having the SIM unlocked (which launches re-initialization) and being completely initialized.
1804 lines
58 KiB
C
1804 lines
58 KiB
C
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
|
/*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* 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:
|
|
*
|
|
* Copyright (C) 2008 - 2009 Novell, Inc.
|
|
* Copyright (C) 2009 - 2011 Red Hat, Inc.
|
|
* Copyright (C) 2011 Google, Inc.
|
|
*/
|
|
|
|
#include <config.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <unistd.h>
|
|
#include <string.h>
|
|
#include <ctype.h>
|
|
|
|
#include <ModemManager.h>
|
|
#include <libmm-common.h>
|
|
|
|
#include "mm-iface-modem.h"
|
|
#include "mm-sim.h"
|
|
#include "mm-base-modem-at.h"
|
|
#include "mm-base-modem.h"
|
|
#include "mm-utils.h"
|
|
#include "mm-log.h"
|
|
#include "mm-modem-helpers.h"
|
|
#include "mm-marshal.h"
|
|
|
|
static void async_initable_iface_init (GAsyncInitableIface *iface);
|
|
|
|
G_DEFINE_TYPE_EXTENDED (MMSim, mm_sim, MM_GDBUS_TYPE_SIM_SKELETON, 0,
|
|
G_IMPLEMENT_INTERFACE (G_TYPE_ASYNC_INITABLE,
|
|
async_initable_iface_init));
|
|
|
|
enum {
|
|
PROP_0,
|
|
PROP_PATH,
|
|
PROP_CONNECTION,
|
|
PROP_MODEM,
|
|
PROP_LAST
|
|
};
|
|
|
|
enum {
|
|
SIGNAL_PIN_LOCK_ENABLED,
|
|
SIGNAL_LAST
|
|
};
|
|
|
|
static GParamSpec *properties[PROP_LAST];
|
|
|
|
struct _MMSimPrivate {
|
|
/* The connection to the system bus */
|
|
GDBusConnection *connection;
|
|
/* The modem which owns this SIM */
|
|
MMBaseModem *modem;
|
|
/* The path where the SIM object is exported */
|
|
gchar *path;
|
|
};
|
|
|
|
static guint signals[SIGNAL_LAST] = { 0 };
|
|
|
|
/*****************************************************************************/
|
|
|
|
void
|
|
mm_sim_export (MMSim *self)
|
|
{
|
|
static guint id = 0;
|
|
gchar *path;
|
|
|
|
path = g_strdup_printf (MM_DBUS_SIM_PREFIX "/%d", id++);
|
|
g_object_set (self,
|
|
MM_SIM_PATH, path,
|
|
NULL);
|
|
g_free (path);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* CHANGE PIN (Generic implementation) */
|
|
|
|
static gboolean
|
|
change_pin_finish (MMSim *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
return !g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error);
|
|
}
|
|
|
|
static void
|
|
change_pin_ready (MMBaseModem *modem,
|
|
GAsyncResult *res,
|
|
GSimpleAsyncResult *simple)
|
|
{
|
|
GError *error = NULL;
|
|
|
|
mm_base_modem_at_command_finish (modem, res, &error);
|
|
if (error)
|
|
g_simple_async_result_take_error (simple, error);
|
|
else
|
|
g_simple_async_result_set_op_res_gboolean (simple, TRUE);
|
|
g_simple_async_result_complete (simple);
|
|
g_object_unref (simple);
|
|
}
|
|
|
|
static void
|
|
change_pin (MMSim *self,
|
|
const gchar *old_pin,
|
|
const gchar *new_pin,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
GSimpleAsyncResult *result;
|
|
gchar *command;
|
|
|
|
result = g_simple_async_result_new (G_OBJECT (self),
|
|
callback,
|
|
user_data,
|
|
change_pin);
|
|
|
|
command = g_strdup_printf ("+CPWD=\"SC\",\"%s\",\"%s\"",
|
|
old_pin,
|
|
new_pin);
|
|
mm_base_modem_at_command (MM_BASE_MODEM (self->priv->modem),
|
|
command,
|
|
3,
|
|
FALSE,
|
|
(GAsyncReadyCallback)change_pin_ready,
|
|
result);
|
|
g_free (command);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* CHANGE PIN (DBus call handling) */
|
|
|
|
typedef struct {
|
|
MMSim *self;
|
|
GDBusMethodInvocation *invocation;
|
|
gchar *old_pin;
|
|
gchar *new_pin;
|
|
} HandleChangePinContext;
|
|
|
|
static void
|
|
handle_change_pin_context_free (HandleChangePinContext *ctx)
|
|
{
|
|
g_object_unref (ctx->invocation);
|
|
g_object_unref (ctx->self);
|
|
g_free (ctx->old_pin);
|
|
g_free (ctx->new_pin);
|
|
g_free (ctx);
|
|
}
|
|
|
|
static void
|
|
after_change_unlock_retries_ready (MMIfaceModem *self,
|
|
GAsyncResult *res,
|
|
HandleChangePinContext *ctx)
|
|
{
|
|
/* We just want to ensure that we tried to update the unlock
|
|
* retries, no big issue if it failed */
|
|
mm_iface_modem_update_unlock_retries_finish (self, res, NULL);
|
|
|
|
mm_gdbus_sim_complete_change_pin (MM_GDBUS_SIM (ctx->self), ctx->invocation);
|
|
handle_change_pin_context_free (ctx);
|
|
}
|
|
|
|
static void
|
|
handle_change_pin_ready (MMSim *self,
|
|
GAsyncResult *res,
|
|
HandleChangePinContext *ctx)
|
|
{
|
|
GError *error = NULL;
|
|
|
|
MM_SIM_GET_CLASS (self)->change_pin_finish (self, res, &error);
|
|
if (error) {
|
|
g_dbus_method_invocation_take_error (ctx->invocation, error);
|
|
handle_change_pin_context_free (ctx);
|
|
return;
|
|
}
|
|
|
|
/* Before returning success, ensure that we get the unlock retry
|
|
* counts updated properly. */
|
|
mm_iface_modem_update_unlock_retries (MM_IFACE_MODEM (self->priv->modem),
|
|
(GAsyncReadyCallback)after_change_unlock_retries_ready,
|
|
ctx);
|
|
}
|
|
|
|
static void
|
|
handle_change_pin_auth_ready (MMBaseModem *modem,
|
|
GAsyncResult *res,
|
|
HandleChangePinContext *ctx)
|
|
{
|
|
GError *error = NULL;
|
|
|
|
if (!mm_base_modem_authorize_finish (modem, res, &error)) {
|
|
g_dbus_method_invocation_take_error (ctx->invocation, error);
|
|
handle_change_pin_context_free (ctx);
|
|
return;
|
|
}
|
|
|
|
/* If changing PIN is not implemented, report an error */
|
|
if (!MM_SIM_GET_CLASS (ctx->self)->change_pin ||
|
|
!MM_SIM_GET_CLASS (ctx->self)->change_pin_finish) {
|
|
g_dbus_method_invocation_return_error (ctx->invocation,
|
|
MM_CORE_ERROR,
|
|
MM_CORE_ERROR_UNSUPPORTED,
|
|
"Cannot change PIN: "
|
|
"operation not supported");
|
|
handle_change_pin_context_free (ctx);
|
|
return;
|
|
}
|
|
|
|
MM_SIM_GET_CLASS (ctx->self)->change_pin (ctx->self,
|
|
ctx->old_pin,
|
|
ctx->new_pin,
|
|
(GAsyncReadyCallback)handle_change_pin_ready,
|
|
ctx);
|
|
}
|
|
|
|
static gboolean
|
|
handle_change_pin (MMSim *self,
|
|
GDBusMethodInvocation *invocation,
|
|
const gchar *old_pin,
|
|
const gchar *new_pin,
|
|
gboolean changed)
|
|
{
|
|
HandleChangePinContext *ctx;
|
|
|
|
ctx = g_new0 (HandleChangePinContext, 1);
|
|
ctx->self = g_object_ref (self);
|
|
ctx->invocation = g_object_ref (invocation);
|
|
ctx->old_pin = g_strdup (old_pin);
|
|
ctx->new_pin = g_strdup (new_pin);
|
|
|
|
mm_base_modem_authorize (self->priv->modem,
|
|
invocation,
|
|
MM_AUTHORIZATION_DEVICE_CONTROL,
|
|
(GAsyncReadyCallback)handle_change_pin_auth_ready,
|
|
ctx);
|
|
return TRUE;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* ENABLE PIN (Generic implementation) */
|
|
|
|
static gboolean
|
|
enable_pin_finish (MMSim *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
return !g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error);
|
|
}
|
|
|
|
static void
|
|
enable_pin_ready (MMBaseModem *modem,
|
|
GAsyncResult *res,
|
|
GSimpleAsyncResult *simple)
|
|
{
|
|
GError *error = NULL;
|
|
|
|
mm_base_modem_at_command_finish (modem, res, &error);
|
|
if (error)
|
|
g_simple_async_result_take_error (simple, error);
|
|
else
|
|
g_simple_async_result_set_op_res_gboolean (simple, TRUE);
|
|
g_simple_async_result_complete (simple);
|
|
g_object_unref (simple);
|
|
}
|
|
|
|
static void
|
|
enable_pin (MMSim *self,
|
|
const gchar *pin,
|
|
gboolean enabled,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
GSimpleAsyncResult *result;
|
|
gchar *command;
|
|
|
|
result = g_simple_async_result_new (G_OBJECT (self),
|
|
callback,
|
|
user_data,
|
|
enable_pin);
|
|
|
|
command = g_strdup_printf ("+CLCK=\"SC\",%d,\"%s\"",
|
|
enabled ? 1 : 0,
|
|
pin);
|
|
mm_base_modem_at_command (MM_BASE_MODEM (self->priv->modem),
|
|
command,
|
|
3,
|
|
FALSE,
|
|
(GAsyncReadyCallback)enable_pin_ready,
|
|
result);
|
|
g_free (command);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* ENABLE PIN (DBus call handling) */
|
|
|
|
typedef struct {
|
|
MMSim *self;
|
|
GDBusMethodInvocation *invocation;
|
|
gchar *pin;
|
|
gboolean enabled;
|
|
} HandleEnablePinContext;
|
|
|
|
static void
|
|
handle_enable_pin_context_free (HandleEnablePinContext *ctx)
|
|
{
|
|
g_object_unref (ctx->invocation);
|
|
g_object_unref (ctx->self);
|
|
g_free (ctx->pin);
|
|
g_free (ctx);
|
|
}
|
|
|
|
static void
|
|
after_enable_unlock_retries_ready (MMIfaceModem *self,
|
|
GAsyncResult *res,
|
|
HandleEnablePinContext *ctx)
|
|
{
|
|
/* We just want to ensure that we tried to update the unlock
|
|
* retries, no big issue if it failed */
|
|
mm_iface_modem_update_unlock_retries_finish (self, res, NULL);
|
|
|
|
mm_gdbus_sim_complete_enable_pin (MM_GDBUS_SIM (ctx->self), ctx->invocation);
|
|
handle_enable_pin_context_free (ctx);
|
|
}
|
|
|
|
static void
|
|
handle_enable_pin_ready (MMSim *self,
|
|
GAsyncResult *res,
|
|
HandleEnablePinContext *ctx)
|
|
{
|
|
GError *error = NULL;
|
|
|
|
MM_SIM_GET_CLASS (self)->enable_pin_finish (self, res, &error);
|
|
if (error) {
|
|
g_dbus_method_invocation_take_error (ctx->invocation, error);
|
|
handle_enable_pin_context_free (ctx);
|
|
return;
|
|
}
|
|
|
|
/* Signal about the new lock state */
|
|
g_signal_emit (self, signals[SIGNAL_PIN_LOCK_ENABLED], 0, ctx->enabled);
|
|
|
|
/* Before returning success, ensure that we get the unlock retry
|
|
* counts updated properly. */
|
|
mm_iface_modem_update_unlock_retries (MM_IFACE_MODEM (self->priv->modem),
|
|
(GAsyncReadyCallback)after_enable_unlock_retries_ready,
|
|
ctx);
|
|
}
|
|
|
|
static void
|
|
handle_enable_pin_auth_ready (MMBaseModem *modem,
|
|
GAsyncResult *res,
|
|
HandleEnablePinContext *ctx)
|
|
{
|
|
GError *error = NULL;
|
|
|
|
if (!mm_base_modem_authorize_finish (modem, res, &error)) {
|
|
g_dbus_method_invocation_take_error (ctx->invocation, error);
|
|
handle_enable_pin_context_free (ctx);
|
|
return;
|
|
}
|
|
|
|
/* If changing PIN is not implemented, report an error */
|
|
if (!MM_SIM_GET_CLASS (ctx->self)->enable_pin ||
|
|
!MM_SIM_GET_CLASS (ctx->self)->enable_pin_finish) {
|
|
g_dbus_method_invocation_return_error (ctx->invocation,
|
|
MM_CORE_ERROR,
|
|
MM_CORE_ERROR_UNSUPPORTED,
|
|
"Cannot enable/disable PIN: "
|
|
"operation not supported");
|
|
handle_enable_pin_context_free (ctx);
|
|
return;
|
|
}
|
|
|
|
MM_SIM_GET_CLASS (ctx->self)->enable_pin (ctx->self,
|
|
ctx->pin,
|
|
ctx->enabled,
|
|
(GAsyncReadyCallback)handle_enable_pin_ready,
|
|
ctx);
|
|
}
|
|
|
|
static gboolean
|
|
handle_enable_pin (MMSim *self,
|
|
GDBusMethodInvocation *invocation,
|
|
const gchar *pin,
|
|
gboolean enabled)
|
|
{
|
|
HandleEnablePinContext *ctx;
|
|
|
|
ctx = g_new0 (HandleEnablePinContext, 1);
|
|
ctx->self = g_object_ref (self);
|
|
ctx->invocation = g_object_ref (invocation);
|
|
ctx->pin = g_strdup (pin);
|
|
ctx->enabled = enabled;
|
|
|
|
mm_base_modem_authorize (self->priv->modem,
|
|
invocation,
|
|
MM_AUTHORIZATION_DEVICE_CONTROL,
|
|
(GAsyncReadyCallback)handle_enable_pin_auth_ready,
|
|
ctx);
|
|
return TRUE;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* SEND PIN/PUK (Generic implementation) */
|
|
|
|
static gboolean
|
|
common_send_pin_puk_finish (MMSim *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
return !g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error);
|
|
}
|
|
|
|
static void
|
|
send_pin_puk_ready (MMBaseModem *modem,
|
|
GAsyncResult *res,
|
|
GSimpleAsyncResult *simple)
|
|
{
|
|
GError *error = NULL;
|
|
|
|
mm_base_modem_at_command_finish (modem, res, &error);
|
|
if (error)
|
|
g_simple_async_result_take_error (simple, error);
|
|
else
|
|
g_simple_async_result_set_op_res_gboolean (simple, TRUE);
|
|
g_simple_async_result_complete (simple);
|
|
g_object_unref (simple);
|
|
}
|
|
|
|
static void
|
|
common_send_pin_puk (MMSim *self,
|
|
const gchar *pin,
|
|
const gchar *puk,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
GSimpleAsyncResult *result;
|
|
gchar *command;
|
|
|
|
result = g_simple_async_result_new (G_OBJECT (self),
|
|
callback,
|
|
user_data,
|
|
common_send_pin_puk);
|
|
|
|
command = (puk ?
|
|
g_strdup_printf ("+CPIN=\"%s\",\"%s\"", puk, pin) :
|
|
g_strdup_printf ("+CPIN=\"%s\"", pin));
|
|
|
|
mm_base_modem_at_command (MM_BASE_MODEM (self->priv->modem),
|
|
command,
|
|
3,
|
|
FALSE,
|
|
(GAsyncReadyCallback)send_pin_puk_ready,
|
|
result);
|
|
g_free (command);
|
|
}
|
|
|
|
static void
|
|
send_puk (MMSim *self,
|
|
const gchar *puk,
|
|
const gchar *new_pin,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
common_send_pin_puk (self, new_pin, puk, callback, user_data);
|
|
}
|
|
|
|
static void
|
|
send_pin (MMSim *self,
|
|
const gchar *pin,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
common_send_pin_puk (self, pin, NULL, callback, user_data);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* SEND PIN/PUK (common logic) */
|
|
|
|
typedef struct {
|
|
MMSim *self;
|
|
GSimpleAsyncResult *result;
|
|
GError *save_error;
|
|
gulong wait_for_unlock_id;
|
|
} SendPinPukContext;
|
|
|
|
static void
|
|
send_pin_puk_context_complete_and_free (SendPinPukContext *ctx)
|
|
{
|
|
if (ctx->wait_for_unlock_id)
|
|
g_signal_handler_disconnect (ctx->self->priv->modem,
|
|
ctx->wait_for_unlock_id);
|
|
if (ctx->save_error)
|
|
g_error_free (ctx->save_error);
|
|
g_simple_async_result_complete (ctx->result);
|
|
g_object_unref (ctx->result);
|
|
g_object_unref (ctx->self);
|
|
}
|
|
|
|
static GError *
|
|
error_for_unlock_check (MMModemLock lock)
|
|
{
|
|
static const MMMobileEquipmentError errors_for_locks [] = {
|
|
MM_MOBILE_EQUIPMENT_ERROR_UNKNOWN, /* MM_MODEM_LOCK_UNKNOWN */
|
|
MM_MOBILE_EQUIPMENT_ERROR_UNKNOWN, /* MM_MODEM_LOCK_NONE */
|
|
MM_MOBILE_EQUIPMENT_ERROR_SIM_PIN, /* MM_MODEM_LOCK_SIM_PIN */
|
|
MM_MOBILE_EQUIPMENT_ERROR_SIM_PIN2, /* MM_MODEM_LOCK_SIM_PIN2 */
|
|
MM_MOBILE_EQUIPMENT_ERROR_SIM_PUK, /* MM_MODEM_LOCK_SIM_PUK */
|
|
MM_MOBILE_EQUIPMENT_ERROR_SIM_PUK2, /* MM_MODEM_LOCK_SIM_PUK2 */
|
|
MM_MOBILE_EQUIPMENT_ERROR_SERVICE_PIN, /* MM_MODEM_LOCK_PH_SP_PIN */
|
|
MM_MOBILE_EQUIPMENT_ERROR_SERVICE_PUK, /* MM_MODEM_LOCK_PH_SP_PUK */
|
|
MM_MOBILE_EQUIPMENT_ERROR_NETWORK_PIN, /* MM_MODEM_LOCK_PH_NET_PIN */
|
|
MM_MOBILE_EQUIPMENT_ERROR_NETWORK_PUK, /* MM_MODEM_LOCK_PH_NET_PUK */
|
|
MM_MOBILE_EQUIPMENT_ERROR_PH_SIM_PIN, /* MM_MODEM_LOCK_PH_SIM_PIN */
|
|
MM_MOBILE_EQUIPMENT_ERROR_CORP_PIN, /* MM_MODEM_LOCK_PH_CORP_PIN */
|
|
MM_MOBILE_EQUIPMENT_ERROR_CORP_PUK, /* MM_MODEM_LOCK_PH_CORP_PUK */
|
|
MM_MOBILE_EQUIPMENT_ERROR_PH_FSIM_PIN, /* MM_MODEM_LOCK_PH_FSIM_PIN */
|
|
MM_MOBILE_EQUIPMENT_ERROR_PH_FSIM_PUK, /* MM_MODEM_LOCK_PH_FSIM_PUK */
|
|
MM_MOBILE_EQUIPMENT_ERROR_NETWORK_SUBSET_PIN, /* MM_MODEM_LOCK_PH_NETSUB_PIN */
|
|
MM_MOBILE_EQUIPMENT_ERROR_NETWORK_SUBSET_PUK, /* MM_MODEM_LOCK_PH_NETSUB_PUK */
|
|
};
|
|
|
|
g_assert (lock >= MM_MODEM_LOCK_UNKNOWN);
|
|
g_assert (lock <= MM_MODEM_LOCK_PH_NETSUB_PUK);
|
|
|
|
return g_error_new (MM_MOBILE_EQUIPMENT_ERROR,
|
|
errors_for_locks[lock],
|
|
"Device is locked: '%s'",
|
|
mm_modem_lock_get_string (lock));
|
|
}
|
|
|
|
gboolean
|
|
mm_sim_send_pin_finish (MMSim *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
return !g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error);
|
|
}
|
|
|
|
gboolean
|
|
mm_sim_send_puk_finish (MMSim *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
return !g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error);
|
|
}
|
|
|
|
static void
|
|
unlock_check_ready (MMIfaceModem *modem,
|
|
GAsyncResult *res,
|
|
SendPinPukContext *ctx)
|
|
{
|
|
GError *error = NULL;
|
|
MMModemLock lock;
|
|
|
|
lock = mm_iface_modem_unlock_check_finish (modem, res, &error);
|
|
if (lock != MM_MODEM_LOCK_NONE) {
|
|
/* Device is locked. Now:
|
|
* - If we got an error in the original send-pin action, report it.
|
|
* - If we got an error in the pin-check action, report it.
|
|
* - Otherwise, build our own error from the lock code.
|
|
*/
|
|
if (ctx->save_error) {
|
|
g_simple_async_result_take_error (ctx->result, ctx->save_error);
|
|
ctx->save_error = NULL;
|
|
g_clear_error (&error);
|
|
} else if (error)
|
|
g_simple_async_result_take_error (ctx->result, error);
|
|
else
|
|
g_simple_async_result_take_error (ctx->result,
|
|
error_for_unlock_check (lock));
|
|
send_pin_puk_context_complete_and_free (ctx);
|
|
}
|
|
|
|
g_simple_async_result_set_op_res_gboolean (ctx->result, TRUE);
|
|
send_pin_puk_context_complete_and_free (ctx);
|
|
return;
|
|
}
|
|
|
|
static void
|
|
send_pin_ready (MMSim *self,
|
|
GAsyncResult *res,
|
|
SendPinPukContext *ctx)
|
|
{
|
|
MM_SIM_GET_CLASS (self)->send_pin_finish (self, res, &ctx->save_error);
|
|
|
|
/* Once pin/puk has been sent, recheck lock */
|
|
mm_iface_modem_unlock_check (MM_IFACE_MODEM (self->priv->modem),
|
|
(GAsyncReadyCallback)unlock_check_ready,
|
|
ctx);
|
|
}
|
|
|
|
static void
|
|
send_puk_ready (MMSim *self,
|
|
GAsyncResult *res,
|
|
SendPinPukContext *ctx)
|
|
{
|
|
MM_SIM_GET_CLASS (self)->send_puk_finish (self, res, &ctx->save_error);
|
|
|
|
/* Once pin/puk has been sent, recheck lock */
|
|
mm_iface_modem_unlock_check (MM_IFACE_MODEM (self->priv->modem),
|
|
(GAsyncReadyCallback)unlock_check_ready,
|
|
ctx);
|
|
}
|
|
|
|
void
|
|
mm_sim_send_pin (MMSim *self,
|
|
const gchar *pin,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
SendPinPukContext *ctx;
|
|
|
|
/* If sending PIN is not implemented, report an error */
|
|
if (!MM_SIM_GET_CLASS (self)->send_pin ||
|
|
!MM_SIM_GET_CLASS (self)->send_pin_finish) {
|
|
g_simple_async_report_error_in_idle (G_OBJECT (self),
|
|
callback,
|
|
user_data,
|
|
MM_CORE_ERROR,
|
|
MM_CORE_ERROR_UNSUPPORTED,
|
|
"Cannot send PIN: "
|
|
"operation not supported");
|
|
return;
|
|
}
|
|
|
|
ctx = g_new0 (SendPinPukContext, 1);
|
|
ctx->self = g_object_ref (self);
|
|
ctx->result = g_simple_async_result_new (G_OBJECT (self),
|
|
callback,
|
|
user_data,
|
|
mm_sim_send_pin);
|
|
|
|
MM_SIM_GET_CLASS (self)->send_pin (self,
|
|
pin,
|
|
(GAsyncReadyCallback)send_pin_ready,
|
|
ctx);
|
|
}
|
|
|
|
void
|
|
mm_sim_send_puk (MMSim *self,
|
|
const gchar *puk,
|
|
const gchar *new_pin,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
SendPinPukContext *ctx;
|
|
|
|
/* If sending PIN is not implemented, report an error */
|
|
if (!MM_SIM_GET_CLASS (self)->send_puk ||
|
|
!MM_SIM_GET_CLASS (self)->send_puk_finish) {
|
|
g_simple_async_report_error_in_idle (G_OBJECT (self),
|
|
callback,
|
|
user_data,
|
|
MM_CORE_ERROR,
|
|
MM_CORE_ERROR_UNSUPPORTED,
|
|
"Cannot send PUK: "
|
|
"operation not supported");
|
|
return;
|
|
}
|
|
|
|
ctx = g_new0 (SendPinPukContext, 1);
|
|
ctx->self = g_object_ref (self);
|
|
ctx->result = g_simple_async_result_new (G_OBJECT (self),
|
|
callback,
|
|
user_data,
|
|
mm_sim_send_puk);
|
|
|
|
MM_SIM_GET_CLASS (self)->send_puk (self,
|
|
puk,
|
|
new_pin,
|
|
(GAsyncReadyCallback)send_puk_ready,
|
|
ctx);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* SEND PIN (DBus call handling) */
|
|
|
|
typedef struct {
|
|
MMSim *self;
|
|
GDBusMethodInvocation *invocation;
|
|
gchar *pin;
|
|
} HandleSendPinContext;
|
|
|
|
static void
|
|
handle_send_pin_context_free (HandleSendPinContext *ctx)
|
|
{
|
|
g_object_unref (ctx->invocation);
|
|
g_object_unref (ctx->self);
|
|
g_free (ctx->pin);
|
|
g_free (ctx);
|
|
}
|
|
|
|
static void
|
|
handle_send_pin_ready (MMSim *self,
|
|
GAsyncResult *res,
|
|
HandleSendPinContext *ctx)
|
|
{
|
|
GError *error = NULL;
|
|
|
|
if (!mm_sim_send_pin_finish (self, res, &error))
|
|
g_dbus_method_invocation_take_error (ctx->invocation, error);
|
|
else
|
|
mm_gdbus_sim_complete_send_pin (MM_GDBUS_SIM (self), ctx->invocation);
|
|
|
|
handle_send_pin_context_free (ctx);
|
|
}
|
|
|
|
static void
|
|
handle_send_pin_auth_ready (MMBaseModem *modem,
|
|
GAsyncResult *res,
|
|
HandleSendPinContext *ctx)
|
|
{
|
|
GError *error = NULL;
|
|
|
|
if (!mm_base_modem_authorize_finish (modem, res, &error)) {
|
|
g_dbus_method_invocation_take_error (ctx->invocation, error);
|
|
handle_send_pin_context_free (ctx);
|
|
return;
|
|
}
|
|
|
|
mm_sim_send_pin (ctx->self,
|
|
ctx->pin,
|
|
(GAsyncReadyCallback)handle_send_pin_ready,
|
|
ctx);
|
|
}
|
|
|
|
static gboolean
|
|
handle_send_pin (MMSim *self,
|
|
GDBusMethodInvocation *invocation,
|
|
const gchar *pin)
|
|
{
|
|
HandleSendPinContext *ctx;
|
|
|
|
ctx = g_new0 (HandleSendPinContext, 1);
|
|
ctx->self = g_object_ref (self);
|
|
ctx->invocation = g_object_ref (invocation);
|
|
ctx->pin = g_strdup (pin);
|
|
|
|
mm_base_modem_authorize (self->priv->modem,
|
|
invocation,
|
|
MM_AUTHORIZATION_DEVICE_CONTROL,
|
|
(GAsyncReadyCallback)handle_send_pin_auth_ready,
|
|
ctx);
|
|
return TRUE;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* SEND PUK (DBus call handling) */
|
|
|
|
typedef struct {
|
|
MMSim *self;
|
|
GDBusMethodInvocation *invocation;
|
|
gchar *puk;
|
|
gchar *new_pin;
|
|
} HandleSendPukContext;
|
|
|
|
static void
|
|
handle_send_puk_context_free (HandleSendPukContext *ctx)
|
|
{
|
|
g_object_unref (ctx->invocation);
|
|
g_object_unref (ctx->self);
|
|
g_free (ctx->puk);
|
|
g_free (ctx->new_pin);
|
|
g_free (ctx);
|
|
}
|
|
|
|
static void
|
|
handle_send_puk_ready (MMSim *self,
|
|
GAsyncResult *res,
|
|
HandleSendPukContext *ctx)
|
|
{
|
|
GError *error = NULL;
|
|
|
|
if (!mm_sim_send_puk_finish (self, res, &error))
|
|
g_dbus_method_invocation_take_error (ctx->invocation, error);
|
|
else
|
|
mm_gdbus_sim_complete_send_puk (MM_GDBUS_SIM (self), ctx->invocation);
|
|
|
|
handle_send_puk_context_free (ctx);
|
|
}
|
|
|
|
static void
|
|
handle_send_puk_auth_ready (MMBaseModem *modem,
|
|
GAsyncResult *res,
|
|
HandleSendPukContext *ctx)
|
|
{
|
|
GError *error = NULL;
|
|
|
|
if (!mm_base_modem_authorize_finish (modem, res, &error)) {
|
|
g_dbus_method_invocation_take_error (ctx->invocation, error);
|
|
handle_send_puk_context_free (ctx);
|
|
return;
|
|
}
|
|
|
|
mm_sim_send_puk (ctx->self,
|
|
ctx->puk,
|
|
ctx->new_pin,
|
|
(GAsyncReadyCallback)handle_send_puk_ready,
|
|
ctx);
|
|
}
|
|
|
|
static gboolean
|
|
handle_send_puk (MMSim *self,
|
|
GDBusMethodInvocation *invocation,
|
|
const gchar *puk,
|
|
const gchar *new_pin)
|
|
{
|
|
HandleSendPukContext *ctx;
|
|
|
|
ctx = g_new0 (HandleSendPukContext, 1);
|
|
ctx->self = g_object_ref (self);
|
|
ctx->invocation = g_object_ref (invocation);
|
|
ctx->puk = g_strdup (puk);
|
|
ctx->new_pin = g_strdup (new_pin);
|
|
|
|
mm_base_modem_authorize (self->priv->modem,
|
|
invocation,
|
|
MM_AUTHORIZATION_DEVICE_CONTROL,
|
|
(GAsyncReadyCallback)handle_send_puk_auth_ready,
|
|
ctx);
|
|
return TRUE;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
static void
|
|
mm_sim_dbus_export (MMSim *self)
|
|
{
|
|
GError *error = NULL;
|
|
|
|
/* Handle method invocations */
|
|
g_signal_connect (self,
|
|
"handle-change-pin",
|
|
G_CALLBACK (handle_change_pin),
|
|
NULL);
|
|
g_signal_connect (self,
|
|
"handle-enable-pin",
|
|
G_CALLBACK (handle_enable_pin),
|
|
NULL);
|
|
g_signal_connect (self,
|
|
"handle-send-pin",
|
|
G_CALLBACK (handle_send_pin),
|
|
NULL);
|
|
g_signal_connect (self,
|
|
"handle-send-puk",
|
|
G_CALLBACK (handle_send_puk),
|
|
NULL);
|
|
|
|
if (!g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (self),
|
|
self->priv->connection,
|
|
self->priv->path,
|
|
&error)) {
|
|
mm_warn ("couldn't export SIM at '%s': '%s'",
|
|
self->priv->path,
|
|
error->message);
|
|
g_error_free (error);
|
|
}
|
|
}
|
|
|
|
static void
|
|
mm_sim_dbus_unexport (MMSim *self)
|
|
{
|
|
/* Only unexport if currently exported */
|
|
if (g_dbus_interface_skeleton_get_object_path (G_DBUS_INTERFACE_SKELETON (self)))
|
|
g_dbus_interface_skeleton_unexport (G_DBUS_INTERFACE_SKELETON (self));
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
const gchar *
|
|
mm_sim_get_path (MMSim *self)
|
|
{
|
|
return self->priv->path;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
#undef STR_REPLY_READY_FN
|
|
#define STR_REPLY_READY_FN(NAME) \
|
|
static void \
|
|
NAME##_command_ready (MMBaseModem *modem, \
|
|
GAsyncResult *res, \
|
|
GSimpleAsyncResult *operation_result) \
|
|
{ \
|
|
GError *error = NULL; \
|
|
const gchar *response; \
|
|
\
|
|
response = mm_base_modem_at_command_finish (modem, res, &error); \
|
|
if (error) \
|
|
g_simple_async_result_take_error (operation_result, error); \
|
|
else \
|
|
g_simple_async_result_set_op_res_gpointer (operation_result, \
|
|
(gpointer)response, \
|
|
NULL); \
|
|
\
|
|
g_simple_async_result_complete (operation_result); \
|
|
g_object_unref (operation_result); \
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* SIM IDENTIFIER */
|
|
|
|
static gchar *
|
|
parse_iccid (const gchar *response,
|
|
GError **error)
|
|
{
|
|
gchar buf[21];
|
|
gchar swapped[21];
|
|
const gchar *str;
|
|
gint sw1;
|
|
gint sw2;
|
|
gboolean success = FALSE;
|
|
|
|
memset (buf, 0, sizeof (buf));
|
|
str = mm_strip_tag (response, "+CRSM:");
|
|
if (sscanf (str, "%d,%d,\"%20c\"", &sw1, &sw2, (char *) &buf) == 3)
|
|
success = TRUE;
|
|
else {
|
|
/* May not include quotes... */
|
|
if (sscanf (str, "%d,%d,%20c", &sw1, &sw2, (char *) &buf) == 3)
|
|
success = TRUE;
|
|
}
|
|
|
|
if (!success) {
|
|
g_set_error (error,
|
|
MM_CORE_ERROR,
|
|
MM_CORE_ERROR_FAILED,
|
|
"Could not parse the CRSM response");
|
|
return NULL;
|
|
}
|
|
|
|
if ((sw1 == 0x90 && sw2 == 0x00) ||
|
|
(sw1 == 0x91) ||
|
|
(sw1 == 0x92) ||
|
|
(sw1 == 0x9f)) {
|
|
gsize len = 0;
|
|
gint f_pos = -1;
|
|
gint i;
|
|
|
|
/* Make sure the buffer is only digits or 'F' */
|
|
for (len = 0; len < sizeof (buf) && buf[len]; len++) {
|
|
if (isdigit (buf[len]))
|
|
continue;
|
|
if (buf[len] == 'F' || buf[len] == 'f') {
|
|
buf[len] = 'F'; /* canonicalize the F */
|
|
f_pos = len;
|
|
continue;
|
|
}
|
|
if (buf[len] == '\"') {
|
|
buf[len] = 0;
|
|
break;
|
|
}
|
|
|
|
/* Invalid character */
|
|
g_set_error (error,
|
|
MM_CORE_ERROR,
|
|
MM_CORE_ERROR_FAILED,
|
|
"CRSM ICCID response contained invalid character '%c'",
|
|
buf[len]);
|
|
return NULL;
|
|
}
|
|
|
|
/* BCD encoded ICCIDs are 20 digits long */
|
|
if (len != 20) {
|
|
g_set_error (error,
|
|
MM_CORE_ERROR,
|
|
MM_CORE_ERROR_FAILED,
|
|
"Invalid +CRSM ICCID response size (was %zd, expected 20)",
|
|
len);
|
|
return NULL;
|
|
}
|
|
|
|
/* Ensure if there's an 'F' that it's second-to-last */
|
|
if ((f_pos >= 0) && (f_pos != len - 2)) {
|
|
g_set_error (error,
|
|
MM_CORE_ERROR,
|
|
MM_CORE_ERROR_FAILED,
|
|
"Invalid +CRSM ICCID length (unexpected F)");
|
|
return NULL;
|
|
}
|
|
|
|
/* Swap digits in the EFiccid response to get the actual ICCID, each
|
|
* group of 2 digits is reversed in the +CRSM response. i.e.:
|
|
*
|
|
* 21436587 -> 12345678
|
|
*/
|
|
memset (swapped, 0, sizeof (swapped));
|
|
for (i = 0; i < 10; i++) {
|
|
swapped[i * 2] = buf[(i * 2) + 1];
|
|
swapped[(i * 2) + 1] = buf[i * 2];
|
|
}
|
|
|
|
/* Zero out the F for 19 digit ICCIDs */
|
|
if (swapped[len - 1] == 'F')
|
|
swapped[len - 1] = 0;
|
|
|
|
|
|
return g_strdup (swapped);
|
|
} else {
|
|
g_set_error (error,
|
|
MM_CORE_ERROR,
|
|
MM_CORE_ERROR_FAILED,
|
|
"SIM failed to handle CRSM request (sw1 %d sw2 %d)",
|
|
sw1, sw2);
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
static gchar *
|
|
load_sim_identifier_finish (MMSim *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
const gchar *result;
|
|
gchar *sim_identifier;
|
|
|
|
if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error))
|
|
return NULL;
|
|
result = g_simple_async_result_get_op_res_gpointer (G_SIMPLE_ASYNC_RESULT (res));
|
|
|
|
sim_identifier = parse_iccid (result, error);
|
|
if (!sim_identifier)
|
|
return NULL;
|
|
|
|
mm_dbg ("loaded SIM identifier: %s", sim_identifier);
|
|
return sim_identifier;
|
|
}
|
|
|
|
STR_REPLY_READY_FN (load_sim_identifier)
|
|
|
|
static void
|
|
load_sim_identifier (MMSim *self,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
mm_dbg ("loading SIM identifier...");
|
|
|
|
/* READ BINARY of EFiccid (ICC Identification) ETSI TS 102.221 section 13.2 */
|
|
mm_base_modem_at_command (
|
|
MM_BASE_MODEM (self->priv->modem),
|
|
"+CRSM=176,12258,0,0,10",
|
|
20,
|
|
FALSE,
|
|
(GAsyncReadyCallback)load_sim_identifier_command_ready,
|
|
g_simple_async_result_new (G_OBJECT (self),
|
|
callback,
|
|
user_data,
|
|
load_sim_identifier));
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* IMSI */
|
|
|
|
static gchar *
|
|
load_imsi_finish (MMSim *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
gchar *imsi;
|
|
|
|
if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error))
|
|
return NULL;
|
|
imsi = g_strdup (g_simple_async_result_get_op_res_gpointer (G_SIMPLE_ASYNC_RESULT (res)));
|
|
|
|
mm_dbg ("loaded IMSI: %s", imsi);
|
|
return imsi;
|
|
}
|
|
|
|
STR_REPLY_READY_FN (load_imsi)
|
|
|
|
static void
|
|
load_imsi (MMSim *self,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
mm_dbg ("loading IMSI...");
|
|
|
|
mm_base_modem_at_command (
|
|
MM_BASE_MODEM (self->priv->modem),
|
|
"+CIMI",
|
|
3,
|
|
TRUE,
|
|
(GAsyncReadyCallback)load_imsi_command_ready,
|
|
g_simple_async_result_new (G_OBJECT (self),
|
|
callback,
|
|
user_data,
|
|
load_imsi));
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Operator ID */
|
|
|
|
static guint
|
|
parse_mnc_length (const gchar *response,
|
|
GError **error)
|
|
{
|
|
gint sw1;
|
|
gint sw2;
|
|
gboolean success = FALSE;
|
|
gchar hex[51];
|
|
|
|
memset (hex, 0, sizeof (hex));
|
|
if (sscanf (response, "+CRSM:%d,%d,\"%50c\"", &sw1, &sw2, (char *) &hex) == 3)
|
|
success = TRUE;
|
|
else {
|
|
/* May not include quotes... */
|
|
if (sscanf (response, "+CRSM:%d,%d,%50c", &sw1, &sw2, (char *) &hex) == 3)
|
|
success = TRUE;
|
|
}
|
|
|
|
if (!success) {
|
|
g_set_error (error,
|
|
MM_CORE_ERROR,
|
|
MM_CORE_ERROR_FAILED,
|
|
"Could not parse the CRSM response");
|
|
return 0;
|
|
}
|
|
|
|
if ((sw1 == 0x90 && sw2 == 0x00) ||
|
|
(sw1 == 0x91) ||
|
|
(sw1 == 0x92) ||
|
|
(sw1 == 0x9f)) {
|
|
gsize buflen = 0;
|
|
guint32 mnc_len;
|
|
gchar *bin;
|
|
|
|
/* Make sure the buffer is only hex characters */
|
|
while (buflen < sizeof (hex) && hex[buflen]) {
|
|
if (!isxdigit (hex[buflen])) {
|
|
hex[buflen] = 0x0;
|
|
break;
|
|
}
|
|
buflen++;
|
|
}
|
|
|
|
/* Convert hex string to binary */
|
|
bin = utils_hexstr2bin (hex, &buflen);
|
|
if (!bin || buflen < 4) {
|
|
g_set_error (error,
|
|
MM_CORE_ERROR,
|
|
MM_CORE_ERROR_FAILED,
|
|
"SIM returned malformed response '%s'",
|
|
hex);
|
|
g_free (bin);
|
|
return 0;
|
|
}
|
|
|
|
/* MNC length is byte 4 of this SIM file */
|
|
mnc_len = bin[3] & 0xFF;
|
|
if (mnc_len == 2 || mnc_len == 3) {
|
|
g_free (bin);
|
|
return mnc_len;
|
|
}
|
|
|
|
g_set_error (error,
|
|
MM_CORE_ERROR,
|
|
MM_CORE_ERROR_FAILED,
|
|
"SIM returned invalid MNC length %d (should be either 2 or 3)",
|
|
mnc_len);
|
|
g_free (bin);
|
|
return 0;
|
|
}
|
|
|
|
g_set_error (error,
|
|
MM_CORE_ERROR,
|
|
MM_CORE_ERROR_FAILED,
|
|
"SIM failed to handle CRSM request (sw1 %d sw2 %d)",
|
|
sw1, sw2);
|
|
return 0;
|
|
}
|
|
|
|
static gchar *
|
|
load_operator_identifier_finish (MMSim *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
GError *inner_error = NULL;
|
|
const gchar *imsi;
|
|
const gchar *result;
|
|
guint mnc_length;
|
|
|
|
if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error))
|
|
return NULL;
|
|
result = g_simple_async_result_get_op_res_gpointer (G_SIMPLE_ASYNC_RESULT (res));
|
|
|
|
imsi = mm_gdbus_sim_get_imsi (MM_GDBUS_SIM (self));
|
|
if (!imsi) {
|
|
g_set_error (error,
|
|
MM_CORE_ERROR,
|
|
MM_CORE_ERROR_FAILED,
|
|
"Cannot load Operator ID without IMSI");
|
|
return NULL;
|
|
}
|
|
|
|
mnc_length = parse_mnc_length (result, &inner_error);
|
|
if (inner_error) {
|
|
g_propagate_error (error, inner_error);
|
|
return NULL;
|
|
}
|
|
|
|
/* Build Operator ID */
|
|
return g_strndup (imsi, 3 + mnc_length);
|
|
}
|
|
|
|
STR_REPLY_READY_FN (load_operator_identifier)
|
|
|
|
static void
|
|
load_operator_identifier (MMSim *self,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
mm_dbg ("loading Operator ID...");
|
|
|
|
/* READ BINARY of EFad (Administrative Data) ETSI 51.011 section 10.3.18 */
|
|
mm_base_modem_at_command (
|
|
MM_BASE_MODEM (self->priv->modem),
|
|
"+CRSM=176,28589,0,0,4",
|
|
10,
|
|
FALSE,
|
|
(GAsyncReadyCallback)load_operator_identifier_command_ready,
|
|
g_simple_async_result_new (G_OBJECT (self),
|
|
callback,
|
|
user_data,
|
|
load_operator_identifier));
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Operator Name (Service Provider Name) */
|
|
|
|
static gchar *
|
|
parse_spn (const gchar *response,
|
|
GError **error)
|
|
{
|
|
gint sw1;
|
|
gint sw2;
|
|
gboolean success = FALSE;
|
|
gchar hex[51];
|
|
|
|
memset (hex, 0, sizeof (hex));
|
|
if (sscanf (response, "+CRSM:%d,%d,\"%50c\"", &sw1, &sw2, (char *) &hex) == 3)
|
|
success = TRUE;
|
|
else {
|
|
/* May not include quotes... */
|
|
if (sscanf (response, "+CRSM:%d,%d,%50c", &sw1, &sw2, (char *) &hex) == 3)
|
|
success = TRUE;
|
|
}
|
|
|
|
if (!success) {
|
|
g_set_error (error,
|
|
MM_CORE_ERROR,
|
|
MM_CORE_ERROR_FAILED,
|
|
"Could not parse the CRSM response");
|
|
return NULL;
|
|
}
|
|
|
|
if ((sw1 == 0x90 && sw2 == 0x00) ||
|
|
(sw1 == 0x91) ||
|
|
(sw1 == 0x92) ||
|
|
(sw1 == 0x9f)) {
|
|
gsize buflen = 0;
|
|
gchar *bin;
|
|
gchar *utf8;
|
|
|
|
/* Make sure the buffer is only hex characters */
|
|
while (buflen < sizeof (hex) && hex[buflen]) {
|
|
if (!isxdigit (hex[buflen])) {
|
|
hex[buflen] = 0x0;
|
|
break;
|
|
}
|
|
buflen++;
|
|
}
|
|
|
|
/* Convert hex string to binary */
|
|
bin = utils_hexstr2bin (hex, &buflen);
|
|
if (!bin) {
|
|
g_set_error (error,
|
|
MM_CORE_ERROR,
|
|
MM_CORE_ERROR_FAILED,
|
|
"SIM returned malformed response '%s'",
|
|
hex);
|
|
return NULL;
|
|
}
|
|
|
|
/* Remove the FF filler at the end */
|
|
while (buflen > 1 && bin[buflen - 1] == (char)0xff)
|
|
buflen--;
|
|
|
|
/* First byte is metadata; remainder is GSM-7 unpacked into octets; convert to UTF8 */
|
|
utf8 = (gchar *)mm_charset_gsm_unpacked_to_utf8 ((guint8 *)bin + 1, buflen - 1);
|
|
g_free (bin);
|
|
return utf8;
|
|
}
|
|
|
|
g_set_error (error,
|
|
MM_CORE_ERROR,
|
|
MM_CORE_ERROR_FAILED,
|
|
"SIM failed to handle CRSM request (sw1 %d sw2 %d)",
|
|
sw1, sw2);
|
|
return NULL;
|
|
}
|
|
|
|
static gchar *
|
|
load_operator_name_finish (MMSim *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
const gchar *result;
|
|
|
|
if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error))
|
|
return NULL;
|
|
result = g_simple_async_result_get_op_res_gpointer (G_SIMPLE_ASYNC_RESULT (res));
|
|
|
|
return parse_spn (result, error);
|
|
}
|
|
|
|
STR_REPLY_READY_FN (load_operator_name)
|
|
|
|
static void
|
|
load_operator_name (MMSim *self,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
mm_dbg ("loading Operator Name...");
|
|
|
|
/* READ BINARY of EFspn (Service Provider Name) ETSI 51.011 section 10.3.11 */
|
|
mm_base_modem_at_command (
|
|
MM_BASE_MODEM (self->priv->modem),
|
|
"+CRSM=176,28486,0,0,17",
|
|
10,
|
|
FALSE,
|
|
(GAsyncReadyCallback)load_operator_name_command_ready,
|
|
g_simple_async_result_new (G_OBJECT (self),
|
|
callback,
|
|
user_data,
|
|
load_operator_name));
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
typedef struct _InitAsyncContext InitAsyncContext;
|
|
static void interface_initialization_step (InitAsyncContext *ctx);
|
|
|
|
typedef enum {
|
|
INITIALIZATION_STEP_FIRST,
|
|
INITIALIZATION_STEP_SIM_IDENTIFIER,
|
|
INITIALIZATION_STEP_IMSI,
|
|
INITIALIZATION_STEP_OPERATOR_ID,
|
|
INITIALIZATION_STEP_OPERATOR_NAME,
|
|
INITIALIZATION_STEP_LAST
|
|
} InitializationStep;
|
|
|
|
struct _InitAsyncContext {
|
|
GSimpleAsyncResult *result;
|
|
GCancellable *cancellable;
|
|
MMSim *self;
|
|
InitializationStep step;
|
|
guint sim_identifier_tries;
|
|
};
|
|
|
|
static void
|
|
init_async_context_free (InitAsyncContext *ctx)
|
|
{
|
|
g_object_unref (ctx->self);
|
|
g_object_unref (ctx->result);
|
|
if (ctx->cancellable)
|
|
g_object_unref (ctx->cancellable);
|
|
g_free (ctx);
|
|
}
|
|
|
|
MMSim *
|
|
mm_sim_new_finish (GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
GObject *source;
|
|
GObject *sim;
|
|
|
|
source = g_async_result_get_source_object (res);
|
|
sim = g_async_initable_new_finish (G_ASYNC_INITABLE (source), res, error);
|
|
g_object_unref (source);
|
|
|
|
if (!sim)
|
|
return NULL;
|
|
|
|
/* Only export valid SIMs */
|
|
mm_sim_export (MM_SIM (sim));
|
|
|
|
return MM_SIM (sim);
|
|
}
|
|
|
|
static gboolean
|
|
initable_init_finish (GAsyncInitable *initable,
|
|
GAsyncResult *result,
|
|
GError **error)
|
|
{
|
|
return !g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (result), error);
|
|
}
|
|
|
|
static void
|
|
load_sim_identifier_ready (MMSim *self,
|
|
GAsyncResult *res,
|
|
InitAsyncContext *ctx)
|
|
{
|
|
GError *error = NULL;
|
|
gchar *simid;
|
|
|
|
simid = MM_SIM_GET_CLASS (ctx->self)->load_sim_identifier_finish (self, res, &error);
|
|
if (!simid) {
|
|
/* TODO: make the retries gobi-specific? */
|
|
|
|
/* Try one more time... Gobi 1K cards may reply to the first
|
|
* request with '+CRSM: 106,134,""' which is bogus because
|
|
* subsequent requests work fine.
|
|
*/
|
|
if (++ctx->sim_identifier_tries < 2) {
|
|
g_clear_error (&error);
|
|
interface_initialization_step (ctx);
|
|
return;
|
|
}
|
|
|
|
mm_warn ("couldn't load SIM identifier: '%s'",
|
|
error ? error->message : "unknown error");
|
|
g_clear_error (&error);
|
|
}
|
|
|
|
mm_gdbus_sim_set_sim_identifier (MM_GDBUS_SIM (self), simid);
|
|
g_free (simid);
|
|
|
|
/* Go on to next step */
|
|
ctx->step++;
|
|
interface_initialization_step (ctx);
|
|
}
|
|
|
|
#undef STR_REPLY_READY_FN
|
|
#define STR_REPLY_READY_FN(NAME,DISPLAY) \
|
|
static void \
|
|
load_##NAME##_ready (MMSim *self, \
|
|
GAsyncResult *res, \
|
|
InitAsyncContext *ctx) \
|
|
{ \
|
|
GError *error = NULL; \
|
|
gchar *val; \
|
|
\
|
|
val = MM_SIM_GET_CLASS (ctx->self)->load_##NAME##_finish (self, res, &error); \
|
|
mm_gdbus_sim_set_##NAME (MM_GDBUS_SIM (self), val); \
|
|
g_free (val); \
|
|
\
|
|
if (error) { \
|
|
mm_warn ("couldn't load %s: '%s'", DISPLAY, error->message); \
|
|
g_error_free (error); \
|
|
} \
|
|
\
|
|
/* Go on to next step */ \
|
|
ctx->step++; \
|
|
interface_initialization_step (ctx); \
|
|
}
|
|
|
|
STR_REPLY_READY_FN (imsi, "IMSI")
|
|
STR_REPLY_READY_FN (operator_identifier, "Operator identifier")
|
|
STR_REPLY_READY_FN (operator_name, "Operator name")
|
|
|
|
static void
|
|
interface_initialization_step (InitAsyncContext *ctx)
|
|
{
|
|
switch (ctx->step) {
|
|
case INITIALIZATION_STEP_FIRST:
|
|
/* Fall down to next step */
|
|
ctx->step++;
|
|
|
|
case INITIALIZATION_STEP_SIM_IDENTIFIER:
|
|
/* SIM ID is meant to be loaded only once during the whole
|
|
* lifetime of the modem. Therefore, if we already have them loaded,
|
|
* don't try to load them again. */
|
|
if (mm_gdbus_sim_get_sim_identifier (MM_GDBUS_SIM (ctx->self)) == NULL &&
|
|
MM_SIM_GET_CLASS (ctx->self)->load_sim_identifier &&
|
|
MM_SIM_GET_CLASS (ctx->self)->load_sim_identifier_finish) {
|
|
MM_SIM_GET_CLASS (ctx->self)->load_sim_identifier (
|
|
ctx->self,
|
|
(GAsyncReadyCallback)load_sim_identifier_ready,
|
|
ctx);
|
|
return;
|
|
}
|
|
/* Fall down to next step */
|
|
ctx->step++;
|
|
|
|
case INITIALIZATION_STEP_IMSI:
|
|
/* IMSI is meant to be loaded only once during the whole
|
|
* lifetime of the modem. Therefore, if we already have them loaded,
|
|
* don't try to load them again. */
|
|
if (mm_gdbus_sim_get_imsi (MM_GDBUS_SIM (ctx->self)) == NULL &&
|
|
MM_SIM_GET_CLASS (ctx->self)->load_imsi &&
|
|
MM_SIM_GET_CLASS (ctx->self)->load_imsi_finish) {
|
|
MM_SIM_GET_CLASS (ctx->self)->load_imsi (
|
|
ctx->self,
|
|
(GAsyncReadyCallback)load_imsi_ready,
|
|
ctx);
|
|
return;
|
|
}
|
|
/* Fall down to next step */
|
|
ctx->step++;
|
|
|
|
case INITIALIZATION_STEP_OPERATOR_ID:
|
|
/* Operator ID is meant to be loaded only once during the whole
|
|
* lifetime of the modem. Therefore, if we already have them loaded,
|
|
* don't try to load them again. */
|
|
if (mm_gdbus_sim_get_operator_identifier (MM_GDBUS_SIM (ctx->self)) == NULL &&
|
|
MM_SIM_GET_CLASS (ctx->self)->load_operator_identifier &&
|
|
MM_SIM_GET_CLASS (ctx->self)->load_operator_identifier_finish) {
|
|
MM_SIM_GET_CLASS (ctx->self)->load_operator_identifier (
|
|
ctx->self,
|
|
(GAsyncReadyCallback)load_operator_identifier_ready,
|
|
ctx);
|
|
return;
|
|
}
|
|
/* Fall down to next step */
|
|
ctx->step++;
|
|
|
|
case INITIALIZATION_STEP_OPERATOR_NAME:
|
|
/* Operator Name is meant to be loaded only once during the whole
|
|
* lifetime of the modem. Therefore, if we already have them loaded,
|
|
* don't try to load them again. */
|
|
if (mm_gdbus_sim_get_operator_name (MM_GDBUS_SIM (ctx->self)) == NULL &&
|
|
MM_SIM_GET_CLASS (ctx->self)->load_operator_name &&
|
|
MM_SIM_GET_CLASS (ctx->self)->load_operator_name_finish) {
|
|
MM_SIM_GET_CLASS (ctx->self)->load_operator_name (
|
|
ctx->self,
|
|
(GAsyncReadyCallback)load_operator_name_ready,
|
|
ctx);
|
|
return;
|
|
}
|
|
/* Fall down to next step */
|
|
ctx->step++;
|
|
|
|
case INITIALIZATION_STEP_LAST:
|
|
/* We are done without errors! */
|
|
g_simple_async_result_set_op_res_gboolean (ctx->result, TRUE);
|
|
g_simple_async_result_complete_in_idle (ctx->result);
|
|
init_async_context_free (ctx);
|
|
return;
|
|
}
|
|
|
|
|
|
g_assert_not_reached ();
|
|
}
|
|
|
|
static void
|
|
common_init_async (GAsyncInitable *initable,
|
|
GCancellable *cancellable,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
|
|
{
|
|
InitAsyncContext *ctx;
|
|
|
|
ctx = g_new (InitAsyncContext, 1);
|
|
ctx->self = g_object_ref (initable);
|
|
ctx->result = g_simple_async_result_new (G_OBJECT (initable),
|
|
callback,
|
|
user_data,
|
|
common_init_async);
|
|
ctx->cancellable = (cancellable ?
|
|
g_object_ref (cancellable) :
|
|
NULL);
|
|
ctx->step = INITIALIZATION_STEP_FIRST;
|
|
ctx->sim_identifier_tries = 0;
|
|
|
|
interface_initialization_step (ctx);
|
|
}
|
|
|
|
static void
|
|
initable_init_async (GAsyncInitable *initable,
|
|
int io_priority,
|
|
GCancellable *cancellable,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
mm_gdbus_sim_set_sim_identifier (MM_GDBUS_SIM (initable), NULL);
|
|
mm_gdbus_sim_set_imsi (MM_GDBUS_SIM (initable), NULL);
|
|
mm_gdbus_sim_set_operator_identifier (MM_GDBUS_SIM (initable), NULL);
|
|
mm_gdbus_sim_set_operator_name (MM_GDBUS_SIM (initable), NULL);
|
|
|
|
common_init_async (initable, cancellable, callback, user_data);
|
|
}
|
|
|
|
void
|
|
mm_sim_new (MMBaseModem *modem,
|
|
GCancellable *cancellable,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
g_async_initable_new_async (MM_TYPE_SIM,
|
|
G_PRIORITY_DEFAULT,
|
|
cancellable,
|
|
callback,
|
|
user_data,
|
|
MM_SIM_MODEM, modem,
|
|
NULL);
|
|
}
|
|
|
|
gboolean
|
|
mm_sim_initialize_finish (MMSim *self,
|
|
GAsyncResult *result,
|
|
GError **error)
|
|
{
|
|
return initable_init_finish (G_ASYNC_INITABLE (self), result, error);
|
|
}
|
|
|
|
void
|
|
mm_sim_initialize (MMSim *self,
|
|
GCancellable *cancellable,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
common_init_async (G_ASYNC_INITABLE (self),
|
|
cancellable,
|
|
callback,
|
|
user_data);
|
|
}
|
|
|
|
static void
|
|
set_property (GObject *object,
|
|
guint prop_id,
|
|
const GValue *value,
|
|
GParamSpec *pspec)
|
|
{
|
|
MMSim *self = MM_SIM (object);
|
|
|
|
switch (prop_id) {
|
|
case PROP_PATH:
|
|
g_free (self->priv->path);
|
|
self->priv->path = g_value_dup_string (value);
|
|
|
|
/* Export when we get a DBus connection AND we have a path */
|
|
if (self->priv->path &&
|
|
self->priv->connection)
|
|
mm_sim_dbus_export (self);
|
|
break;
|
|
case PROP_CONNECTION:
|
|
g_clear_object (&self->priv->connection);
|
|
self->priv->connection = g_value_dup_object (value);
|
|
|
|
/* Export when we get a DBus connection AND we have a path */
|
|
if (!self->priv->connection)
|
|
mm_sim_dbus_unexport (self);
|
|
else if (self->priv->path)
|
|
mm_sim_dbus_export (self);
|
|
break;
|
|
case PROP_MODEM:
|
|
g_clear_object (&self->priv->modem);
|
|
self->priv->modem = g_value_dup_object (value);
|
|
if (self->priv->modem) {
|
|
/* Bind the modem's connection (which is set when it is exported,
|
|
* and unset when unexported) to the SIM's connection */
|
|
g_object_bind_property (self->priv->modem, MM_BASE_MODEM_CONNECTION,
|
|
self, MM_SIM_CONNECTION,
|
|
G_BINDING_DEFAULT | G_BINDING_SYNC_CREATE);
|
|
}
|
|
break;
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
get_property (GObject *object,
|
|
guint prop_id,
|
|
GValue *value,
|
|
GParamSpec *pspec)
|
|
{
|
|
MMSim *self = MM_SIM (object);
|
|
|
|
switch (prop_id) {
|
|
case PROP_PATH:
|
|
g_value_set_string (value, self->priv->path);
|
|
break;
|
|
case PROP_CONNECTION:
|
|
g_value_set_object (value, self->priv->connection);
|
|
break;
|
|
case PROP_MODEM:
|
|
g_value_set_object (value, self->priv->modem);
|
|
break;
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
mm_sim_init (MMSim *self)
|
|
{
|
|
/* Initialize private data */
|
|
self->priv = G_TYPE_INSTANCE_GET_PRIVATE ((self),
|
|
MM_TYPE_SIM,
|
|
MMSimPrivate);
|
|
}
|
|
|
|
static void
|
|
finalize (GObject *object)
|
|
{
|
|
MMSim *self = MM_SIM (object);
|
|
|
|
g_free (self->priv->path);
|
|
|
|
G_OBJECT_CLASS (mm_sim_parent_class)->finalize (object);
|
|
}
|
|
|
|
static void
|
|
dispose (GObject *object)
|
|
{
|
|
MMSim *self = MM_SIM (object);
|
|
|
|
if (self->priv->connection) {
|
|
/* If we arrived here with a valid connection, make sure we unexport
|
|
* the object */
|
|
mm_sim_dbus_unexport (self);
|
|
g_clear_object (&self->priv->connection);
|
|
}
|
|
|
|
g_clear_object (&self->priv->modem);
|
|
|
|
G_OBJECT_CLASS (mm_sim_parent_class)->dispose (object);
|
|
}
|
|
|
|
static void
|
|
async_initable_iface_init (GAsyncInitableIface *iface)
|
|
{
|
|
iface->init_async = initable_init_async;
|
|
iface->init_finish = initable_init_finish;
|
|
}
|
|
|
|
static void
|
|
mm_sim_class_init (MMSimClass *klass)
|
|
{
|
|
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
|
|
|
g_type_class_add_private (object_class, sizeof (MMSimPrivate));
|
|
|
|
/* Virtual methods */
|
|
object_class->get_property = get_property;
|
|
object_class->set_property = set_property;
|
|
object_class->finalize = finalize;
|
|
object_class->dispose = dispose;
|
|
|
|
klass->load_sim_identifier = load_sim_identifier;
|
|
klass->load_sim_identifier_finish = load_sim_identifier_finish;
|
|
klass->load_imsi = load_imsi;
|
|
klass->load_imsi_finish = load_imsi_finish;
|
|
klass->load_operator_identifier = load_operator_identifier;
|
|
klass->load_operator_identifier_finish = load_operator_identifier_finish;
|
|
klass->load_operator_name = load_operator_name;
|
|
klass->load_operator_name_finish = load_operator_name_finish;
|
|
klass->send_pin = send_pin;
|
|
klass->send_pin_finish = common_send_pin_puk_finish;
|
|
klass->send_puk = send_puk;
|
|
klass->send_puk_finish = common_send_pin_puk_finish;
|
|
klass->enable_pin = enable_pin;
|
|
klass->enable_pin_finish = enable_pin_finish;
|
|
klass->change_pin = change_pin;
|
|
klass->change_pin_finish = change_pin_finish;
|
|
|
|
properties[PROP_CONNECTION] =
|
|
g_param_spec_object (MM_SIM_CONNECTION,
|
|
"Connection",
|
|
"GDBus connection to the system bus.",
|
|
G_TYPE_DBUS_CONNECTION,
|
|
G_PARAM_READWRITE);
|
|
g_object_class_install_property (object_class, PROP_CONNECTION, properties[PROP_CONNECTION]);
|
|
|
|
properties[PROP_PATH] =
|
|
g_param_spec_string (MM_SIM_PATH,
|
|
"Path",
|
|
"DBus path of the SIM",
|
|
NULL,
|
|
G_PARAM_READWRITE);
|
|
g_object_class_install_property (object_class, PROP_PATH, properties[PROP_PATH]);
|
|
|
|
properties[PROP_MODEM] =
|
|
g_param_spec_object (MM_SIM_MODEM,
|
|
"Modem",
|
|
"The Modem which owns this SIM",
|
|
MM_TYPE_BASE_MODEM,
|
|
G_PARAM_READWRITE);
|
|
g_object_class_install_property (object_class, PROP_MODEM, properties[PROP_MODEM]);
|
|
|
|
/* Signals */
|
|
signals[SIGNAL_PIN_LOCK_ENABLED] =
|
|
g_signal_new (MM_SIM_PIN_LOCK_ENABLED,
|
|
G_OBJECT_CLASS_TYPE (object_class),
|
|
G_SIGNAL_RUN_FIRST,
|
|
G_STRUCT_OFFSET (MMSimClass, pin_lock_enabled),
|
|
NULL, NULL,
|
|
mm_marshal_VOID__BOOLEAN,
|
|
G_TYPE_NONE, 1, G_TYPE_BOOLEAN);
|
|
}
|