1843 lines
62 KiB
C
1843 lines
62 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) 2013 Aleksander Morgado <aleksander@gnu.org>
|
|
* Copyright (C) 2022 Google Inc.
|
|
*/
|
|
|
|
#include <config.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <unistd.h>
|
|
#include <string.h>
|
|
#include <ctype.h>
|
|
|
|
#include <ModemManager.h>
|
|
#define _LIBMM_INSIDE_MM
|
|
#include <libmm-glib.h>
|
|
|
|
#include "mm-broadband-modem-mbim.h"
|
|
#include "mm-error-helpers.h"
|
|
#include "mm-iface-modem.h"
|
|
#include "mm-log-object.h"
|
|
#include "mm-modem-helpers-mbim.h"
|
|
#include "mm-sim-mbim.h"
|
|
|
|
#define MS_UICC_LOW_LEVEL_SUPPORTED_VERSION 0x01
|
|
|
|
G_DEFINE_TYPE (MMSimMbim, mm_sim_mbim, MM_TYPE_BASE_SIM)
|
|
|
|
struct _MMSimMbimPrivate {
|
|
gboolean preload;
|
|
GCancellable *preload_cancellable;
|
|
GError *preload_error;
|
|
gchar *imsi;
|
|
gchar *iccid;
|
|
GError *iccid_error;
|
|
MMSimType sim_type;
|
|
MMSimEsimStatus esim_status;
|
|
MMSimRemovability removability;
|
|
GByteArray *application_id;
|
|
GError *application_id_error;
|
|
|
|
/* need to get notified when a full sync of the info
|
|
* is needed, so that we clear the preloaded data. */
|
|
guint modem_sync_needed_id;
|
|
};
|
|
|
|
/*****************************************************************************/
|
|
|
|
static gboolean
|
|
peek_device (gpointer self,
|
|
MbimDevice **o_device,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
MMBaseModem *modem = NULL;
|
|
MMPortMbim *port;
|
|
|
|
g_object_get (G_OBJECT (self),
|
|
MM_BASE_SIM_MODEM, &modem,
|
|
NULL);
|
|
g_assert (MM_IS_BASE_MODEM (modem));
|
|
|
|
port = mm_broadband_modem_mbim_peek_port_mbim (MM_BROADBAND_MODEM_MBIM (modem));
|
|
g_object_unref (modem);
|
|
|
|
if (!port) {
|
|
g_task_report_new_error (self,
|
|
callback,
|
|
user_data,
|
|
peek_device,
|
|
MM_CORE_ERROR,
|
|
MM_CORE_ERROR_FAILED,
|
|
"Couldn't peek MBIM port");
|
|
return FALSE;
|
|
}
|
|
|
|
*o_device = mm_port_mbim_peek_device (port);
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
update_modem_unlock_retries (MMSimMbim *self,
|
|
MbimPinType pin_type,
|
|
guint32 remaining_attempts)
|
|
{
|
|
MMBaseModem *modem = NULL;
|
|
|
|
g_object_get (G_OBJECT (self),
|
|
MM_BASE_SIM_MODEM, &modem,
|
|
NULL);
|
|
g_assert (MM_IS_BASE_MODEM (modem));
|
|
|
|
mm_broadband_modem_mbim_set_unlock_retries (MM_BROADBAND_MODEM_MBIM (modem),
|
|
mm_modem_lock_from_mbim_pin_type (pin_type),
|
|
remaining_attempts);
|
|
g_object_unref (modem);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Monitor modem sync signal */
|
|
|
|
#if defined WITH_SUSPEND_RESUME
|
|
|
|
static void reset_subscriber_info (MMSimMbim *self);
|
|
|
|
static void
|
|
clear_modem_sync_monitor (MMSimMbim *self)
|
|
{
|
|
g_autoptr(MMBaseModem) modem = NULL;
|
|
|
|
if (!self->priv->modem_sync_needed_id)
|
|
return;
|
|
|
|
g_object_get (G_OBJECT (self),
|
|
MM_BASE_SIM_MODEM, &modem,
|
|
NULL);
|
|
if (g_signal_handler_is_connected (modem, self->priv->modem_sync_needed_id))
|
|
g_signal_handler_disconnect (modem, self->priv->modem_sync_needed_id);
|
|
self->priv->modem_sync_needed_id = 0;
|
|
}
|
|
|
|
static void
|
|
setup_modem_sync_monitor (MMSimMbim *self)
|
|
{
|
|
g_autoptr(MMBaseModem) modem = NULL;
|
|
|
|
if (self->priv->modem_sync_needed_id)
|
|
return;
|
|
|
|
g_object_get (G_OBJECT (self),
|
|
MM_BASE_SIM_MODEM, &modem,
|
|
NULL);
|
|
|
|
self->priv->modem_sync_needed_id = g_signal_connect_swapped (modem,
|
|
MM_BROADBAND_MODEM_SIGNAL_SYNC_NEEDED,
|
|
G_CALLBACK (reset_subscriber_info),
|
|
self);
|
|
}
|
|
|
|
#endif /* WITH_SUSPEND_RESUME */
|
|
|
|
/*****************************************************************************/
|
|
/* Preload subscriber info */
|
|
|
|
static void
|
|
reset_subscriber_info (MMSimMbim *self)
|
|
{
|
|
/* Request to stop any ongoing preload attempt */
|
|
g_cancellable_cancel (self->priv->preload_cancellable);
|
|
g_clear_object (&self->priv->preload_cancellable);
|
|
|
|
/* And reset the info right away */
|
|
self->priv->preload = FALSE;
|
|
g_clear_error (&self->priv->preload_error);
|
|
g_clear_pointer (&self->priv->imsi, g_free);
|
|
g_clear_pointer (&self->priv->iccid, g_free);
|
|
g_clear_error (&self->priv->iccid_error);
|
|
self->priv->sim_type = MM_SIM_TYPE_UNKNOWN;
|
|
self->priv->esim_status = MM_SIM_ESIM_STATUS_UNKNOWN;
|
|
self->priv->removability = MM_SIM_REMOVABILITY_UNKNOWN;
|
|
g_clear_pointer (&self->priv->application_id, g_byte_array_unref);
|
|
g_clear_error (&self->priv->application_id_error);
|
|
}
|
|
|
|
static gboolean
|
|
preload_subscriber_info_finish (MMSimMbim *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
return g_task_propagate_boolean (G_TASK (res), error);
|
|
}
|
|
|
|
static void
|
|
application_list_query_ready (MbimDevice *device,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
MMSimMbim *self;
|
|
g_autoptr(MbimMessage) response = NULL;
|
|
guint32 version;
|
|
guint32 application_count;
|
|
guint32 active_application_index;
|
|
g_autoptr(MbimUiccApplicationArray) applications = NULL;
|
|
g_autoptr(GError) error = NULL;
|
|
|
|
self = g_task_get_source_object (task);
|
|
|
|
response = mbim_device_command_finish (device, res, &error);
|
|
|
|
if (g_task_return_error_if_cancelled (task)) {
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
g_clear_pointer (&self->priv->application_id, g_byte_array_unref);
|
|
g_clear_error (&self->priv->application_id_error);
|
|
self->priv->application_id_error = g_steal_pointer (&error);
|
|
|
|
if (response &&
|
|
mbim_message_response_get_result (response, MBIM_MESSAGE_TYPE_COMMAND_DONE, &self->priv->application_id_error) &&
|
|
mbim_message_ms_uicc_low_level_access_application_list_response_parse (
|
|
response,
|
|
&version,
|
|
&application_count,
|
|
&active_application_index,
|
|
NULL, /* application_list_size_bytes */
|
|
&applications,
|
|
&self->priv->application_id_error)) {
|
|
mm_obj_dbg (self, "processed MS UICC low level access application list response");
|
|
if (version != MS_UICC_LOW_LEVEL_SUPPORTED_VERSION)
|
|
self->priv->application_id_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED,
|
|
"Application list version %u is not supported",
|
|
version);
|
|
else if (active_application_index >= application_count)
|
|
self->priv->application_id_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_INVALID_ARGS,
|
|
"Invalid active application index: %u >= %u",
|
|
active_application_index, application_count);
|
|
else
|
|
self->priv->application_id = g_byte_array_append (g_byte_array_sized_new (applications[active_application_index]->application_id_size),
|
|
applications[active_application_index]->application_id,
|
|
applications[active_application_index]->application_id_size);
|
|
}
|
|
|
|
/* At this point we just complete, as all the info and errors have already
|
|
* been stored */
|
|
g_clear_object (&self->priv->preload_cancellable);
|
|
g_task_return_boolean (task, TRUE);
|
|
g_object_unref (task);
|
|
}
|
|
|
|
static void
|
|
uicc_application_list (GTask *task,
|
|
MbimDevice *device)
|
|
{
|
|
g_autoptr(MbimMessage) message = NULL;
|
|
|
|
message = mbim_message_ms_uicc_low_level_access_application_list_query_new (NULL);
|
|
mbim_device_command (device,
|
|
message,
|
|
10,
|
|
NULL,
|
|
(GAsyncReadyCallback)application_list_query_ready,
|
|
task);
|
|
}
|
|
|
|
static void
|
|
subscriber_ready_status_ready (MbimDevice *device,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
MMSimMbim *self;
|
|
g_autoptr(MbimMessage) response = NULL;
|
|
g_autofree gchar *raw_iccid = NULL;
|
|
g_autoptr(GError) error = NULL;
|
|
|
|
self = g_task_get_source_object (task);
|
|
|
|
response = mbim_device_command_finish (device, res, &error);
|
|
|
|
if (g_task_return_error_if_cancelled (task)) {
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
g_clear_error (&self->priv->preload_error);
|
|
g_clear_pointer (&self->priv->imsi, g_free);
|
|
g_clear_pointer (&self->priv->iccid, g_free);
|
|
g_clear_error (&self->priv->iccid_error);
|
|
|
|
self->priv->preload_error = g_steal_pointer (&error);
|
|
|
|
if (response && mbim_message_response_get_result (response, MBIM_MESSAGE_TYPE_COMMAND_DONE, &self->priv->preload_error)) {
|
|
if (mbim_device_check_ms_mbimex_version (device, 3, 0)) {
|
|
MbimSubscriberReadyStatusFlag flags = MBIM_SUBSCRIBER_READY_STATUS_FLAG_NONE;
|
|
MbimSubscriberReadyState ready_state = MBIM_SUBSCRIBER_READY_STATE_NOT_INITIALIZED;
|
|
|
|
if (!mbim_message_ms_basic_connect_v3_subscriber_ready_status_response_parse (
|
|
response,
|
|
&ready_state,
|
|
&flags,
|
|
&self->priv->imsi,
|
|
&raw_iccid,
|
|
NULL, /* ready_info */
|
|
NULL, /* telephone_numbers_count */
|
|
NULL, /* telephone_numbers */
|
|
&self->priv->preload_error))
|
|
g_prefix_error (&self->priv->preload_error, "Failed processing MBIMEx v3.0 subscriber ready status response: ");
|
|
else {
|
|
mm_obj_dbg (self, "processed MBIMEx v3.0 subscriber ready status response");
|
|
|
|
/* SIM type */
|
|
if (flags & MBIM_SUBSCRIBER_READY_STATUS_FLAG_ESIM) {
|
|
self->priv->sim_type = MM_SIM_TYPE_ESIM;
|
|
/* eSIM status */
|
|
if (ready_state == MBIM_SUBSCRIBER_READY_STATE_INITIALIZED)
|
|
self->priv->esim_status = MM_SIM_ESIM_STATUS_WITH_PROFILES;
|
|
else if (ready_state == MBIM_SUBSCRIBER_READY_STATE_NO_ESIM_PROFILE)
|
|
self->priv->esim_status = MM_SIM_ESIM_STATUS_NO_PROFILES;
|
|
else
|
|
mm_obj_warn (self, "couldn't load esim status: %s", mbim_subscriber_ready_state_get_string (ready_state));
|
|
} else
|
|
self->priv->sim_type = MM_SIM_TYPE_PHYSICAL;
|
|
|
|
/* Removability */
|
|
if (flags & MBIM_SUBSCRIBER_READY_STATUS_FLAG_SIM_REMOVABILITY_KNOWN) {
|
|
if (flags & MBIM_SUBSCRIBER_READY_STATUS_FLAG_SIM_REMOVABLE)
|
|
self->priv->removability = MM_SIM_REMOVABILITY_REMOVABLE;
|
|
else
|
|
self->priv->removability = MM_SIM_REMOVABILITY_NOT_REMOVABLE;
|
|
}
|
|
}
|
|
} else {
|
|
if (!mbim_message_subscriber_ready_status_response_parse (
|
|
response,
|
|
NULL, /* ready_state */
|
|
&self->priv->imsi,
|
|
&raw_iccid,
|
|
NULL, /* ready_info */
|
|
NULL, /* telephone_numbers_count */
|
|
NULL, /* telephone_numbers */
|
|
&self->priv->preload_error))
|
|
g_prefix_error (&self->priv->preload_error, "Failed processing subscriber ready status response: ");
|
|
else
|
|
mm_obj_dbg (self, "processed subscriber ready status response");
|
|
}
|
|
|
|
if (raw_iccid)
|
|
self->priv->iccid = mm_3gpp_parse_iccid (raw_iccid, &self->priv->iccid_error);
|
|
}
|
|
|
|
/* Go on to preload next contents */
|
|
uicc_application_list (task, device);
|
|
}
|
|
|
|
static void
|
|
subscriber_ready_status (GTask *task,
|
|
MbimDevice *device)
|
|
{
|
|
g_autoptr(MbimMessage) message = NULL;
|
|
|
|
message = mbim_message_subscriber_ready_status_query_new (NULL);
|
|
mbim_device_command (device,
|
|
message,
|
|
10,
|
|
NULL,
|
|
(GAsyncReadyCallback)subscriber_ready_status_ready,
|
|
task);
|
|
}
|
|
|
|
static void
|
|
preload_subscriber_info (MMSimMbim *self,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
g_autoptr(GCancellable) cancellable = NULL;
|
|
GTask *task;
|
|
MbimDevice *device;
|
|
|
|
if (!peek_device (self, &device, callback, user_data))
|
|
return;
|
|
|
|
cancellable = g_cancellable_new ();
|
|
task = g_task_new (self, cancellable, callback, user_data);
|
|
|
|
/* only preload one single time; the info of the SIM should not
|
|
* change during runtime, unless we're handling hotplug events */
|
|
if (self->priv->preload) {
|
|
g_task_return_boolean (task, TRUE);
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
self->priv->preload = TRUE;
|
|
|
|
g_assert (!self->priv->preload_cancellable);
|
|
self->priv->preload_cancellable = g_steal_pointer (&cancellable);
|
|
|
|
#if defined WITH_SUSPEND_RESUME
|
|
|
|
/* If modem reports sync needed, we will reset the preloaded info */
|
|
setup_modem_sync_monitor (self);
|
|
|
|
#endif
|
|
|
|
subscriber_ready_status (task, device);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Load SIM identifier */
|
|
|
|
static gchar *
|
|
load_sim_identifier_finish (MMBaseSim *_self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
MMSimMbim *self = MM_SIM_MBIM (_self);
|
|
|
|
if (!preload_subscriber_info_finish (self, res, error))
|
|
return NULL;
|
|
|
|
if (self->priv->iccid_error) {
|
|
g_propagate_error (error, g_error_copy (self->priv->iccid_error));
|
|
return NULL;
|
|
}
|
|
|
|
if (self->priv->preload_error) {
|
|
g_propagate_error (error, g_error_copy (self->priv->preload_error));
|
|
return NULL;
|
|
}
|
|
|
|
if (!self->priv->iccid) {
|
|
g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "SIM iccid not available");
|
|
return NULL;
|
|
}
|
|
|
|
return g_strdup (self->priv->iccid);
|
|
}
|
|
|
|
static void
|
|
load_sim_identifier (MMBaseSim *self,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
preload_subscriber_info (MM_SIM_MBIM (self), callback, user_data);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Load IMSI */
|
|
|
|
static gchar *
|
|
load_imsi_finish (MMBaseSim *_self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
MMSimMbim *self = MM_SIM_MBIM (_self);
|
|
|
|
if (!preload_subscriber_info_finish (self, res, error))
|
|
return NULL;
|
|
|
|
if (self->priv->preload_error) {
|
|
g_propagate_error (error, g_error_copy (self->priv->preload_error));
|
|
return NULL;
|
|
}
|
|
|
|
if (!self->priv->imsi) {
|
|
g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "SIM imsi not available");
|
|
return NULL;
|
|
}
|
|
|
|
return g_strdup (self->priv->imsi);
|
|
}
|
|
|
|
static void
|
|
load_imsi (MMBaseSim *self,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
preload_subscriber_info (MM_SIM_MBIM (self), callback, user_data);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Load SIM identifier */
|
|
|
|
static MMSimType
|
|
load_sim_type_finish (MMBaseSim *_self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
MMSimMbim *self = MM_SIM_MBIM (_self);
|
|
|
|
if (!preload_subscriber_info_finish (self, res, error))
|
|
return MM_SIM_TYPE_UNKNOWN;
|
|
|
|
if (self->priv->preload_error) {
|
|
g_propagate_error (error, g_error_copy (self->priv->preload_error));
|
|
return MM_SIM_TYPE_UNKNOWN;
|
|
}
|
|
|
|
return self->priv->sim_type;
|
|
}
|
|
|
|
static void
|
|
load_sim_type (MMBaseSim *self,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
preload_subscriber_info (MM_SIM_MBIM (self), callback, user_data);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Load eSIM status */
|
|
|
|
static MMSimEsimStatus
|
|
load_esim_status_finish (MMBaseSim *_self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
MMSimMbim *self = MM_SIM_MBIM (_self);
|
|
|
|
if (!preload_subscriber_info_finish (self, res, error))
|
|
return MM_SIM_ESIM_STATUS_UNKNOWN;
|
|
|
|
if (self->priv->preload_error) {
|
|
g_propagate_error (error, g_error_copy (self->priv->preload_error));
|
|
return MM_SIM_ESIM_STATUS_UNKNOWN;
|
|
}
|
|
|
|
return self->priv->esim_status;
|
|
}
|
|
|
|
static void
|
|
load_esim_status (MMBaseSim *self,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
preload_subscriber_info (MM_SIM_MBIM (self), callback, user_data);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Load eSIM status */
|
|
|
|
static MMSimRemovability
|
|
load_removability_finish (MMBaseSim *_self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
MMSimMbim *self = MM_SIM_MBIM (_self);
|
|
|
|
if (!preload_subscriber_info_finish (self, res, error))
|
|
return MM_SIM_REMOVABILITY_UNKNOWN;
|
|
|
|
if (self->priv->preload_error) {
|
|
g_propagate_error (error, g_error_copy (self->priv->preload_error));
|
|
return MM_SIM_REMOVABILITY_UNKNOWN;
|
|
}
|
|
|
|
return self->priv->removability;
|
|
}
|
|
|
|
static void
|
|
load_removability (MMBaseSim *self,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
preload_subscriber_info (MM_SIM_MBIM (self), callback, user_data);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Load EID */
|
|
|
|
#define UICC_STATUS_OK 144
|
|
#define EID_APDU_HEADER 5
|
|
|
|
typedef enum {
|
|
ESIM_CHECK_STEP_FIRST,
|
|
ESIM_CHECK_STEP_UICC_OPEN_CHANNEL,
|
|
ESIM_CHECK_STEP_UICC_GET_APDU,
|
|
ESIM_CHECK_STEP_UICC_CLOSE_CHANNEL,
|
|
ESIM_CHECK_STEP_LAST
|
|
} EsimCheckStep;
|
|
|
|
typedef struct {
|
|
EsimCheckStep step;
|
|
guint32 channel;
|
|
guint32 channel_grp;
|
|
gchar *eid;
|
|
GError *saved_error;
|
|
} EsimCheckContext;
|
|
|
|
static void
|
|
esim_check_context_free (EsimCheckContext *ctx)
|
|
{
|
|
g_assert (!ctx->saved_error);
|
|
g_free (ctx->eid);
|
|
g_free (ctx);
|
|
}
|
|
|
|
static gchar *
|
|
load_eid_finish (MMBaseSim *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
return g_task_propagate_pointer (G_TASK (res), error);
|
|
}
|
|
|
|
static void esim_check_step (MbimDevice *device,
|
|
GTask *task);
|
|
|
|
static void
|
|
check_uicc_close_channel_ready (MbimDevice *device,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
g_autoptr(MbimMessage) response = NULL;
|
|
g_autoptr(GError) error = NULL;
|
|
guint32 status;
|
|
EsimCheckContext *ctx;
|
|
|
|
ctx = g_task_get_task_data (task);
|
|
|
|
response = mbim_device_command_finish (device, res, &error);
|
|
if (!response ||
|
|
!mbim_message_response_get_result (response, MBIM_MESSAGE_TYPE_COMMAND_DONE, &error) ||
|
|
!mbim_message_ms_uicc_low_level_access_close_channel_response_parse (response, &status, &error)) {
|
|
/* if we have a saved error, prefer that one */
|
|
if (!ctx->saved_error)
|
|
ctx->saved_error = g_steal_pointer (&error);
|
|
} else if (status != UICC_STATUS_OK) {
|
|
/* if we have a saved error, prefer that one */
|
|
if (!ctx->saved_error)
|
|
ctx->saved_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
|
|
"UICC close channel failed");
|
|
}
|
|
|
|
/* go on to next step */
|
|
ctx->step++;
|
|
esim_check_step (device, task);
|
|
}
|
|
|
|
static void
|
|
check_uicc_apdu_ready (MbimDevice *device,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
g_autoptr(MbimMessage) response = NULL;
|
|
GError *error = NULL;
|
|
guint32 status;
|
|
guint32 apdu_response_size;
|
|
const guint8 *apdu_response = NULL;
|
|
EsimCheckContext *ctx;
|
|
|
|
ctx = g_task_get_task_data (task);
|
|
|
|
response = mbim_device_command_finish (device, res, &error);
|
|
if (!response ||
|
|
!mbim_message_response_get_result (response, MBIM_MESSAGE_TYPE_COMMAND_DONE, &error) ||
|
|
!mbim_message_ms_uicc_low_level_access_apdu_response_parse (
|
|
response,
|
|
&status,
|
|
&apdu_response_size,
|
|
&apdu_response,
|
|
&error))
|
|
ctx->saved_error = error;
|
|
else {
|
|
ctx->eid = mm_decode_eid ((const gchar *)(apdu_response + EID_APDU_HEADER),
|
|
apdu_response_size - EID_APDU_HEADER);
|
|
if (!ctx->eid)
|
|
ctx->saved_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_INVALID_ARGS,
|
|
"Invalid APDU response: unable to decode EID");
|
|
}
|
|
|
|
/* always go on to the close channel step, even on error */
|
|
ctx->step++;
|
|
esim_check_step (device, task);
|
|
}
|
|
|
|
static void
|
|
check_uicc_open_channel_ready (MbimDevice *device,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
g_autoptr(MbimMessage) response = NULL;
|
|
GError *error = NULL;
|
|
guint32 status;
|
|
guint32 channel;
|
|
EsimCheckContext *ctx;
|
|
|
|
ctx = g_task_get_task_data (task);
|
|
|
|
response = mbim_device_command_finish (device, res, &error);
|
|
if (!response ||
|
|
!mbim_message_response_get_result (response, MBIM_MESSAGE_TYPE_COMMAND_DONE, &error) ||
|
|
!mbim_message_ms_uicc_low_level_access_open_channel_response_parse (
|
|
response,
|
|
&status,
|
|
&channel,
|
|
NULL,
|
|
NULL,
|
|
&error)) {
|
|
ctx->saved_error = error;
|
|
ctx->step = ESIM_CHECK_STEP_LAST;
|
|
} else if (status != UICC_STATUS_OK) {
|
|
ctx->saved_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
|
|
"UICC open channel failed");
|
|
ctx->step = ESIM_CHECK_STEP_LAST;
|
|
} else {
|
|
/* channel is open, from now on we'll need to always explicitly close,
|
|
* even on errors */
|
|
ctx->channel = channel;
|
|
ctx->step++;
|
|
}
|
|
|
|
esim_check_step (device, task);
|
|
}
|
|
|
|
static void
|
|
esim_check_step (MbimDevice *device,
|
|
GTask *task)
|
|
{
|
|
MMSimMbim *self;
|
|
EsimCheckContext *ctx;
|
|
g_autoptr(MbimMessage) message = NULL;
|
|
|
|
self = g_task_get_source_object (task);
|
|
ctx = g_task_get_task_data (task);
|
|
|
|
/* Don't run new steps if we're cancelled */
|
|
if (g_task_return_error_if_cancelled (task)) {
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
switch (ctx->step) {
|
|
case ESIM_CHECK_STEP_FIRST:
|
|
ctx->step++;
|
|
/* fall through */
|
|
|
|
case ESIM_CHECK_STEP_UICC_OPEN_CHANNEL: {
|
|
const guint8 app_id[] = {0xa0, 0x00, 0x00, 0x05, 0x59, 0x10, 0x10, 0xff,
|
|
0xff, 0xff, 0xff, 0x89, 0x00, 0x00, 0x01, 0x00};
|
|
|
|
/* Channel group is used to bundle all logical channels opened and for
|
|
* future reference to close */
|
|
ctx->channel_grp = 1;
|
|
|
|
mm_obj_dbg (self, "opening UICC channel...");
|
|
message = mbim_message_ms_uicc_low_level_access_open_channel_set_new (
|
|
sizeof (app_id),
|
|
app_id,
|
|
4, /* SelectP2Arg: Return File Control Parameters(FCP) template.
|
|
* Refer 11.1.13 of of the ETSI TS 102 221 technical specification.
|
|
*/
|
|
ctx->channel_grp,
|
|
NULL);
|
|
mbim_device_command (device,
|
|
message,
|
|
10,
|
|
NULL,
|
|
(GAsyncReadyCallback)check_uicc_open_channel_ready,
|
|
task);
|
|
return;
|
|
}
|
|
|
|
case ESIM_CHECK_STEP_UICC_GET_APDU: {
|
|
const guint8 apdu_cmd[] = {0x81, 0xe2, 0x91, 0x00, 0x06, 0xbf,
|
|
0x3e, 0x03, 0x5c, 0x01, 0x5a, 0x00};
|
|
|
|
mm_obj_dbg (self, "reading EID...");
|
|
message = mbim_message_ms_uicc_low_level_access_apdu_set_new (
|
|
ctx->channel,
|
|
MBIM_UICC_SECURE_MESSAGING_NONE,
|
|
MBIM_UICC_CLASS_BYTE_TYPE_EXTENDED,
|
|
sizeof (apdu_cmd),
|
|
apdu_cmd,
|
|
NULL);
|
|
mbim_device_command (device,
|
|
message,
|
|
10,
|
|
NULL,
|
|
(GAsyncReadyCallback)check_uicc_apdu_ready,
|
|
task);
|
|
return;
|
|
}
|
|
|
|
case ESIM_CHECK_STEP_UICC_CLOSE_CHANNEL:
|
|
mm_obj_dbg (self, "closing UICC channel...");
|
|
message = mbim_message_ms_uicc_low_level_access_close_channel_set_new (
|
|
ctx->channel,
|
|
ctx->channel_grp,
|
|
NULL);
|
|
mbim_device_command (device,
|
|
message,
|
|
10,
|
|
NULL,
|
|
(GAsyncReadyCallback)check_uicc_close_channel_ready,
|
|
task);
|
|
return;
|
|
|
|
case ESIM_CHECK_STEP_LAST:
|
|
if (ctx->saved_error)
|
|
g_task_return_error (task, g_steal_pointer (&ctx->saved_error));
|
|
else if (ctx->eid)
|
|
g_task_return_pointer (task, g_steal_pointer (&ctx->eid), g_free);
|
|
else
|
|
g_assert_not_reached ();
|
|
g_object_unref (task);
|
|
return;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
g_assert_not_reached ();
|
|
}
|
|
|
|
static void
|
|
load_eid (MMBaseSim *self,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
MbimDevice *device;
|
|
GTask *task;
|
|
EsimCheckContext *ctx;
|
|
|
|
if (!peek_device (self, &device, callback, user_data))
|
|
return;
|
|
|
|
task = g_task_new (self, NULL, callback, user_data);
|
|
|
|
ctx = g_new0 (EsimCheckContext, 1);
|
|
ctx->step = ESIM_CHECK_STEP_FIRST;
|
|
g_task_set_task_data (task, ctx, (GDestroyNotify)esim_check_context_free);
|
|
esim_check_step (device, task);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Common method to read transparent files */
|
|
|
|
typedef struct {
|
|
MbimDevice *device;
|
|
GByteArray *file_path;
|
|
} CommonReadBinaryContext;
|
|
|
|
static void
|
|
common_read_binary_context_free (CommonReadBinaryContext *ctx)
|
|
{
|
|
g_byte_array_unref (ctx->file_path);
|
|
g_object_unref (ctx->device);
|
|
g_slice_free (CommonReadBinaryContext, ctx);
|
|
}
|
|
|
|
static GByteArray *
|
|
common_read_binary_finish (MMSimMbim *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
return g_task_propagate_pointer (G_TASK (res), error);
|
|
}
|
|
|
|
static void
|
|
read_binary_query_ready (MbimDevice *device,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
g_autoptr(MbimMessage) response = NULL;
|
|
GError *error = NULL;
|
|
const guint8 *data;
|
|
guint32 data_size;
|
|
|
|
response = mbim_device_command_finish (device, res, &error);
|
|
if (!response ||
|
|
!mbim_message_response_get_result (response, MBIM_MESSAGE_TYPE_COMMAND_DONE, &error) ||
|
|
!mbim_message_ms_uicc_low_level_access_read_binary_response_parse (
|
|
response,
|
|
NULL, /* version */
|
|
NULL, /* status_word_1 */
|
|
NULL, /* status_word_2 */
|
|
&data_size,
|
|
&data,
|
|
&error))
|
|
g_task_return_error (task, error);
|
|
else if (!data_size || !data)
|
|
g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
|
|
"data not available");
|
|
else
|
|
g_task_return_pointer (task,
|
|
g_byte_array_append (g_byte_array_sized_new (data_size), data, data_size),
|
|
(GDestroyNotify)g_byte_array_unref);
|
|
g_object_unref (task);
|
|
}
|
|
|
|
static void
|
|
file_status_query_ready (MbimDevice *device,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
g_autoptr(MbimMessage) request = NULL;
|
|
g_autoptr(MbimMessage) response = NULL;
|
|
MMSimMbim *self;
|
|
CommonReadBinaryContext *ctx;
|
|
GError *error = NULL;
|
|
guint32 file_item_count;
|
|
guint32 file_item_size;
|
|
guint64 read_size;
|
|
|
|
self = g_task_get_source_object (task);
|
|
ctx = (CommonReadBinaryContext *) g_task_get_task_data (task);
|
|
|
|
response = mbim_device_command_finish (device, res, &error);
|
|
if (!response ||
|
|
!mbim_message_response_get_result (response, MBIM_MESSAGE_TYPE_COMMAND_DONE, &error) ||
|
|
!mbim_message_ms_uicc_low_level_access_file_status_response_parse (
|
|
response,
|
|
NULL, /* version */
|
|
NULL, /* status_word_1 */
|
|
NULL, /* status_word_2 */
|
|
NULL, /* file_accessibility */
|
|
NULL, /* file_type */
|
|
NULL, /* file_structure */
|
|
&file_item_count,
|
|
&file_item_size,
|
|
NULL, /* access_condition_read */
|
|
NULL, /* access_condition_update */
|
|
NULL, /* access_condition_activate */
|
|
NULL, /* access_condition_deactivate */
|
|
&error)) {
|
|
g_task_return_error (task, error);
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
if (!file_item_size || !file_item_count) {
|
|
g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
|
|
"file contents not available");
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
/* Fail if we attempt to read too much. UICC operations can read up to
|
|
* 255 bytes at a time, and the SIM files we try to process are all
|
|
* (or at least should be) small. Use a 64bit value to avoid overflowing
|
|
* if the modem returns weird size/count values. */
|
|
read_size = file_item_size * file_item_count;
|
|
if (read_size > 255) {
|
|
g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
|
|
"file size too big: item size %u, item count %u",
|
|
file_item_size, file_item_count);
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
request = mbim_message_ms_uicc_low_level_access_read_binary_query_new (MS_UICC_LOW_LEVEL_SUPPORTED_VERSION,
|
|
self->priv->application_id->len,
|
|
self->priv->application_id->data,
|
|
ctx->file_path->len,
|
|
ctx->file_path->data,
|
|
0, /* read_offset */
|
|
(guint32)read_size,
|
|
NULL, /* local_pin */
|
|
0, /* data_size */
|
|
NULL, /* data */
|
|
NULL);
|
|
mbim_device_command (ctx->device,
|
|
request,
|
|
10,
|
|
NULL,
|
|
(GAsyncReadyCallback)read_binary_query_ready,
|
|
task);
|
|
}
|
|
|
|
static void
|
|
read_binary_subscriber_info_ready (MMSimMbim *self,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
g_autoptr(MbimMessage) request = NULL;
|
|
CommonReadBinaryContext *ctx;
|
|
GError *error = NULL;
|
|
|
|
ctx = (CommonReadBinaryContext *) g_task_get_task_data (task);
|
|
|
|
if (!preload_subscriber_info_finish (self, res, &error)) {
|
|
g_task_return_error (task, error);
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
if (self->priv->application_id_error) {
|
|
g_task_return_error (task, g_error_copy (self->priv->application_id_error));
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
if (!self->priv->application_id) {
|
|
g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
|
|
"UICC application id not available");
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
request = mbim_message_ms_uicc_low_level_access_file_status_query_new (MS_UICC_LOW_LEVEL_SUPPORTED_VERSION,
|
|
self->priv->application_id->len,
|
|
self->priv->application_id->data,
|
|
ctx->file_path->len,
|
|
ctx->file_path->data,
|
|
NULL);
|
|
|
|
mbim_device_command (ctx->device,
|
|
request,
|
|
10,
|
|
NULL,
|
|
(GAsyncReadyCallback)file_status_query_ready,
|
|
task);
|
|
}
|
|
|
|
static void
|
|
common_read_binary (MMSimMbim *self,
|
|
const guint8 *file_path,
|
|
gsize file_path_size,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
GTask *task;
|
|
MbimDevice *device;
|
|
CommonReadBinaryContext *ctx;
|
|
|
|
if (!peek_device (self, &device, callback, user_data))
|
|
return;
|
|
|
|
task = g_task_new (self, NULL, callback, user_data);
|
|
ctx = g_slice_new0 (CommonReadBinaryContext);
|
|
ctx->device = g_object_ref (device);
|
|
ctx->file_path = g_byte_array_append (g_byte_array_sized_new (file_path_size), file_path, file_path_size);
|
|
g_task_set_task_data (task, ctx, (GDestroyNotify) common_read_binary_context_free);
|
|
|
|
preload_subscriber_info (self,
|
|
(GAsyncReadyCallback) read_binary_subscriber_info_ready,
|
|
task);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Load operator identifier */
|
|
|
|
static gchar *
|
|
load_operator_identifier_finish (MMBaseSim *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
return g_task_propagate_pointer (G_TASK (res), error);
|
|
}
|
|
|
|
static void
|
|
parent_load_operator_id_ready (MMBaseSim *self,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
g_autoptr(GError) error = NULL;
|
|
gchar *value;
|
|
|
|
value = MM_BASE_SIM_CLASS (mm_sim_mbim_parent_class)->load_operator_identifier_finish (self, res, &error);
|
|
if (value) {
|
|
g_task_return_pointer (task, value, g_free);
|
|
} else {
|
|
mm_obj_dbg (self, "failed reading operator ID using AT: %s", error->message);
|
|
g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
|
|
"Failed reading operator ID from SIM card");
|
|
}
|
|
|
|
g_object_unref (task);
|
|
}
|
|
|
|
static void
|
|
common_read_binary_operator_id_ready (MMSimMbim *self,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
g_autoptr(GError) error = NULL;
|
|
GByteArray *value;
|
|
|
|
value = common_read_binary_finish (self, res, &error);
|
|
if (value) {
|
|
guint mnc_len = mm_sim_validate_mnc_length (value->data, value->len, &error);;
|
|
if (mnc_len) {
|
|
g_task_return_pointer (task, g_strndup (self->priv->imsi, 3 + mnc_len), g_free);
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
}
|
|
mm_obj_dbg (self, "failed reading operator ID using MBIM: %s", error->message);
|
|
|
|
/* Fallback to parent implementation if possible */
|
|
MM_BASE_SIM_CLASS (mm_sim_mbim_parent_class)->load_operator_identifier (MM_BASE_SIM (self),
|
|
(GAsyncReadyCallback)parent_load_operator_id_ready,
|
|
task);
|
|
}
|
|
|
|
static void
|
|
load_operator_identifier (MMBaseSim *self,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
GTask *task;
|
|
const guint8 file_path[] = { 0x7F, 0xFF, 0x6F, 0xAD };
|
|
|
|
task = g_task_new (self, NULL, callback, user_data);
|
|
|
|
common_read_binary (MM_SIM_MBIM (self),
|
|
file_path,
|
|
G_N_ELEMENTS (file_path),
|
|
(GAsyncReadyCallback)common_read_binary_operator_id_ready,
|
|
task);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Load operator name */
|
|
|
|
static gchar *
|
|
load_operator_name_finish (MMBaseSim *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
return g_task_propagate_pointer (G_TASK (res), error);
|
|
}
|
|
|
|
static void
|
|
parent_load_operator_name_ready (MMBaseSim *self,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
g_autoptr(GError) error = NULL;
|
|
gchar *value;
|
|
|
|
value = MM_BASE_SIM_CLASS (mm_sim_mbim_parent_class)->load_operator_name_finish (self, res, &error);
|
|
if (value) {
|
|
g_task_return_pointer (task, value, g_free);
|
|
} else {
|
|
mm_obj_dbg (self, "failed reading operator name using AT: %s", error->message);
|
|
g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
|
|
"Failed reading operator name from SIM card");
|
|
}
|
|
|
|
g_object_unref (task);
|
|
}
|
|
|
|
static void
|
|
common_read_binary_operator_name_ready (MMSimMbim *self,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
g_autoptr(GError) error = NULL;
|
|
GByteArray *value;
|
|
|
|
value = common_read_binary_finish (self, res, &error);
|
|
if (value) {
|
|
gchar *name = mm_sim_convert_spn_to_utf8 (value->data, value->len, &error);
|
|
if (name) {
|
|
g_task_return_pointer (task, name, g_free);
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
}
|
|
mm_obj_dbg (self, "failed reading operator name using MBIM: %s", error->message);
|
|
|
|
/* Fallback to parent implementation if possible */
|
|
MM_BASE_SIM_CLASS (mm_sim_mbim_parent_class)->load_operator_name (MM_BASE_SIM (self),
|
|
(GAsyncReadyCallback)parent_load_operator_name_ready,
|
|
task);
|
|
}
|
|
|
|
static void
|
|
load_operator_name (MMBaseSim *self,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
GTask *task;
|
|
const guint8 file_path[] = { 0x7F, 0xFF, 0x6F, 0x46 };
|
|
|
|
task = g_task_new (self, NULL, callback, user_data);
|
|
|
|
common_read_binary (MM_SIM_MBIM (self),
|
|
file_path,
|
|
G_N_ELEMENTS (file_path),
|
|
(GAsyncReadyCallback)common_read_binary_operator_name_ready,
|
|
task);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Read GID1 */
|
|
|
|
static GByteArray *
|
|
load_gid1_finish (MMBaseSim *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
return g_task_propagate_pointer (G_TASK (res), error);
|
|
}
|
|
|
|
static void
|
|
parent_load_gid1_ready (MMBaseSim *self,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
g_autoptr(GError) error = NULL;
|
|
GByteArray *value;
|
|
|
|
value = MM_BASE_SIM_CLASS(mm_sim_mbim_parent_class)->load_gid1_finish (self, res, &error);
|
|
if (value) {
|
|
g_task_return_pointer (task, value, (GDestroyNotify)g_byte_array_unref);
|
|
} else {
|
|
mm_obj_dbg (self, "failed reading GID1 using AT: %s", error->message);
|
|
g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
|
|
"Failed reading GID1 from SIM card");
|
|
}
|
|
|
|
g_object_unref (task);
|
|
}
|
|
|
|
static void
|
|
common_read_binary_gid1_ready (MMSimMbim *self,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
g_autoptr(GError) error = NULL;
|
|
GByteArray *value;
|
|
|
|
value = common_read_binary_finish (self, res, &error);
|
|
if (value) {
|
|
g_task_return_pointer (task, value, (GDestroyNotify)g_byte_array_unref);
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
/* Fallback to parent implementation if possible */
|
|
mm_obj_dbg (self, "failed reading GID1 using MBIM: %s", error->message);
|
|
MM_BASE_SIM_CLASS(mm_sim_mbim_parent_class)->load_gid1 (MM_BASE_SIM (self),
|
|
(GAsyncReadyCallback)parent_load_gid1_ready,
|
|
task);
|
|
}
|
|
|
|
static void
|
|
load_gid1 (MMBaseSim *self,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
GTask *task;
|
|
const guint8 file_path[] = { 0x7F, 0xFF, 0x6F, 0x3E };
|
|
|
|
task = g_task_new (self, NULL, callback, user_data);
|
|
|
|
common_read_binary (MM_SIM_MBIM (self),
|
|
file_path,
|
|
G_N_ELEMENTS (file_path),
|
|
(GAsyncReadyCallback)common_read_binary_gid1_ready,
|
|
task);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Read GID2 */
|
|
|
|
static GByteArray *
|
|
load_gid2_finish (MMBaseSim *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
return g_task_propagate_pointer (G_TASK (res), error);
|
|
}
|
|
|
|
static void
|
|
parent_load_gid2_ready (MMBaseSim *self,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
g_autoptr(GError) error = NULL;
|
|
GByteArray *value;
|
|
|
|
value = MM_BASE_SIM_CLASS(mm_sim_mbim_parent_class)->load_gid2_finish (self, res, &error);
|
|
if (value) {
|
|
g_task_return_pointer (task, value, (GDestroyNotify)g_byte_array_unref);
|
|
} else {
|
|
mm_obj_dbg (self, "failed reading GID2 using AT: %s", error->message);
|
|
g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
|
|
"Failed reading GID2 from SIM card");
|
|
}
|
|
|
|
g_object_unref (task);
|
|
}
|
|
|
|
static void
|
|
common_read_binary_gid2_ready (MMSimMbim *self,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
g_autoptr(GError) error = NULL;
|
|
GByteArray *value;
|
|
|
|
value = common_read_binary_finish (self, res, &error);
|
|
if (value) {
|
|
g_task_return_pointer (task, value, (GDestroyNotify)g_byte_array_unref);
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
/* Fallback to parent implementation if possible */
|
|
mm_obj_dbg (self, "failed reading GID2 using MBIM: %s", error->message);
|
|
MM_BASE_SIM_CLASS(mm_sim_mbim_parent_class)->load_gid2 (MM_BASE_SIM (self),
|
|
(GAsyncReadyCallback)parent_load_gid2_ready,
|
|
task);
|
|
}
|
|
|
|
static void
|
|
load_gid2 (MMBaseSim *self,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
GTask *task;
|
|
const guint8 file_path[] = { 0x7F, 0xFF, 0x6F, 0x3F };
|
|
|
|
task = g_task_new (self, NULL, callback, user_data);
|
|
|
|
common_read_binary (MM_SIM_MBIM (self),
|
|
file_path,
|
|
G_N_ELEMENTS (file_path),
|
|
(GAsyncReadyCallback)common_read_binary_gid2_ready,
|
|
task);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Send PIN */
|
|
|
|
static gboolean
|
|
send_pin_finish (MMBaseSim *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
return g_task_propagate_boolean (G_TASK (res), error);
|
|
}
|
|
|
|
static void
|
|
pin_set_enter_ready (MbimDevice *device,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
MMSimMbim *self;
|
|
GError *error = NULL;
|
|
MbimMessage *response;
|
|
gboolean success;
|
|
MbimPinType pin_type;
|
|
MbimPinState pin_state;
|
|
guint32 remaining_attempts;
|
|
|
|
self = g_task_get_source_object (task);
|
|
|
|
response = mbim_device_command_finish (device, res, &error);
|
|
if (response) {
|
|
success = mbim_message_response_get_result (response, MBIM_MESSAGE_TYPE_COMMAND_DONE, &error);
|
|
|
|
if (mbim_message_pin_response_parse (response,
|
|
&pin_type,
|
|
&pin_state,
|
|
&remaining_attempts,
|
|
NULL)) {
|
|
update_modem_unlock_retries (self, pin_type, remaining_attempts);
|
|
|
|
if (!success) {
|
|
/* Sending PIN failed, build a better error to report */
|
|
if (pin_type == MBIM_PIN_TYPE_PIN1 && pin_state == MBIM_PIN_STATE_LOCKED) {
|
|
g_error_free (error);
|
|
error = mm_mobile_equipment_error_for_code (MM_MOBILE_EQUIPMENT_ERROR_INCORRECT_PASSWORD, self);
|
|
} else if (pin_type == MBIM_PIN_TYPE_PUK1 && pin_state == MBIM_PIN_STATE_LOCKED) {
|
|
g_error_free (error);
|
|
error = mm_mobile_equipment_error_for_code (MM_MOBILE_EQUIPMENT_ERROR_SIM_PUK, self);
|
|
}
|
|
}
|
|
}
|
|
|
|
mbim_message_unref (response);
|
|
}
|
|
|
|
if (error) {
|
|
g_task_return_error (task, error);
|
|
} else {
|
|
g_task_return_boolean (task, TRUE);
|
|
|
|
/* Reset cached SIM subscriber info */
|
|
reset_subscriber_info (self);
|
|
}
|
|
|
|
g_object_unref (task);
|
|
}
|
|
|
|
static void
|
|
send_pin (MMBaseSim *self,
|
|
const gchar *pin,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
MbimDevice *device;
|
|
MbimMessage *message;
|
|
GTask *task;
|
|
GError *error = NULL;
|
|
|
|
if (!peek_device (self, &device, callback, user_data))
|
|
return;
|
|
|
|
task = g_task_new (self, NULL, callback, user_data);
|
|
|
|
mm_obj_dbg (self, "sending PIN...");
|
|
message = (mbim_message_pin_set_new (
|
|
MBIM_PIN_TYPE_PIN1,
|
|
MBIM_PIN_OPERATION_ENTER,
|
|
pin,
|
|
"",
|
|
&error));
|
|
if (!message) {
|
|
g_task_return_error (task, error);
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
mbim_device_command (device,
|
|
message,
|
|
10,
|
|
NULL,
|
|
(GAsyncReadyCallback)pin_set_enter_ready,
|
|
task);
|
|
mbim_message_unref (message);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Send PUK */
|
|
|
|
static gboolean
|
|
send_puk_finish (MMBaseSim *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
return g_task_propagate_boolean (G_TASK (res), error);
|
|
}
|
|
|
|
static void
|
|
puk_set_enter_ready (MbimDevice *device,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
MMSimMbim *self;
|
|
GError *error = NULL;
|
|
MbimMessage *response;
|
|
gboolean success;
|
|
MbimPinType pin_type;
|
|
MbimPinState pin_state;
|
|
guint32 remaining_attempts;
|
|
|
|
self = g_task_get_source_object (task);
|
|
|
|
response = mbim_device_command_finish (device, res, &error);
|
|
if (response) {
|
|
success = mbim_message_response_get_result (response, MBIM_MESSAGE_TYPE_COMMAND_DONE, &error);
|
|
|
|
if (mbim_message_pin_response_parse (response,
|
|
&pin_type,
|
|
&pin_state,
|
|
&remaining_attempts,
|
|
NULL)) {
|
|
update_modem_unlock_retries (self, pin_type, remaining_attempts);
|
|
|
|
if (!success) {
|
|
/* Sending PUK failed, build a better error to report */
|
|
if (pin_type == MBIM_PIN_TYPE_PUK1 && pin_state == MBIM_PIN_STATE_LOCKED) {
|
|
g_error_free (error);
|
|
if (remaining_attempts == 0)
|
|
error = mm_mobile_equipment_error_for_code (MM_MOBILE_EQUIPMENT_ERROR_SIM_WRONG, self);
|
|
else
|
|
error = mm_mobile_equipment_error_for_code (MM_MOBILE_EQUIPMENT_ERROR_INCORRECT_PASSWORD, self);
|
|
}
|
|
}
|
|
}
|
|
|
|
mbim_message_unref (response);
|
|
}
|
|
|
|
if (error) {
|
|
g_task_return_error (task, error);
|
|
} else {
|
|
g_task_return_boolean (task, TRUE);
|
|
|
|
/* Reset cached SIM subscriber info */
|
|
reset_subscriber_info (self);
|
|
}
|
|
|
|
g_object_unref (task);
|
|
}
|
|
|
|
static void
|
|
send_puk (MMBaseSim *self,
|
|
const gchar *puk,
|
|
const gchar *new_pin,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
MbimDevice *device;
|
|
MbimMessage *message;
|
|
GTask *task;
|
|
GError *error = NULL;
|
|
|
|
if (!peek_device (self, &device, callback, user_data))
|
|
return;
|
|
|
|
task = g_task_new (self, NULL, callback, user_data);
|
|
|
|
mm_obj_dbg (self, "sending PUK...");
|
|
message = (mbim_message_pin_set_new (
|
|
MBIM_PIN_TYPE_PUK1,
|
|
MBIM_PIN_OPERATION_ENTER,
|
|
puk,
|
|
new_pin,
|
|
&error));
|
|
if (!message) {
|
|
g_task_return_error (task, error);
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
mbim_device_command (device,
|
|
message,
|
|
10,
|
|
NULL,
|
|
(GAsyncReadyCallback)puk_set_enter_ready,
|
|
task);
|
|
mbim_message_unref (message);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Enable PIN */
|
|
|
|
static gboolean
|
|
enable_pin_finish (MMBaseSim *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
return g_task_propagate_boolean (G_TASK (res), error);
|
|
}
|
|
|
|
static void
|
|
pin_set_enable_ready (MbimDevice *device,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
MMSimMbim *self;
|
|
GError *error = NULL;
|
|
MbimMessage *response;
|
|
MbimPinType pin_type;
|
|
guint32 remaining_attempts;
|
|
|
|
self = g_task_get_source_object (task);
|
|
|
|
response = mbim_device_command_finish (device, res, &error);
|
|
if (response) {
|
|
mbim_message_response_get_result (response, MBIM_MESSAGE_TYPE_COMMAND_DONE, &error);
|
|
|
|
if (mbim_message_pin_response_parse (response,
|
|
&pin_type,
|
|
NULL,
|
|
&remaining_attempts,
|
|
NULL))
|
|
update_modem_unlock_retries (self, pin_type, remaining_attempts);
|
|
|
|
mbim_message_unref (response);
|
|
}
|
|
|
|
if (error) {
|
|
if (g_error_matches (error, MBIM_STATUS_ERROR, MBIM_STATUS_ERROR_PIN_REQUIRED)) {
|
|
g_error_free (error);
|
|
error = g_error_new (MM_CORE_ERROR,
|
|
MM_CORE_ERROR_WRONG_STATE,
|
|
"Need to be unlocked to allow enabling/disabling PIN");
|
|
}
|
|
|
|
g_task_return_error (task, error);
|
|
} else
|
|
g_task_return_boolean (task, TRUE);
|
|
g_object_unref (task);
|
|
}
|
|
|
|
static void
|
|
enable_pin (MMBaseSim *self,
|
|
const gchar *pin,
|
|
gboolean enabled,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
MbimDevice *device;
|
|
MbimMessage *message;
|
|
GTask *task;
|
|
GError *error = NULL;
|
|
|
|
if (!peek_device (self, &device, callback, user_data))
|
|
return;
|
|
|
|
task = g_task_new (self, NULL, callback, user_data);
|
|
|
|
mm_obj_dbg (self, "%s PIN ...", enabled ? "enabling" : "disabling");
|
|
message = (mbim_message_pin_set_new (
|
|
MBIM_PIN_TYPE_PIN1,
|
|
enabled ? MBIM_PIN_OPERATION_ENABLE : MBIM_PIN_OPERATION_DISABLE,
|
|
pin,
|
|
"",
|
|
&error));
|
|
if (!message) {
|
|
g_task_return_error (task, error);
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
mbim_device_command (device,
|
|
message,
|
|
10,
|
|
NULL,
|
|
(GAsyncReadyCallback)pin_set_enable_ready,
|
|
task);
|
|
mbim_message_unref (message);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Change PIN */
|
|
|
|
static gboolean
|
|
change_pin_finish (MMBaseSim *self,
|
|
GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
return g_task_propagate_boolean (G_TASK (res), error);
|
|
}
|
|
|
|
static void
|
|
pin_set_change_ready (MbimDevice *device,
|
|
GAsyncResult *res,
|
|
GTask *task)
|
|
{
|
|
MMSimMbim *self;
|
|
GError *error = NULL;
|
|
MbimMessage *response;
|
|
MbimPinType pin_type;
|
|
guint32 remaining_attempts;
|
|
|
|
self = g_task_get_source_object (task);
|
|
|
|
response = mbim_device_command_finish (device, res, &error);
|
|
if (response) {
|
|
mbim_message_response_get_result (response, MBIM_MESSAGE_TYPE_COMMAND_DONE, &error);
|
|
|
|
if (mbim_message_pin_response_parse (response,
|
|
&pin_type,
|
|
NULL,
|
|
&remaining_attempts,
|
|
NULL))
|
|
update_modem_unlock_retries (self, pin_type, remaining_attempts);
|
|
|
|
mbim_message_unref (response);
|
|
}
|
|
|
|
if (error) {
|
|
if (g_error_matches (error, MBIM_STATUS_ERROR, MBIM_STATUS_ERROR_PIN_REQUIRED)) {
|
|
g_error_free (error);
|
|
error = g_error_new (MM_CORE_ERROR,
|
|
MM_CORE_ERROR_WRONG_STATE,
|
|
"Need to be unlocked to allow changing PIN");
|
|
}
|
|
|
|
g_task_return_error (task, error);
|
|
} else
|
|
g_task_return_boolean (task, TRUE);
|
|
g_object_unref (task);
|
|
}
|
|
|
|
static void
|
|
change_pin (MMBaseSim *self,
|
|
const gchar *old_pin,
|
|
const gchar *new_pin,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
MbimDevice *device;
|
|
MbimMessage *message;
|
|
GTask *task;
|
|
GError *error = NULL;
|
|
|
|
if (!peek_device (self, &device, callback, user_data))
|
|
return;
|
|
|
|
task = g_task_new (self, NULL, callback, user_data);
|
|
|
|
mm_obj_dbg (self, "changing PIN...");
|
|
message = (mbim_message_pin_set_new (
|
|
MBIM_PIN_TYPE_PIN1,
|
|
MBIM_PIN_OPERATION_CHANGE,
|
|
old_pin,
|
|
new_pin,
|
|
&error));
|
|
if (!message) {
|
|
g_task_return_error (task, error);
|
|
g_object_unref (task);
|
|
return;
|
|
}
|
|
|
|
mbim_device_command (device,
|
|
message,
|
|
10,
|
|
NULL,
|
|
(GAsyncReadyCallback)pin_set_change_ready,
|
|
task);
|
|
mbim_message_unref (message);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
MMBaseSim *
|
|
mm_sim_mbim_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_base_sim_export (MM_BASE_SIM (sim));
|
|
|
|
return MM_BASE_SIM (sim);
|
|
}
|
|
|
|
void
|
|
mm_sim_mbim_new (MMBaseModem *modem,
|
|
GCancellable *cancellable,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
g_async_initable_new_async (MM_TYPE_SIM_MBIM,
|
|
G_PRIORITY_DEFAULT,
|
|
cancellable,
|
|
callback,
|
|
user_data,
|
|
MM_BASE_SIM_MODEM, modem,
|
|
"active", TRUE, /* by default always active */
|
|
NULL);
|
|
}
|
|
|
|
MMBaseSim *
|
|
mm_sim_mbim_new_initialized (MMBaseModem *modem,
|
|
guint slot_number,
|
|
gboolean active,
|
|
MMSimType sim_type,
|
|
MMSimEsimStatus esim_status,
|
|
const gchar *sim_identifier,
|
|
const gchar *imsi,
|
|
const gchar *eid,
|
|
const gchar *operator_identifier,
|
|
const gchar *operator_name,
|
|
const GStrv emergency_numbers)
|
|
{
|
|
MMBaseSim *sim;
|
|
|
|
sim = MM_BASE_SIM (g_object_new (MM_TYPE_SIM_MBIM,
|
|
MM_BASE_SIM_MODEM, modem,
|
|
MM_BASE_SIM_SLOT_NUMBER, slot_number,
|
|
"active", active,
|
|
"sim-type", sim_type,
|
|
"esim-status", esim_status,
|
|
"sim-identifier", sim_identifier,
|
|
"eid", eid,
|
|
"operator-identifier", operator_identifier,
|
|
"operator-name", operator_name,
|
|
"emergency-numbers", emergency_numbers,
|
|
NULL));
|
|
|
|
mm_base_sim_export (sim);
|
|
return sim;
|
|
}
|
|
|
|
static void
|
|
mm_sim_mbim_init (MMSimMbim *self)
|
|
{
|
|
/* Initialize private data */
|
|
self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
|
|
MM_TYPE_SIM_MBIM,
|
|
MMSimMbimPrivate);
|
|
reset_subscriber_info (self);
|
|
}
|
|
|
|
static void
|
|
finalize (GObject *object)
|
|
{
|
|
MMSimMbim *self = MM_SIM_MBIM (object);
|
|
|
|
reset_subscriber_info (self);
|
|
|
|
G_OBJECT_CLASS (mm_sim_mbim_parent_class)->finalize (object);
|
|
}
|
|
|
|
static void
|
|
dispose (GObject *object)
|
|
{
|
|
#if defined WITH_SUSPEND_RESUME
|
|
MMSimMbim *self = MM_SIM_MBIM (object);
|
|
|
|
clear_modem_sync_monitor (self);
|
|
#endif
|
|
G_OBJECT_CLASS (mm_sim_mbim_parent_class)->dispose (object);
|
|
}
|
|
|
|
static void
|
|
mm_sim_mbim_class_init (MMSimMbimClass *klass)
|
|
{
|
|
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
|
MMBaseSimClass *base_sim_class = MM_BASE_SIM_CLASS (klass);
|
|
|
|
g_type_class_add_private (object_class, sizeof (MMSimMbimPrivate));
|
|
|
|
object_class->finalize = finalize;
|
|
object_class->dispose = dispose;
|
|
base_sim_class->load_sim_identifier = load_sim_identifier;
|
|
base_sim_class->load_sim_identifier_finish = load_sim_identifier_finish;
|
|
base_sim_class->load_imsi = load_imsi;
|
|
base_sim_class->load_imsi_finish = load_imsi_finish;
|
|
base_sim_class->load_eid = load_eid;
|
|
base_sim_class->load_eid_finish = load_eid_finish;
|
|
base_sim_class->load_operator_identifier = load_operator_identifier;
|
|
base_sim_class->load_operator_identifier_finish = load_operator_identifier_finish;
|
|
base_sim_class->load_operator_name = load_operator_name;
|
|
base_sim_class->load_operator_name_finish = load_operator_name_finish;
|
|
base_sim_class->load_sim_type = load_sim_type;
|
|
base_sim_class->load_sim_type_finish = load_sim_type_finish;
|
|
base_sim_class->load_esim_status = load_esim_status;
|
|
base_sim_class->load_esim_status_finish = load_esim_status_finish;
|
|
base_sim_class->load_removability = load_removability;
|
|
base_sim_class->load_removability_finish = load_removability_finish;
|
|
base_sim_class->load_gid1 = load_gid1;
|
|
base_sim_class->load_gid1_finish = load_gid1_finish;
|
|
base_sim_class->load_gid2 = load_gid2;
|
|
base_sim_class->load_gid2_finish = load_gid2_finish;
|
|
base_sim_class->send_pin = send_pin;
|
|
base_sim_class->send_pin_finish = send_pin_finish;
|
|
base_sim_class->send_puk = send_puk;
|
|
base_sim_class->send_puk_finish = send_puk_finish;
|
|
base_sim_class->enable_pin = enable_pin;
|
|
base_sim_class->enable_pin_finish = enable_pin_finish;
|
|
base_sim_class->change_pin = change_pin;
|
|
base_sim_class->change_pin_finish = change_pin_finish;
|
|
}
|