| /* -*- 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; |
| MbimSubscriberReadyState ready_state = MBIM_SUBSCRIBER_READY_STATE_NOT_INITIALIZED; |
| |
| 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; |
| |
| 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, |
| &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); |
| } |
| |
| if (self->priv->preload_error || ready_state == MBIM_SUBSCRIBER_READY_STATE_NOT_INITIALIZED) { |
| self->priv->preload = FALSE; |
| g_clear_object (&self->priv->preload_cancellable); |
| if (self->priv->preload_error) |
| g_task_return_error (task, g_steal_pointer (&self->priv->preload_error)); |
| else |
| g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_WRONG_SIM_STATE, |
| "SIM is not ready: NotInitialized"); |
| |
| g_object_unref (task); |
| return; |
| } |
| /* 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); |
| |
| /* when preloading is successful, 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; |
| } |