/* -*- 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 * Copyright (C) 2022 Google Inc. */ #include #include #include #include #include #include #include #define _LIBMM_INSIDE_MM #include #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); } /*****************************************************************************/ /* 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 load_operator_identifier_ready (MbimDevice *device, GAsyncResult *res, GTask *task) { MbimMessage *response; GError *error = NULL; MbimProvider *provider; response = mbim_device_command_finish (device, res, &error); if (response && mbim_message_response_get_result (response, MBIM_MESSAGE_TYPE_COMMAND_DONE, &error) && mbim_message_home_provider_response_parse ( response, &provider, &error)) { g_task_return_pointer (task, g_strdup (provider->provider_id), g_free); mbim_provider_free (provider); } else g_task_return_error (task, error); g_object_unref (task); if (response) mbim_message_unref (response); } static void load_operator_identifier (MMBaseSim *self, GAsyncReadyCallback callback, gpointer user_data) { MbimDevice *device; MbimMessage *message; GTask *task; if (!peek_device (self, &device, callback, user_data)) return; task = g_task_new (self, NULL, callback, user_data); message = mbim_message_home_provider_query_new (NULL); mbim_device_command (device, message, 30, NULL, (GAsyncReadyCallback)load_operator_identifier_ready, task); mbim_message_unref (message); } /*****************************************************************************/ /* 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 load_operator_name_ready (MbimDevice *device, GAsyncResult *res, GTask *task) { MbimMessage *response; GError *error = NULL; MbimProvider *provider; response = mbim_device_command_finish (device, res, &error); if (response && mbim_message_response_get_result (response, MBIM_MESSAGE_TYPE_COMMAND_DONE, &error) && mbim_message_home_provider_response_parse ( response, &provider, &error)) { g_task_return_pointer (task, g_strdup (provider->provider_name), g_free); mbim_provider_free (provider); } else g_task_return_error (task, error); g_object_unref (task); if (response) mbim_message_unref (response); } static void load_operator_name (MMBaseSim *self, GAsyncReadyCallback callback, gpointer user_data) { MbimDevice *device; MbimMessage *message; GTask *task; if (!peek_device (self, &device, callback, user_data)) return; task = g_task_new (self, NULL, callback, user_data); message = mbim_message_home_provider_query_new (NULL); mbim_device_command (device, message, 30, NULL, (GAsyncReadyCallback)load_operator_name_ready, task); mbim_message_unref (message); } /*****************************************************************************/ /* 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); } /*****************************************************************************/ /* 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; }