| /* -*- 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) 2012 Google, Inc. |
| * Copyright (C) 2016 Aleksander Morgado <aleksander@aleksander.es> |
| * Copyright (c) 2022 Qualcomm Innovation Center, 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-qmi.h" |
| #include "mm-log-object.h" |
| #include "mm-sim-qmi.h" |
| #include "mm-modem-helpers-qmi.h" |
| #include "mm-shared-qmi.h" |
| #include "mm-bind.h" |
| |
| G_DEFINE_TYPE (MMSimQmi, mm_sim_qmi, MM_TYPE_BASE_SIM) |
| |
| enum { |
| PROP_0, |
| PROP_DMS_UIM_DEPRECATED, |
| PROP_LAST |
| }; |
| |
| static GParamSpec *properties[PROP_LAST]; |
| |
| struct _MMSimQmiPrivate { |
| gboolean dms_uim_deprecated; |
| gchar *imsi; |
| }; |
| |
| static const guint16 mf_file_path[] = { 0x3F00 }; |
| static const guint16 adf_file_path[] = { 0x3F00, 0x7FFF }; |
| |
| /*****************************************************************************/ |
| |
| static gboolean |
| ensure_qmi_client (GTask *task, |
| MMSimQmi *self, |
| QmiService service, |
| QmiClient **o_client) |
| { |
| MMBaseModem *modem = NULL; |
| QmiClient *client; |
| g_autoptr(GError) error = NULL; |
| |
| g_object_get (self, |
| MM_BASE_SIM_MODEM, &modem, |
| NULL); |
| g_assert (MM_IS_BASE_MODEM (modem)); |
| |
| g_assert (MM_IS_SHARED_QMI (modem)); |
| client = mm_shared_qmi_peek_client (MM_SHARED_QMI (modem), |
| service, |
| MM_PORT_QMI_FLAG_DEFAULT, |
| &error); |
| |
| g_object_unref (modem); |
| |
| if (!client) { |
| if (task) { |
| g_task_return_error (task, g_steal_pointer (&error)); |
| g_object_unref (task); |
| } |
| return FALSE; |
| } |
| |
| *o_client = client; |
| return TRUE; |
| } |
| |
| /*****************************************************************************/ |
| /* Wait for SIM ready */ |
| |
| #define SIM_READY_CHECKS_MAX 5 |
| #define SIM_READY_CHECKS_TIMEOUT_SECS 1 |
| |
| typedef struct { |
| QmiClient *client_uim; |
| guint ready_checks_n; |
| } WaitSimReadyContext; |
| |
| static void |
| wait_sim_ready_context_free (WaitSimReadyContext *ctx) |
| { |
| g_clear_object (&ctx->client_uim); |
| g_slice_free (WaitSimReadyContext, ctx); |
| } |
| |
| static gboolean |
| wait_sim_ready_finish (MMBaseSim *self, |
| GAsyncResult *res, |
| GError **error) |
| { |
| return g_task_propagate_boolean (G_TASK (res), error); |
| } |
| |
| static void sim_ready_check (GTask *task); |
| |
| static gboolean |
| sim_ready_retry_cb (GTask *task) |
| { |
| sim_ready_check (task); |
| return G_SOURCE_REMOVE; |
| } |
| |
| static void |
| sim_ready_retry (GTask *task) |
| { |
| g_timeout_add_seconds (SIM_READY_CHECKS_TIMEOUT_SECS, (GSourceFunc) sim_ready_retry_cb, task); |
| } |
| |
| static void |
| uim_get_card_status_ready (QmiClientUim *client, |
| GAsyncResult *res, |
| GTask *task) |
| { |
| g_autoptr(QmiMessageUimGetCardStatusOutput) output = NULL; |
| g_autoptr(GError) error = NULL; |
| MMSimQmi *self; |
| |
| self = g_task_get_source_object (task); |
| |
| output = qmi_client_uim_get_card_status_finish (client, res, &error); |
| if (!output || |
| !qmi_message_uim_get_card_status_output_get_result (output, &error) || |
| (!mm_qmi_uim_get_card_status_output_parse (self, output, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, &error) && |
| (g_error_matches (error, MM_MOBILE_EQUIPMENT_ERROR, MM_MOBILE_EQUIPMENT_ERROR_SIM_NOT_INSERTED) || |
| g_error_matches (error, MM_CORE_ERROR, MM_CORE_ERROR_RETRY)))) { |
| mm_obj_dbg (self, "sim not yet considered ready... retrying"); |
| sim_ready_retry (task); |
| return; |
| } |
| |
| /* SIM is considered ready now */ |
| mm_obj_dbg (self, "sim is ready"); |
| g_task_return_boolean (task, TRUE); |
| g_object_unref (task); |
| } |
| |
| static void |
| sim_ready_check (GTask *task) |
| { |
| WaitSimReadyContext *ctx; |
| MMSimQmi *self; |
| |
| self = g_task_get_source_object (task); |
| ctx = g_task_get_task_data (task); |
| |
| ctx->ready_checks_n++; |
| if (ctx->ready_checks_n == SIM_READY_CHECKS_MAX) { |
| g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, |
| "failed waiting for SIM readiness"); |
| g_object_unref (task); |
| return; |
| } |
| |
| mm_obj_dbg (self, "checking SIM readiness"); |
| qmi_client_uim_get_card_status (QMI_CLIENT_UIM (ctx->client_uim), |
| NULL, |
| 5, |
| NULL, |
| (GAsyncReadyCallback) uim_get_card_status_ready, |
| task); |
| } |
| |
| static void |
| wait_sim_ready (MMBaseSim *_self, |
| GAsyncReadyCallback callback, |
| gpointer user_data) |
| { |
| QmiClient *client; |
| MMSimQmi *self; |
| GTask *task; |
| WaitSimReadyContext *ctx; |
| |
| self = MM_SIM_QMI (_self); |
| task = g_task_new (self, NULL, callback, user_data); |
| |
| mm_obj_dbg (self, "waiting for SIM to be ready..."); |
| if (!self->priv->dms_uim_deprecated) { |
| g_task_return_boolean (task, TRUE); |
| g_object_unref (task); |
| return; |
| } |
| |
| if (!ensure_qmi_client (task, self, QMI_SERVICE_UIM, &client)) |
| return; |
| |
| ctx = g_slice_new0 (WaitSimReadyContext); |
| ctx->client_uim = g_object_ref (client); |
| g_task_set_task_data (task, ctx, (GDestroyNotify) wait_sim_ready_context_free); |
| |
| sim_ready_check (task); |
| } |
| |
| /*****************************************************************************/ |
| /* Load SIM ID (ICCID) */ |
| |
| static GArray * |
| uim_read_finish (QmiClientUim *client, |
| GAsyncResult *res, |
| GError **error) |
| { |
| return g_task_propagate_pointer (G_TASK (res), error); |
| } |
| |
| static void |
| uim_read_ready (QmiClientUim *client, |
| GAsyncResult *res, |
| GTask *task) |
| { |
| QmiMessageUimReadTransparentOutput *output; |
| GError *error = NULL; |
| |
| output = qmi_client_uim_read_transparent_finish (client, res, &error); |
| if (!output) { |
| g_prefix_error (&error, "QMI operation failed: "); |
| g_task_return_error (task, error); |
| } else if (!qmi_message_uim_read_transparent_output_get_result (output, &error)) { |
| g_prefix_error (&error, "Couldn't read data from UIM: "); |
| g_task_return_error (task, error); |
| } else { |
| GArray *read_result = NULL; |
| |
| qmi_message_uim_read_transparent_output_get_read_result (output, &read_result, NULL); |
| if (read_result) |
| g_task_return_pointer (task, |
| g_array_ref (read_result), |
| (GDestroyNotify) g_array_unref); |
| else |
| g_task_return_new_error (task, |
| MM_CORE_ERROR, |
| MM_CORE_ERROR_FAILED, |
| "Read malformed data from UIM"); |
| } |
| |
| if (output) |
| qmi_message_uim_read_transparent_output_unref (output); |
| |
| g_object_unref (task); |
| } |
| |
| static void |
| uim_read (MMSimQmi *self, |
| guint16 file_id, |
| const guint16 *file_path, |
| gsize file_path_len, |
| GAsyncReadyCallback callback, |
| gpointer user_data) |
| { |
| GTask *task; |
| QmiClient *client = NULL; |
| GArray *file_path_bytes; |
| gsize i; |
| QmiMessageUimReadTransparentInput *input; |
| GArray *aid; |
| |
| task = g_task_new (self, NULL, callback, user_data); |
| |
| if (!ensure_qmi_client (task, |
| self, |
| QMI_SERVICE_UIM, &client)) |
| return; |
| |
| file_path_bytes = g_array_sized_new (FALSE, FALSE, 1, file_path_len * 2); |
| for (i = 0; i < file_path_len; ++i) { |
| guint8 byte; |
| |
| byte = file_path[i] & 0xFF; |
| g_array_append_val (file_path_bytes, byte); |
| byte = (file_path[i] >> 8) & 0xFF; |
| g_array_append_val (file_path_bytes, byte); |
| } |
| |
| input = qmi_message_uim_read_transparent_input_new (); |
| aid = g_array_new (FALSE, FALSE, sizeof (guint8)); /* empty AID */ |
| qmi_message_uim_read_transparent_input_set_session ( |
| input, |
| QMI_UIM_SESSION_TYPE_PRIMARY_GW_PROVISIONING, |
| aid, |
| NULL); |
| g_array_unref (aid); |
| qmi_message_uim_read_transparent_input_set_file (input, |
| file_id, |
| file_path_bytes, |
| NULL); |
| qmi_message_uim_read_transparent_input_set_read_information (input, |
| 0, |
| 0, |
| NULL); |
| g_array_unref (file_path_bytes); |
| |
| qmi_client_uim_read_transparent (QMI_CLIENT_UIM (client), |
| input, |
| 10, |
| NULL, |
| (GAsyncReadyCallback)uim_read_ready, |
| task); |
| qmi_message_uim_read_transparent_input_unref (input); |
| } |
| |
| static gchar * |
| load_sim_identifier_finish (MMBaseSim *self, |
| GAsyncResult *res, |
| GError **error) |
| { |
| return g_task_propagate_pointer (G_TASK (res), error); |
| } |
| |
| static void |
| uim_get_iccid_ready (QmiClientUim *client, |
| GAsyncResult *res, |
| GTask *task) |
| { |
| GError *error = NULL; |
| g_autoptr(GArray) read_result = NULL; |
| g_autofree gchar *raw_iccid = NULL; |
| gchar *iccid; |
| |
| read_result = uim_read_finish (client, res, &error); |
| if (!read_result) { |
| g_task_return_error (task, error); |
| g_object_unref (task); |
| return; |
| } |
| |
| raw_iccid = mm_utils_bin2hexstr ((const guint8 *) read_result->data, read_result->len); |
| g_assert (raw_iccid); |
| iccid = mm_3gpp_parse_iccid (raw_iccid, &error); |
| if (!iccid) |
| g_task_return_error (task, error); |
| else |
| g_task_return_pointer (task, iccid, g_free); |
| g_object_unref (task); |
| } |
| |
| static void |
| uim_get_iccid (MMSimQmi *self, |
| GTask *task) |
| { |
| uim_read (self, |
| 0x2FE2, |
| mf_file_path, |
| G_N_ELEMENTS (mf_file_path), |
| (GAsyncReadyCallback)uim_get_iccid_ready, |
| task); |
| } |
| |
| static void |
| dms_uim_get_iccid_ready (QmiClientDms *client, |
| GAsyncResult *res, |
| GTask *task) |
| { |
| QmiMessageDmsUimGetIccidOutput *output = NULL; |
| GError *error = NULL; |
| |
| output = qmi_client_dms_uim_get_iccid_finish (client, res, &error); |
| if (!output) { |
| g_prefix_error (&error, "QMI operation failed: "); |
| g_task_return_error (task, error); |
| } else if (!qmi_message_dms_uim_get_iccid_output_get_result (output, &error)) { |
| g_prefix_error (&error, "Couldn't get UIM ICCID: "); |
| g_task_return_error (task, error); |
| } else { |
| const gchar *str = NULL; |
| |
| qmi_message_dms_uim_get_iccid_output_get_iccid (output, &str, NULL); |
| g_task_return_pointer (task, g_strdup (str), g_free); |
| } |
| |
| if (output) |
| qmi_message_dms_uim_get_iccid_output_unref (output); |
| |
| g_object_unref (task); |
| } |
| |
| static void |
| dms_uim_get_iccid (MMSimQmi *self, |
| GTask *task) |
| { |
| QmiClient *client = NULL; |
| |
| if (!ensure_qmi_client (task, |
| self, |
| QMI_SERVICE_DMS, &client)) |
| return; |
| |
| qmi_client_dms_uim_get_iccid (QMI_CLIENT_DMS (client), |
| NULL, |
| 5, |
| NULL, |
| (GAsyncReadyCallback)dms_uim_get_iccid_ready, |
| task); |
| } |
| |
| static void |
| load_sim_identifier (MMBaseSim *_self, |
| GAsyncReadyCallback callback, |
| gpointer user_data) |
| { |
| MMSimQmi *self; |
| GTask *task; |
| |
| self = MM_SIM_QMI (_self); |
| task = g_task_new (self, NULL, callback, user_data); |
| |
| mm_obj_dbg (self, "loading SIM identifier..."); |
| if (!self->priv->dms_uim_deprecated) |
| dms_uim_get_iccid (self, task); |
| else |
| uim_get_iccid (self, task); |
| } |
| |
| /*****************************************************************************/ |
| /* Load IMSI */ |
| |
| static gchar * |
| load_imsi_finish (MMBaseSim *self, |
| GAsyncResult *res, |
| GError **error) |
| { |
| return g_task_propagate_pointer (G_TASK (res), error); |
| } |
| |
| static void |
| uim_get_imsi_ready (QmiClientUim *client, |
| GAsyncResult *res, |
| GTask *task) |
| { |
| MMSimQmi *self; |
| GError *error = NULL; |
| g_autoptr(GArray) read_result = NULL; |
| g_autofree gchar *imsi = NULL; |
| |
| self = g_task_get_source_object (task); |
| |
| read_result = uim_read_finish (client, res, &error); |
| if (!read_result) { |
| g_task_return_error (task, error); |
| g_object_unref (task); |
| return; |
| } |
| |
| imsi = mm_bcd_to_string ((const guint8 *) read_result->data, read_result->len, |
| TRUE /* low_nybble_first */); |
| g_assert (imsi); |
| if (strlen (imsi) < 3) |
| g_task_return_new_error (task, |
| MM_CORE_ERROR, |
| MM_CORE_ERROR_FAILED, |
| "IMSI is malformed"); |
| else { |
| /* EFimsi contains a length byte, followed by a nibble for parity, |
| * and then followed by the actual IMSI in BCD. After converting |
| * the BCD into a decimal string, we simply skip the first 3 |
| * decimal digits to obtain the IMSI. */ |
| |
| /* Cache IMSI */ |
| g_free (self->priv->imsi); |
| self->priv->imsi = g_strdup (imsi + 3); |
| |
| g_task_return_pointer (task, g_strdup (imsi + 3), g_free); |
| } |
| g_object_unref (task); |
| } |
| |
| static void |
| uim_get_imsi (MMSimQmi *self, |
| GTask *task) |
| { |
| uim_read (self, |
| 0x6F07, |
| adf_file_path, |
| G_N_ELEMENTS (adf_file_path), |
| (GAsyncReadyCallback)uim_get_imsi_ready, |
| task); |
| } |
| |
| static void |
| dms_uim_get_imsi_ready (QmiClientDms *client, |
| GAsyncResult *res, |
| GTask *task) |
| { |
| MMSimQmi *self; |
| QmiMessageDmsUimGetImsiOutput *output = NULL; |
| GError *error = NULL; |
| |
| self = g_task_get_source_object (task); |
| |
| output = qmi_client_dms_uim_get_imsi_finish (client, res, &error); |
| if (!output) { |
| g_prefix_error (&error, "QMI operation failed: "); |
| g_task_return_error (task, error); |
| } else if (!qmi_message_dms_uim_get_imsi_output_get_result (output, &error)) { |
| g_prefix_error (&error, "Couldn't get UIM IMSI: "); |
| g_task_return_error (task, error); |
| } else { |
| const gchar *str = NULL; |
| |
| qmi_message_dms_uim_get_imsi_output_get_imsi (output, &str, NULL); |
| |
| /* Cache IMSI */ |
| g_free (self->priv->imsi); |
| self->priv->imsi = g_strdup (str); |
| |
| g_task_return_pointer (task, g_strdup (str), g_free); |
| } |
| |
| if (output) |
| qmi_message_dms_uim_get_imsi_output_unref (output); |
| |
| g_object_unref (task); |
| } |
| |
| static void |
| dms_uim_get_imsi (MMSimQmi *self, |
| GTask *task) |
| { |
| QmiClient *client = NULL; |
| |
| if (!ensure_qmi_client (task, |
| self, |
| QMI_SERVICE_DMS, &client)) |
| return; |
| |
| qmi_client_dms_uim_get_imsi (QMI_CLIENT_DMS (client), |
| NULL, |
| 5, |
| NULL, |
| (GAsyncReadyCallback)dms_uim_get_imsi_ready, |
| task); |
| } |
| |
| static void |
| load_imsi (MMBaseSim *_self, |
| GAsyncReadyCallback callback, |
| gpointer user_data) |
| { |
| MMSimQmi *self; |
| GTask *task; |
| |
| self = MM_SIM_QMI (_self); |
| task = g_task_new (self, NULL, callback, user_data); |
| |
| mm_obj_dbg (self, "loading IMSI..."); |
| if (!self->priv->dms_uim_deprecated) |
| dms_uim_get_imsi (self, task); |
| else |
| uim_get_imsi (self, task); |
| } |
| |
| /*****************************************************************************/ |
| /* Load GID1 and GID2 */ |
| |
| static GByteArray * |
| common_load_gid_finish (MMBaseSim *self, |
| GAsyncResult *res, |
| GError **error) |
| { |
| return g_task_propagate_pointer (G_TASK (res), error); |
| } |
| |
| static void |
| uim_get_gid_ready (QmiClientUim *client, |
| GAsyncResult *res, |
| GTask *task) |
| { |
| GError *error = NULL; |
| g_autoptr(GArray) read_result = NULL; |
| |
| read_result = uim_read_finish (client, res, &error); |
| if (!read_result) |
| g_task_return_error (task, error); |
| else |
| g_task_return_pointer (task, |
| g_byte_array_append (g_byte_array_sized_new (read_result->len), |
| (const guint8 *)(read_result->data), |
| read_result->len), |
| (GDestroyNotify)g_byte_array_unref); |
| g_object_unref (task); |
| } |
| |
| static void |
| common_load_gid (MMBaseSim *self, |
| guint16 file_id, |
| GAsyncReadyCallback callback, |
| gpointer user_data) |
| { |
| GTask *task; |
| |
| task = g_task_new (self, NULL, callback, user_data); |
| |
| uim_read (MM_SIM_QMI (self), |
| file_id, |
| adf_file_path, |
| G_N_ELEMENTS (adf_file_path), |
| (GAsyncReadyCallback)uim_get_gid_ready, |
| task); |
| } |
| |
| static GByteArray * |
| load_gid1_finish (MMBaseSim *self, |
| GAsyncResult *res, |
| GError **error) |
| { |
| return common_load_gid_finish (self, res, error); |
| } |
| |
| static void |
| load_gid1 (MMBaseSim *self, |
| GAsyncReadyCallback callback, |
| gpointer user_data) |
| { |
| common_load_gid (self, 0x6F3E, callback, user_data); |
| } |
| |
| static GByteArray * |
| load_gid2_finish (MMBaseSim *self, |
| GAsyncResult *res, |
| GError **error) |
| { |
| return common_load_gid_finish (self, res, error); |
| } |
| |
| static void |
| load_gid2 (MMBaseSim *self, |
| GAsyncReadyCallback callback, |
| gpointer user_data) |
| { |
| common_load_gid (self, 0x6F3F, callback, user_data); |
| } |
| |
| /*****************************************************************************/ |
| /* 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 |
| uim_read_efad_ready (QmiClientUim *client, |
| GAsyncResult *res, |
| GTask *task) |
| { |
| MMSimQmi *self; |
| GError *error = NULL; |
| g_autoptr(GArray) read_result = NULL; |
| guint mnc_length; |
| |
| self = g_task_get_source_object (task); |
| |
| read_result = uim_read_finish (client, res, &error); |
| if (!read_result) { |
| g_task_return_error (task, error); |
| g_object_unref (task); |
| return; |
| } |
| |
| mnc_length = mm_sim_validate_mnc_length ((const guint8 *) read_result->data, read_result->len, &error); |
| if (!mnc_length) { |
| g_task_return_error (task, error); |
| } else { |
| g_task_return_pointer (task, g_strndup (self->priv->imsi, 3 + mnc_length), g_free); |
| } |
| g_object_unref (task); |
| } |
| |
| static void |
| load_operator_identifier (MMBaseSim *_self, |
| GAsyncReadyCallback callback, |
| gpointer user_data) |
| { |
| MMSimQmi *self; |
| GTask *task; |
| |
| self = MM_SIM_QMI (_self); |
| task = g_task_new (self, NULL, callback, user_data); |
| |
| mm_obj_dbg (self, "loading SIM operator identifier..."); |
| |
| if (!self->priv->imsi) { |
| g_task_return_new_error (task, |
| MM_CORE_ERROR, |
| MM_CORE_ERROR_FAILED, |
| "Couldn't load SIM operator identifier without IMSI"); |
| g_object_unref (task); |
| return; |
| } |
| |
| uim_read (self, |
| 0x6FAD, |
| adf_file_path, |
| G_N_ELEMENTS (adf_file_path), |
| (GAsyncReadyCallback)uim_read_efad_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 |
| uim_read_efspn_ready (QmiClientUim *client, |
| GAsyncResult *res, |
| GTask *task) |
| { |
| GError *error = NULL; |
| g_autoptr(GArray) read_result = NULL; |
| gchar *spn; |
| |
| read_result = uim_read_finish (client, res, &error); |
| if (!read_result) { |
| g_task_return_error (task, error); |
| g_object_unref (task); |
| return; |
| } |
| |
| spn = mm_sim_convert_spn_to_utf8 ((const guint8 *) read_result->data, read_result->len, &error); |
| if (!spn) { |
| g_task_return_error (task, error); |
| } else { |
| g_task_return_pointer (task, spn, g_free); |
| } |
| |
| g_object_unref (task); |
| } |
| |
| static void |
| load_operator_name (MMBaseSim *_self, |
| GAsyncReadyCallback callback, |
| gpointer user_data) |
| { |
| MMSimQmi *self; |
| GTask *task; |
| |
| self = MM_SIM_QMI (_self); |
| task = g_task_new (self, NULL, callback, user_data); |
| |
| mm_obj_dbg (self, "loading SIM operator name..."); |
| |
| uim_read (self, |
| 0x6F46, |
| adf_file_path, |
| G_N_ELEMENTS (adf_file_path), |
| (GAsyncReadyCallback)uim_read_efspn_ready, |
| task); |
| } |
| |
| /*****************************************************************************/ |
| /* Load preferred networks */ |
| |
| static GList * |
| load_preferred_networks_finish (MMBaseSim *self, |
| GAsyncResult *res, |
| GError **error) |
| { |
| return g_task_propagate_pointer (G_TASK (res), error); |
| } |
| |
| static GList * |
| parse_get_preferred_networks (QmiMessageNasGetPreferredNetworksOutput *output) |
| { |
| GList *result = NULL; |
| GArray *preferred_nets_array = NULL; |
| GArray *preferred_nets_mnc_pcs_digit_array = NULL; |
| guint i; |
| |
| if (qmi_message_nas_get_preferred_networks_output_get_preferred_networks (output, |
| &preferred_nets_array, |
| NULL)) { |
| qmi_message_nas_get_preferred_networks_output_get_mnc_pcs_digit_include_status (output, |
| &preferred_nets_mnc_pcs_digit_array, |
| NULL); |
| for (i = 0; i < preferred_nets_array->len; i++) { |
| QmiMessageNasGetPreferredNetworksOutputPreferredNetworksElement *net; |
| QmiMessageNasGetPreferredNetworksOutputMncPcsDigitIncludeStatusElement *mnc_pcs_digit = NULL; |
| MMSimPreferredNetwork *new_item; |
| g_autofree gchar *operator_code = NULL; |
| MMModemAccessTechnology act = MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN; |
| |
| net = &g_array_index (preferred_nets_array, |
| QmiMessageNasGetPreferredNetworksOutputPreferredNetworksElement, i); |
| if (preferred_nets_mnc_pcs_digit_array && i < preferred_nets_mnc_pcs_digit_array->len) |
| mnc_pcs_digit = &g_array_index (preferred_nets_mnc_pcs_digit_array, |
| QmiMessageNasGetPreferredNetworksOutputMncPcsDigitIncludeStatusElement, i); |
| |
| new_item = mm_sim_preferred_network_new (); |
| |
| if (net->mnc > 99 || (mnc_pcs_digit != NULL && mnc_pcs_digit->includes_pcs_digit)) |
| operator_code = g_strdup_printf ("%03d%03d", net->mcc, net->mnc); |
| else |
| operator_code = g_strdup_printf ("%03d%02d", net->mcc, net->mnc); |
| mm_sim_preferred_network_set_operator_code (new_item, operator_code); |
| |
| if (net->radio_access_technology & QMI_NAS_PLMN_ACCESS_TECHNOLOGY_IDENTIFIER_GSM) |
| act |= MM_MODEM_ACCESS_TECHNOLOGY_GSM; |
| if (net->radio_access_technology & QMI_NAS_PLMN_ACCESS_TECHNOLOGY_IDENTIFIER_GSM_COMPACT) |
| act |= MM_MODEM_ACCESS_TECHNOLOGY_GSM_COMPACT; |
| if (net->radio_access_technology & QMI_NAS_PLMN_ACCESS_TECHNOLOGY_IDENTIFIER_UTRAN) |
| act |= MM_MODEM_ACCESS_TECHNOLOGY_UMTS; |
| if (net->radio_access_technology & QMI_NAS_PLMN_ACCESS_TECHNOLOGY_IDENTIFIER_EUTRAN) |
| act |= MM_MODEM_ACCESS_TECHNOLOGY_LTE; |
| if (net->radio_access_technology & QMI_NAS_PLMN_ACCESS_TECHNOLOGY_IDENTIFIER_NGRAN) |
| act |= MM_MODEM_ACCESS_TECHNOLOGY_5GNR; |
| mm_sim_preferred_network_set_access_technology (new_item, act); |
| |
| result = g_list_append (result, new_item); |
| } |
| } |
| |
| return result; |
| } |
| |
| static void |
| load_preferred_networks_ready (QmiClientNas *client, |
| GAsyncResult *res, |
| GTask *task) |
| { |
| QmiMessageNasGetPreferredNetworksOutput *output; |
| GError *error = NULL; |
| |
| output = qmi_client_nas_get_preferred_networks_finish (client, res, &error); |
| if (!output) { |
| g_prefix_error (&error, "QMI operation failed: "); |
| g_task_return_error (task, error); |
| } else if (!qmi_message_nas_get_preferred_networks_output_get_result (output, &error)) { |
| g_prefix_error (&error, "Couldn't get preferred networks: "); |
| g_task_return_error (task, error); |
| } else |
| g_task_return_pointer (task, parse_get_preferred_networks (output), (GDestroyNotify) mm_sim_preferred_network_list_free); |
| |
| if (output) |
| qmi_message_nas_get_preferred_networks_output_unref (output); |
| g_object_unref (task); |
| } |
| |
| static void |
| load_preferred_networks (MMBaseSim *self, |
| GAsyncReadyCallback callback, |
| gpointer user_data) |
| { |
| GTask *task; |
| QmiClient *client = NULL; |
| |
| task = g_task_new (self, NULL, callback, user_data); |
| if (!ensure_qmi_client (task, |
| MM_SIM_QMI (self), |
| QMI_SERVICE_NAS, &client)) |
| return; |
| |
| mm_obj_dbg (self, "loading preferred network list..."); |
| qmi_client_nas_get_preferred_networks (QMI_CLIENT_NAS (client), |
| NULL, |
| 5, |
| NULL, |
| (GAsyncReadyCallback)load_preferred_networks_ready, |
| task); |
| } |
| |
| /*****************************************************************************/ |
| /* Set preferred networks */ |
| |
| typedef struct { |
| /* Preferred network list to be set, used for after-check comparison */ |
| GList *set_list; |
| } SetPreferredNetworksContext; |
| |
| static void |
| set_preferred_network_context_free (SetPreferredNetworksContext *ctx) |
| { |
| g_list_free_full (ctx->set_list, (GDestroyNotify) mm_sim_preferred_network_free); |
| g_slice_free (SetPreferredNetworksContext, ctx); |
| } |
| |
| static gboolean |
| set_preferred_networks_finish (MMBaseSim *self, |
| GAsyncResult *res, |
| GError **error) |
| { |
| return g_task_propagate_boolean (G_TASK (res), error); |
| } |
| |
| static void |
| set_preferred_networks_reload_ready (MMBaseSim *self, |
| GAsyncResult *res, |
| GTask *task) |
| { |
| GError *error = NULL; |
| GList *loaded_list; |
| GList *loaded_iter; |
| GList *set_iter; |
| SetPreferredNetworksContext *ctx; |
| |
| ctx = g_task_get_task_data (task); |
| |
| loaded_list = load_preferred_networks_finish (self, res, &error); |
| if (error) { |
| mm_obj_warn (self, "couldn't reload list of preferred networks: %s", error->message); |
| g_task_return_error (task, error); |
| g_object_unref (task); |
| return; |
| } |
| |
| /* Compare the set and loaded network list for differences */ |
| loaded_iter = loaded_list; |
| set_iter = ctx->set_list; |
| |
| while (loaded_iter && set_iter) { |
| const gchar *loaded_op_code; |
| const gchar *set_op_code; |
| MMModemAccessTechnology loaded_act; |
| MMModemAccessTechnology set_act; |
| |
| loaded_op_code = mm_sim_preferred_network_get_operator_code (loaded_iter->data); |
| set_op_code = mm_sim_preferred_network_get_operator_code (set_iter->data); |
| loaded_act = mm_sim_preferred_network_get_access_technology (loaded_iter->data); |
| set_act = mm_sim_preferred_network_get_access_technology (set_iter->data); |
| |
| /* Operator code mismatch is never expected, but check it just in case */ |
| if (g_strcmp0 (loaded_op_code, set_op_code)) { |
| mm_obj_warn (self, "operator code mismatch, expected '%s' loaded '%s'", |
| set_op_code, loaded_op_code); |
| error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, |
| "Mismatch in requested and set operator code"); |
| break; |
| } |
| /* Check if there are access technology bits requested but unset */ |
| if ((loaded_act & set_act) != set_act) { |
| MMModemAccessTechnology unset = set_act & ~loaded_act; |
| gchar *act; |
| |
| act = mm_modem_access_technology_build_string_from_mask (unset); |
| mm_obj_warn (self, "access technologies '%s' not set for operator code '%s'", |
| act, set_op_code); |
| g_free (act); |
| error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED, |
| "Access technology unsupported by modem or SIM"); |
| break; |
| } |
| loaded_iter = g_list_next (loaded_iter); |
| set_iter = g_list_next (set_iter); |
| } |
| if (!error && loaded_iter == NULL && set_iter != NULL) { |
| /* Not all networks were written; some modems silently discard networks |
| * that exceed the SIM card capacity. |
| */ |
| error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_TOO_MANY, |
| "Too many networks; %u networks written", |
| g_list_length (loaded_list)); |
| } |
| |
| if (error) { |
| /* Update the PreferredNetworks property to real SIM contents */ |
| mm_gdbus_sim_set_preferred_networks (MM_GDBUS_SIM (self), |
| mm_sim_preferred_network_list_get_variant (loaded_list)); |
| g_task_return_error (task, error); |
| } else |
| g_task_return_boolean (task, TRUE); |
| |
| g_list_free_full (loaded_list, (GDestroyNotify) mm_sim_preferred_network_free); |
| g_object_unref (task); |
| } |
| |
| static void |
| set_preferred_networks_ready (QmiClientNas *client, |
| GAsyncResult *res, |
| GTask *task) |
| { |
| QmiMessageNasSetPreferredNetworksOutput *output; |
| GError *error = NULL; |
| MMBaseSim *self; |
| |
| self = g_task_get_source_object (task); |
| |
| output = qmi_client_nas_set_preferred_networks_finish (client, res, &error); |
| if (!output) { |
| g_prefix_error (&error, "QMI operation failed: "); |
| g_task_return_error (task, error); |
| } else if (!qmi_message_nas_set_preferred_networks_output_get_result (output, &error)) { |
| g_prefix_error (&error, "Couldn't set preferred networks: "); |
| g_task_return_error (task, error); |
| } else { |
| /* Reload the networks from modem to check whether everything was written */ |
| load_preferred_networks (self, (GAsyncReadyCallback) set_preferred_networks_reload_ready, task); |
| qmi_message_nas_set_preferred_networks_output_unref (output); |
| return; |
| } |
| |
| if (output) |
| qmi_message_nas_set_preferred_networks_output_unref (output); |
| g_object_unref (task); |
| } |
| |
| static void |
| set_preferred_networks (MMBaseSim *self, |
| GList *preferred_network_list, |
| GAsyncReadyCallback callback, |
| gpointer user_data) |
| { |
| GTask *task; |
| QmiMessageNasSetPreferredNetworksInput *input; |
| QmiClient *client = NULL; |
| GArray *preferred_nets_array; |
| GArray *preferred_nets_mnc_pcs_digit_array; |
| SetPreferredNetworksContext *ctx; |
| |
| task = g_task_new (self, NULL, callback, user_data); |
| if (!ensure_qmi_client (task, |
| MM_SIM_QMI (self), |
| QMI_SERVICE_NAS, &client)) |
| return; |
| |
| ctx = g_slice_new0 (SetPreferredNetworksContext); |
| ctx->set_list = mm_sim_preferred_network_list_copy (preferred_network_list); |
| g_task_set_task_data (task, ctx, (GDestroyNotify) set_preferred_network_context_free); |
| |
| mm_obj_dbg (self, "setting preferred networks..."); |
| |
| input = qmi_message_nas_set_preferred_networks_input_new (); |
| |
| preferred_nets_array = g_array_new (FALSE, TRUE, sizeof (QmiMessageNasSetPreferredNetworksInputPreferredNetworksElement)); |
| preferred_nets_mnc_pcs_digit_array = g_array_new (FALSE, TRUE, sizeof (QmiMessageNasSetPreferredNetworksInputMncPcsDigitIncludeStatusElement)); |
| |
| while (preferred_network_list) { |
| QmiMessageNasSetPreferredNetworksInputPreferredNetworksElement preferred_nets_element; |
| QmiMessageNasSetPreferredNetworksInputMncPcsDigitIncludeStatusElement pcs_digit_element; |
| const gchar *operator_code; |
| MMModemAccessTechnology act; |
| |
| memset (&preferred_nets_element, 0, sizeof (preferred_nets_element)); |
| memset (&pcs_digit_element, 0, sizeof (pcs_digit_element)); |
| |
| operator_code = mm_sim_preferred_network_get_operator_code (preferred_network_list->data); |
| act = mm_sim_preferred_network_get_access_technology (preferred_network_list->data); |
| if (mm_3gpp_parse_operator_id (operator_code, &preferred_nets_element.mcc, &preferred_nets_element.mnc, |
| &pcs_digit_element.includes_pcs_digit, NULL)) { |
| pcs_digit_element.mcc = preferred_nets_element.mcc; |
| pcs_digit_element.mnc = preferred_nets_element.mnc; |
| |
| preferred_nets_element.radio_access_technology = QMI_NAS_PLMN_ACCESS_TECHNOLOGY_IDENTIFIER_UNSPECIFIED; |
| if (act & MM_MODEM_ACCESS_TECHNOLOGY_GSM) |
| preferred_nets_element.radio_access_technology |= QMI_NAS_PLMN_ACCESS_TECHNOLOGY_IDENTIFIER_GSM; |
| if (act & MM_MODEM_ACCESS_TECHNOLOGY_GSM_COMPACT) |
| preferred_nets_element.radio_access_technology |= QMI_NAS_PLMN_ACCESS_TECHNOLOGY_IDENTIFIER_GSM_COMPACT; |
| if (act & MM_MODEM_ACCESS_TECHNOLOGY_UMTS) |
| preferred_nets_element.radio_access_technology |= QMI_NAS_PLMN_ACCESS_TECHNOLOGY_IDENTIFIER_UTRAN; |
| if (act & MM_MODEM_ACCESS_TECHNOLOGY_LTE) |
| preferred_nets_element.radio_access_technology |= QMI_NAS_PLMN_ACCESS_TECHNOLOGY_IDENTIFIER_EUTRAN; |
| if (act & MM_MODEM_ACCESS_TECHNOLOGY_5GNR) |
| preferred_nets_element.radio_access_technology |= QMI_NAS_PLMN_ACCESS_TECHNOLOGY_IDENTIFIER_NGRAN; |
| |
| g_array_append_val (preferred_nets_array, preferred_nets_element); |
| g_array_append_val (preferred_nets_mnc_pcs_digit_array, pcs_digit_element); |
| } |
| |
| preferred_network_list = g_list_next (preferred_network_list); |
| } |
| |
| qmi_message_nas_set_preferred_networks_input_set_preferred_networks (input, preferred_nets_array, NULL); |
| qmi_message_nas_set_preferred_networks_input_set_mnc_pcs_digit_include_status (input, preferred_nets_mnc_pcs_digit_array, NULL); |
| /* Always clear any pre-existing networks */ |
| qmi_message_nas_set_preferred_networks_input_set_clear_previous_preferred_networks (input, TRUE, NULL); |
| |
| qmi_client_nas_set_preferred_networks (QMI_CLIENT_NAS (client), |
| input, |
| 5, |
| NULL, |
| (GAsyncReadyCallback)set_preferred_networks_ready, |
| task); |
| |
| qmi_message_nas_set_preferred_networks_input_unref (input); |
| g_array_unref (preferred_nets_array); |
| g_array_unref (preferred_nets_mnc_pcs_digit_array); |
| } |
| |
| /*****************************************************************************/ |
| /* Send PIN */ |
| |
| static GError * |
| pin_qmi_error_to_mobile_equipment_error (GError *qmi_error) |
| { |
| GError *me_error = NULL; |
| |
| if (g_error_matches (qmi_error, |
| QMI_PROTOCOL_ERROR, |
| QMI_PROTOCOL_ERROR_INCORRECT_PIN)) { |
| me_error = g_error_new_literal (MM_MOBILE_EQUIPMENT_ERROR, |
| MM_MOBILE_EQUIPMENT_ERROR_INCORRECT_PASSWORD, |
| qmi_error->message); |
| } else if (g_error_matches (qmi_error, |
| QMI_PROTOCOL_ERROR, |
| QMI_PROTOCOL_ERROR_PIN_BLOCKED)) { |
| me_error = g_error_new_literal (MM_MOBILE_EQUIPMENT_ERROR, |
| MM_MOBILE_EQUIPMENT_ERROR_SIM_PUK, |
| qmi_error->message); |
| } |
| |
| if (me_error) { |
| g_error_free (qmi_error); |
| return me_error; |
| } |
| |
| return qmi_error; |
| } |
| |
| static gboolean |
| send_pin_finish (MMBaseSim *self, |
| GAsyncResult *res, |
| GError **error) |
| { |
| return g_task_propagate_boolean (G_TASK (res), error); |
| } |
| |
| static void |
| uim_verify_pin_ready (QmiClientUim *client, |
| GAsyncResult *res, |
| GTask *task) |
| { |
| QmiMessageUimVerifyPinOutput *output = NULL; |
| GError *error = NULL; |
| |
| output = qmi_client_uim_verify_pin_finish (client, res, &error); |
| if (!output) { |
| g_prefix_error (&error, "QMI operation failed: "); |
| g_task_return_error (task, error); |
| } else if (!qmi_message_uim_verify_pin_output_get_result (output, &error)) { |
| g_prefix_error (&error, "Couldn't verify PIN: "); |
| g_task_return_error (task, pin_qmi_error_to_mobile_equipment_error (error)); |
| } else |
| g_task_return_boolean (task, TRUE); |
| |
| if (output) |
| qmi_message_uim_verify_pin_output_unref (output); |
| g_object_unref (task); |
| } |
| |
| static void |
| uim_verify_pin (MMSimQmi *self, |
| GTask *task) |
| { |
| QmiMessageUimVerifyPinInput *input; |
| QmiClient *client = NULL; |
| GArray *aid; |
| |
| if (!ensure_qmi_client (task, |
| self, |
| QMI_SERVICE_UIM, &client)) |
| return; |
| |
| input = qmi_message_uim_verify_pin_input_new (); |
| qmi_message_uim_verify_pin_input_set_info ( |
| input, |
| QMI_UIM_PIN_ID_PIN1, |
| g_task_get_task_data (task), |
| NULL); |
| aid = g_array_new (FALSE, FALSE, sizeof (guint8)); /* empty AID */ |
| qmi_message_uim_verify_pin_input_set_session ( |
| input, |
| QMI_UIM_SESSION_TYPE_CARD_SLOT_1, |
| aid, |
| NULL); |
| g_array_unref (aid); |
| qmi_client_uim_verify_pin (QMI_CLIENT_UIM (client), |
| input, |
| 5, |
| NULL, |
| (GAsyncReadyCallback) uim_verify_pin_ready, |
| task); |
| qmi_message_uim_verify_pin_input_unref (input); |
| } |
| |
| static void |
| dms_uim_verify_pin_ready (QmiClientDms *client, |
| GAsyncResult *res, |
| GTask *task) |
| { |
| QmiMessageDmsUimVerifyPinOutput *output = NULL; |
| GError *error = NULL; |
| |
| output = qmi_client_dms_uim_verify_pin_finish (client, res, &error); |
| if (!output) { |
| g_prefix_error (&error, "QMI operation failed: "); |
| g_task_return_error (task, error); |
| } else if (!qmi_message_dms_uim_verify_pin_output_get_result (output, &error)) { |
| g_prefix_error (&error, "Couldn't verify PIN: "); |
| g_task_return_error (task, pin_qmi_error_to_mobile_equipment_error (error)); |
| } else |
| g_task_return_boolean (task, TRUE); |
| |
| if (output) |
| qmi_message_dms_uim_verify_pin_output_unref (output); |
| g_object_unref (task); |
| } |
| |
| static void |
| dms_uim_verify_pin (MMSimQmi *self, |
| GTask *task) |
| { |
| QmiMessageDmsUimVerifyPinInput *input; |
| QmiClient *client = NULL; |
| |
| if (!ensure_qmi_client (NULL, |
| self, |
| QMI_SERVICE_DMS, &client)) { |
| /* Very unlikely that this will ever happen, but anyway, try with |
| * UIM service instead */ |
| uim_verify_pin (self, task); |
| return; |
| } |
| |
| mm_obj_dbg (self, "sending PIN..."); |
| input = qmi_message_dms_uim_verify_pin_input_new (); |
| qmi_message_dms_uim_verify_pin_input_set_info ( |
| input, |
| QMI_DMS_UIM_PIN_ID_PIN, |
| g_task_get_task_data (task), |
| NULL); |
| qmi_client_dms_uim_verify_pin (QMI_CLIENT_DMS (client), |
| input, |
| 5, |
| NULL, |
| (GAsyncReadyCallback) dms_uim_verify_pin_ready, |
| task); |
| qmi_message_dms_uim_verify_pin_input_unref (input); |
| } |
| |
| static void |
| send_pin (MMBaseSim *_self, |
| const gchar *pin, |
| GAsyncReadyCallback callback, |
| gpointer user_data) |
| { |
| GTask *task; |
| MMSimQmi *self; |
| |
| self = MM_SIM_QMI (_self); |
| task = g_task_new (self, NULL, callback, user_data); |
| |
| g_task_set_task_data (task, g_strdup (pin), g_free); |
| |
| mm_obj_dbg (self, "verifying PIN..."); |
| if (!self->priv->dms_uim_deprecated) |
| dms_uim_verify_pin (self, task); |
| else |
| uim_verify_pin (self, task); |
| } |
| |
| /*****************************************************************************/ |
| /* Send PUK */ |
| |
| typedef struct { |
| gchar *puk; |
| gchar *new_pin; |
| } UnblockPinContext; |
| |
| static void |
| unblock_pin_context_free (UnblockPinContext *ctx) |
| { |
| g_free (ctx->puk); |
| g_free (ctx->new_pin); |
| g_slice_free (UnblockPinContext, ctx); |
| } |
| |
| static gboolean |
| send_puk_finish (MMBaseSim *self, |
| GAsyncResult *res, |
| GError **error) |
| { |
| return g_task_propagate_boolean (G_TASK (res), error); |
| } |
| |
| static void |
| uim_unblock_pin_ready (QmiClientUim *client, |
| GAsyncResult *res, |
| GTask *task) |
| { |
| QmiMessageUimUnblockPinOutput *output = NULL; |
| GError *error = NULL; |
| |
| output = qmi_client_uim_unblock_pin_finish (client, res, &error); |
| if (!output) { |
| g_prefix_error (&error, "QMI operation failed: "); |
| g_task_return_error (task, error); |
| } else if (!qmi_message_uim_unblock_pin_output_get_result (output, &error)) { |
| g_prefix_error (&error, "Couldn't unblock PIN: "); |
| g_task_return_error (task, pin_qmi_error_to_mobile_equipment_error (error)); |
| } else |
| g_task_return_boolean (task, TRUE); |
| |
| if (output) |
| qmi_message_uim_unblock_pin_output_unref (output); |
| g_object_unref (task); |
| } |
| |
| static void |
| uim_unblock_pin (MMSimQmi *self, |
| GTask *task) |
| { |
| QmiMessageUimUnblockPinInput *input; |
| QmiClient *client = NULL; |
| UnblockPinContext *ctx; |
| GArray *aid; |
| |
| if (!ensure_qmi_client (task, |
| self, |
| QMI_SERVICE_UIM, &client)) |
| return; |
| |
| ctx = g_task_get_task_data (task); |
| |
| input = qmi_message_uim_unblock_pin_input_new (); |
| qmi_message_uim_unblock_pin_input_set_info ( |
| input, |
| QMI_UIM_PIN_ID_PIN1, |
| ctx->puk, |
| ctx->new_pin, |
| NULL); |
| aid = g_array_new (FALSE, FALSE, sizeof (guint8)); /* empty AID */ |
| qmi_message_uim_unblock_pin_input_set_session ( |
| input, |
| QMI_UIM_SESSION_TYPE_CARD_SLOT_1, |
| aid, |
| NULL); |
| g_array_unref (aid); |
| qmi_client_uim_unblock_pin (QMI_CLIENT_UIM (client), |
| input, |
| 5, |
| NULL, |
| (GAsyncReadyCallback) uim_unblock_pin_ready, |
| task); |
| qmi_message_uim_unblock_pin_input_unref (input); |
| } |
| |
| static void |
| dms_uim_unblock_pin_ready (QmiClientDms *client, |
| GAsyncResult *res, |
| GTask *task) |
| { |
| QmiMessageDmsUimUnblockPinOutput *output = NULL; |
| GError *error = NULL; |
| |
| output = qmi_client_dms_uim_unblock_pin_finish (client, res, &error); |
| if (!output) { |
| g_prefix_error (&error, "QMI operation failed: "); |
| g_task_return_error (task, error); |
| } else if (!qmi_message_dms_uim_unblock_pin_output_get_result (output, &error)) { |
| g_prefix_error (&error, "Couldn't unblock PIN: "); |
| g_task_return_error (task, pin_qmi_error_to_mobile_equipment_error (error)); |
| } else |
| g_task_return_boolean (task, TRUE); |
| |
| if (output) |
| qmi_message_dms_uim_unblock_pin_output_unref (output); |
| g_object_unref (task); |
| } |
| |
| static void |
| dms_uim_unblock_pin (MMSimQmi *self, |
| GTask *task) |
| { |
| QmiMessageDmsUimUnblockPinInput *input; |
| QmiClient *client = NULL; |
| UnblockPinContext *ctx; |
| |
| if (!ensure_qmi_client (NULL, |
| self, |
| QMI_SERVICE_DMS, &client)) { |
| /* Very unlikely that this will ever happen, but anyway, try with |
| * UIM service instead */ |
| uim_unblock_pin (self, task); |
| return; |
| } |
| |
| ctx = g_task_get_task_data (task); |
| |
| input = qmi_message_dms_uim_unblock_pin_input_new (); |
| qmi_message_dms_uim_unblock_pin_input_set_info ( |
| input, |
| QMI_DMS_UIM_PIN_ID_PIN, |
| ctx->puk, |
| ctx->new_pin, |
| NULL); |
| qmi_client_dms_uim_unblock_pin (QMI_CLIENT_DMS (client), |
| input, |
| 5, |
| NULL, |
| (GAsyncReadyCallback)dms_uim_unblock_pin_ready, |
| task); |
| qmi_message_dms_uim_unblock_pin_input_unref (input); |
| } |
| |
| static void |
| send_puk (MMBaseSim *_self, |
| const gchar *puk, |
| const gchar *new_pin, |
| GAsyncReadyCallback callback, |
| gpointer user_data) |
| { |
| GTask *task; |
| UnblockPinContext *ctx; |
| MMSimQmi *self; |
| |
| self = MM_SIM_QMI (_self); |
| task = g_task_new (self, NULL, callback, user_data); |
| |
| ctx = g_slice_new (UnblockPinContext); |
| ctx->puk = g_strdup (puk); |
| ctx->new_pin = g_strdup (new_pin); |
| g_task_set_task_data (task, ctx, (GDestroyNotify) unblock_pin_context_free); |
| |
| mm_obj_dbg (self, "unblocking PIN..."); |
| if (!self->priv->dms_uim_deprecated) |
| dms_uim_unblock_pin (self, task); |
| else |
| uim_unblock_pin (self, task); |
| } |
| |
| /*****************************************************************************/ |
| /* Change PIN */ |
| |
| typedef struct { |
| gchar *old_pin; |
| gchar *new_pin; |
| } ChangePinContext; |
| |
| static void |
| change_pin_context_free (ChangePinContext *ctx) |
| { |
| g_free (ctx->old_pin); |
| g_free (ctx->new_pin); |
| g_slice_free (ChangePinContext, ctx); |
| } |
| |
| static gboolean |
| change_pin_finish (MMBaseSim *self, |
| GAsyncResult *res, |
| GError **error) |
| { |
| return g_task_propagate_boolean (G_TASK (res), error); |
| } |
| |
| static void |
| uim_change_pin_ready (QmiClientUim *client, |
| GAsyncResult *res, |
| GTask *task) |
| { |
| QmiMessageUimChangePinOutput *output = NULL; |
| GError *error = NULL; |
| |
| output = qmi_client_uim_change_pin_finish (client, res, &error); |
| if (!output) { |
| g_prefix_error (&error, "QMI operation failed: "); |
| g_task_return_error (task, error); |
| } else if (!qmi_message_uim_change_pin_output_get_result (output, &error)) { |
| g_prefix_error (&error, "Couldn't change PIN: "); |
| g_task_return_error (task, pin_qmi_error_to_mobile_equipment_error (error)); |
| } else |
| g_task_return_boolean (task, TRUE); |
| |
| if (output) |
| qmi_message_uim_change_pin_output_unref (output); |
| g_object_unref (task); |
| } |
| |
| static void |
| uim_change_pin (MMSimQmi *self, |
| GTask *task) |
| { |
| QmiMessageUimChangePinInput *input; |
| QmiClient *client = NULL; |
| ChangePinContext *ctx; |
| GArray *aid; |
| |
| if (!ensure_qmi_client (task, |
| self, |
| QMI_SERVICE_UIM, &client)) |
| return; |
| |
| ctx = g_task_get_task_data (task); |
| |
| input = qmi_message_uim_change_pin_input_new (); |
| qmi_message_uim_change_pin_input_set_info ( |
| input, |
| QMI_UIM_PIN_ID_PIN1, |
| ctx->old_pin, |
| ctx->new_pin, |
| NULL); |
| aid = g_array_new (FALSE, FALSE, sizeof (guint8)); /* empty AID */ |
| qmi_message_uim_change_pin_input_set_session ( |
| input, |
| QMI_UIM_SESSION_TYPE_CARD_SLOT_1, |
| aid, |
| NULL); |
| g_array_unref (aid); |
| qmi_client_uim_change_pin (QMI_CLIENT_UIM (client), |
| input, |
| 5, |
| NULL, |
| (GAsyncReadyCallback) uim_change_pin_ready, |
| task); |
| qmi_message_uim_change_pin_input_unref (input); |
| } |
| |
| static void |
| dms_uim_change_pin_ready (QmiClientDms *client, |
| GAsyncResult *res, |
| GTask *task) |
| { |
| QmiMessageDmsUimChangePinOutput *output = NULL; |
| GError *error = NULL; |
| |
| output = qmi_client_dms_uim_change_pin_finish (client, res, &error); |
| if (!output) { |
| g_prefix_error (&error, "QMI operation failed: "); |
| g_task_return_error (task, error); |
| } else if (!qmi_message_dms_uim_change_pin_output_get_result (output, &error)) { |
| g_prefix_error (&error, "Couldn't change PIN: "); |
| g_task_return_error (task, pin_qmi_error_to_mobile_equipment_error (error)); |
| } else |
| g_task_return_boolean (task, TRUE); |
| |
| if (output) |
| qmi_message_dms_uim_change_pin_output_unref (output); |
| g_object_unref (task); |
| } |
| |
| static void |
| dms_uim_change_pin (MMSimQmi *self, |
| GTask *task) |
| { |
| QmiMessageDmsUimChangePinInput *input; |
| QmiClient *client = NULL; |
| ChangePinContext *ctx; |
| |
| if (!ensure_qmi_client (NULL, |
| self, |
| QMI_SERVICE_DMS, &client)) { |
| /* Very unlikely that this will ever happen, but anyway, try with |
| * UIM service instead */ |
| uim_change_pin (self, task); |
| return; |
| } |
| |
| ctx = g_task_get_task_data (task); |
| |
| input = qmi_message_dms_uim_change_pin_input_new (); |
| qmi_message_dms_uim_change_pin_input_set_info ( |
| input, |
| QMI_DMS_UIM_PIN_ID_PIN, |
| ctx->old_pin, |
| ctx->new_pin, |
| NULL); |
| qmi_client_dms_uim_change_pin (QMI_CLIENT_DMS (client), |
| input, |
| 5, |
| NULL, |
| (GAsyncReadyCallback) dms_uim_change_pin_ready, |
| task); |
| qmi_message_dms_uim_change_pin_input_unref (input); |
| } |
| |
| static void |
| change_pin (MMBaseSim *_self, |
| const gchar *old_pin, |
| const gchar *new_pin, |
| GAsyncReadyCallback callback, |
| gpointer user_data) |
| { |
| GTask *task; |
| ChangePinContext *ctx; |
| MMSimQmi *self; |
| |
| self = MM_SIM_QMI (_self); |
| task = g_task_new (self, NULL, callback, user_data); |
| |
| ctx = g_slice_new (ChangePinContext); |
| ctx->old_pin = g_strdup (old_pin); |
| ctx->new_pin = g_strdup (new_pin); |
| g_task_set_task_data (task, ctx, (GDestroyNotify) change_pin_context_free); |
| |
| mm_obj_dbg (self, "changing PIN..."); |
| if (!self->priv->dms_uim_deprecated) |
| dms_uim_change_pin (self, task); |
| else |
| uim_change_pin (self, task); |
| } |
| |
| /*****************************************************************************/ |
| /* Enable PIN */ |
| |
| typedef struct { |
| gchar *pin; |
| gboolean enabled; |
| } EnablePinContext; |
| |
| static void |
| enable_pin_context_free (EnablePinContext *ctx) |
| { |
| g_free (ctx->pin); |
| g_slice_free (EnablePinContext, ctx); |
| } |
| |
| static gboolean |
| enable_pin_finish (MMBaseSim *self, |
| GAsyncResult *res, |
| GError **error) |
| { |
| return g_task_propagate_boolean (G_TASK (res), error); |
| } |
| |
| static void |
| uim_set_pin_protection_ready (QmiClientUim *client, |
| GAsyncResult *res, |
| GTask *task) |
| { |
| QmiMessageUimSetPinProtectionOutput *output = NULL; |
| GError *error = NULL; |
| |
| output = qmi_client_uim_set_pin_protection_finish (client, res, &error); |
| if (!output) { |
| g_prefix_error (&error, "QMI operation failed: "); |
| g_task_return_error (task, error); |
| } else if (!qmi_message_uim_set_pin_protection_output_get_result (output, &error)) { |
| g_prefix_error (&error, "Couldn't enable PIN: "); |
| g_task_return_error (task, pin_qmi_error_to_mobile_equipment_error (error)); |
| } else |
| g_task_return_boolean (task, TRUE); |
| |
| if (output) |
| qmi_message_uim_set_pin_protection_output_unref (output); |
| g_object_unref (task); |
| } |
| |
| static void |
| uim_enable_pin (MMSimQmi *self, |
| GTask *task) |
| { |
| QmiMessageUimSetPinProtectionInput *input; |
| QmiClient *client = NULL; |
| EnablePinContext *ctx; |
| GArray *aid; |
| |
| if (!ensure_qmi_client (task, |
| MM_SIM_QMI (self), |
| QMI_SERVICE_UIM, &client)) |
| return; |
| |
| ctx = g_task_get_task_data (task); |
| |
| input = qmi_message_uim_set_pin_protection_input_new (); |
| qmi_message_uim_set_pin_protection_input_set_info ( |
| input, |
| QMI_UIM_PIN_ID_PIN1, |
| ctx->enabled, |
| ctx->pin, |
| NULL); |
| aid = g_array_new (FALSE, FALSE, sizeof (guint8)); /* empty AID */ |
| qmi_message_uim_set_pin_protection_input_set_session ( |
| input, |
| QMI_UIM_SESSION_TYPE_CARD_SLOT_1, |
| aid, |
| NULL); |
| g_array_unref (aid); |
| qmi_client_uim_set_pin_protection (QMI_CLIENT_UIM (client), |
| input, |
| 5, |
| NULL, |
| (GAsyncReadyCallback)uim_set_pin_protection_ready, |
| task); |
| qmi_message_uim_set_pin_protection_input_unref (input); |
| } |
| |
| static void |
| dms_uim_set_pin_protection_ready (QmiClientDms *client, |
| GAsyncResult *res, |
| GTask *task) |
| { |
| QmiMessageDmsUimSetPinProtectionOutput *output = NULL; |
| GError *error = NULL; |
| |
| output = qmi_client_dms_uim_set_pin_protection_finish (client, res, &error); |
| if (!output) { |
| g_prefix_error (&error, "QMI operation failed: "); |
| g_task_return_error (task, error); |
| } else if (!qmi_message_dms_uim_set_pin_protection_output_get_result (output, &error)) { |
| g_prefix_error (&error, "Couldn't enable PIN: "); |
| g_task_return_error (task, pin_qmi_error_to_mobile_equipment_error (error)); |
| } else |
| g_task_return_boolean (task, TRUE); |
| |
| if (output) |
| qmi_message_dms_uim_set_pin_protection_output_unref (output); |
| g_object_unref (task); |
| } |
| |
| static void |
| dms_uim_enable_pin (MMSimQmi *self, |
| GTask *task) |
| { |
| QmiMessageDmsUimSetPinProtectionInput *input; |
| QmiClient *client = NULL; |
| EnablePinContext *ctx; |
| |
| if (!ensure_qmi_client (NULL, |
| MM_SIM_QMI (self), |
| QMI_SERVICE_DMS, &client)) { |
| /* Very unlikely that this will ever happen, but anyway, try with |
| * UIM service instead */ |
| uim_enable_pin (self, task); |
| return; |
| } |
| |
| ctx = g_task_get_task_data (task); |
| |
| input = qmi_message_dms_uim_set_pin_protection_input_new (); |
| qmi_message_dms_uim_set_pin_protection_input_set_info ( |
| input, |
| QMI_DMS_UIM_PIN_ID_PIN, |
| ctx->enabled, |
| ctx->pin, |
| NULL); |
| qmi_client_dms_uim_set_pin_protection (QMI_CLIENT_DMS (client), |
| input, |
| 5, |
| NULL, |
| (GAsyncReadyCallback)dms_uim_set_pin_protection_ready, |
| task); |
| qmi_message_dms_uim_set_pin_protection_input_unref (input); |
| } |
| |
| static void |
| enable_pin (MMBaseSim *_self, |
| const gchar *pin, |
| gboolean enabled, |
| GAsyncReadyCallback callback, |
| gpointer user_data) |
| { |
| GTask *task; |
| EnablePinContext *ctx; |
| MMSimQmi *self; |
| |
| self = MM_SIM_QMI (_self); |
| task = g_task_new (self, NULL, callback, user_data); |
| |
| ctx = g_slice_new (EnablePinContext); |
| ctx->pin = g_strdup (pin); |
| ctx->enabled = enabled; |
| g_task_set_task_data (task, ctx, (GDestroyNotify) enable_pin_context_free); |
| |
| mm_obj_dbg (self, "%s PIN...", enabled ? "enabling" : "disabling"); |
| if (!self->priv->dms_uim_deprecated) |
| dms_uim_enable_pin (self, task); |
| else |
| uim_enable_pin (self, task); |
| } |
| |
| /*****************************************************************************/ |
| |
| MMBaseSim * |
| mm_sim_qmi_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_qmi_new (MMBaseModem *modem, |
| gboolean dms_uim_deprecated, |
| GCancellable *cancellable, |
| GAsyncReadyCallback callback, |
| gpointer user_data) |
| { |
| g_async_initable_new_async (MM_TYPE_SIM_QMI, |
| G_PRIORITY_DEFAULT, |
| cancellable, |
| callback, |
| user_data, |
| MM_BASE_SIM_MODEM, modem, |
| MM_BIND_TO, G_OBJECT (modem), |
| MM_SIM_QMI_DMS_UIM_DEPRECATED, dms_uim_deprecated, |
| "active", TRUE, /* by default always active */ |
| NULL); |
| } |
| |
| MMBaseSim * |
| mm_sim_qmi_new_initialized (MMBaseModem *modem, |
| gboolean dms_uim_deprecated, |
| guint slot_number, |
| gboolean active, |
| 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_QMI, |
| MM_BASE_SIM_MODEM, modem, |
| MM_BIND_TO, G_OBJECT (modem), |
| MM_SIM_QMI_DMS_UIM_DEPRECATED, dms_uim_deprecated, |
| MM_BASE_SIM_SLOT_NUMBER, slot_number, |
| "active", active, |
| "sim-identifier", sim_identifier, |
| "imsi", imsi, |
| "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_qmi_init (MMSimQmi *self) |
| { |
| /* Initialize private data */ |
| self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, |
| MM_TYPE_SIM_QMI, |
| MMSimQmiPrivate); |
| } |
| |
| static void |
| set_property (GObject *object, |
| guint prop_id, |
| const GValue *value, |
| GParamSpec *pspec) |
| { |
| MMSimQmi *self = MM_SIM_QMI (object); |
| |
| switch (prop_id) { |
| case PROP_DMS_UIM_DEPRECATED: |
| self->priv->dms_uim_deprecated = g_value_get_boolean (value); |
| break; |
| default: |
| G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); |
| break; |
| } |
| } |
| |
| static void |
| get_property (GObject *object, |
| guint prop_id, |
| GValue *value, |
| GParamSpec *pspec) |
| { |
| MMSimQmi *self = MM_SIM_QMI (object); |
| |
| switch (prop_id) { |
| case PROP_DMS_UIM_DEPRECATED: |
| g_value_set_boolean (value, self->priv->dms_uim_deprecated); |
| break; |
| default: |
| G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); |
| break; |
| } |
| } |
| |
| static void |
| finalize (GObject *object) |
| { |
| MMSimQmi *self = MM_SIM_QMI (object); |
| |
| g_free (self->priv->imsi); |
| |
| G_OBJECT_CLASS (mm_sim_qmi_parent_class)->finalize (object); |
| } |
| |
| static void |
| mm_sim_qmi_class_init (MMSimQmiClass *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 (MMSimQmiPrivate)); |
| |
| object_class->get_property = get_property; |
| object_class->set_property = set_property; |
| object_class->finalize = finalize; |
| |
| base_sim_class->wait_sim_ready = wait_sim_ready; |
| base_sim_class->wait_sim_ready_finish = wait_sim_ready_finish; |
| 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_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_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->load_preferred_networks = load_preferred_networks; |
| base_sim_class->load_preferred_networks_finish = load_preferred_networks_finish; |
| base_sim_class->set_preferred_networks = set_preferred_networks; |
| base_sim_class->set_preferred_networks_finish = set_preferred_networks_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->change_pin = change_pin; |
| base_sim_class->change_pin_finish = change_pin_finish; |
| base_sim_class->enable_pin = enable_pin; |
| base_sim_class->enable_pin_finish = enable_pin_finish; |
| |
| properties[PROP_DMS_UIM_DEPRECATED] = |
| g_param_spec_boolean (MM_SIM_QMI_DMS_UIM_DEPRECATED, |
| "DMS UIM deprecated", |
| "Whether DMS UIM commands should be skipped", |
| FALSE, |
| G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY); |
| g_object_class_install_property (object_class, PROP_DMS_UIM_DEPRECATED, properties[PROP_DMS_UIM_DEPRECATED]); |
| } |