| /* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ |
| /* |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License as published by |
| * the Free Software Foundation; either version 2 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details: |
| * |
| * Copyright (C) 2008 - 2009 Novell, Inc. |
| * Copyright (C) 2009 - 2011 Red Hat, Inc. |
| * Copyright (C) 2011 - 2022 Aleksander Morgado <aleksander@aleksander.es> |
| * Copyright (C) 2011 - 2022 Google, Inc. |
| */ |
| |
| #include <config.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <unistd.h> |
| #include <string.h> |
| #include <ctype.h> |
| |
| #include <ModemManager.h> |
| #define _LIBMM_INSIDE_MM |
| #include <libmm-glib.h> |
| |
| #include "mm-iface-modem.h" |
| #include "mm-base-sim.h" |
| #include "mm-base-modem-at.h" |
| #include "mm-base-modem.h" |
| #include "mm-log-object.h" |
| #include "mm-modem-helpers.h" |
| #include "mm-error-helpers.h" |
| #include "mm-bind.h" |
| |
| static void async_initable_iface_init (GAsyncInitableIface *iface); |
| static void log_object_iface_init (MMLogObjectInterface *iface); |
| static void bind_iface_init (MMBindInterface *iface); |
| |
| G_DEFINE_TYPE_EXTENDED (MMBaseSim, mm_base_sim, MM_GDBUS_TYPE_SIM_SKELETON, 0, |
| G_IMPLEMENT_INTERFACE (G_TYPE_ASYNC_INITABLE, async_initable_iface_init) |
| G_IMPLEMENT_INTERFACE (MM_TYPE_LOG_OBJECT, log_object_iface_init) |
| G_IMPLEMENT_INTERFACE (MM_TYPE_BIND, bind_iface_init)) |
| |
| enum { |
| PROP_0, |
| PROP_PATH, |
| PROP_CONNECTION, |
| PROP_BIND_TO, |
| PROP_MODEM, |
| PROP_SLOT_NUMBER, |
| PROP_LAST |
| }; |
| |
| enum { |
| SIGNAL_PIN_LOCK_ENABLED, |
| SIGNAL_LAST |
| }; |
| |
| static GParamSpec *properties[PROP_LAST]; |
| |
| struct _MMBaseSimPrivate { |
| /* The connection to the system bus */ |
| GDBusConnection *connection; |
| guint dbus_id; |
| |
| /* The authorization provider */ |
| MMAuthProvider *authp; |
| GCancellable *authp_cancellable; |
| |
| /* The object this SIM is bound to */ |
| GObject *bind_to; |
| |
| /* The modem which owns this SIM */ |
| MMBaseModem *modem; |
| /* The path where the SIM object is exported */ |
| gchar *path; |
| |
| /* The SIM slot number, which will be 0 always if the system |
| * doesn't support multiple SIMS. */ |
| guint slot_number; |
| }; |
| |
| static guint signals[SIGNAL_LAST] = { 0 }; |
| |
| /*****************************************************************************/ |
| /* SIM type helpers */ |
| |
| #define IS_PSIM(self) \ |
| (mm_gdbus_sim_get_sim_type (MM_GDBUS_SIM (self)) == MM_SIM_TYPE_PHYSICAL) |
| |
| #define IS_ESIM(self) \ |
| (mm_gdbus_sim_get_sim_type (MM_GDBUS_SIM (self)) == MM_SIM_TYPE_ESIM) |
| |
| #define IS_ESIM_WITHOUT_PROFILES(self) \ |
| (IS_ESIM (self) && (mm_gdbus_sim_get_esim_status (MM_GDBUS_SIM (self)) == MM_SIM_ESIM_STATUS_NO_PROFILES)) |
| |
| gboolean |
| mm_base_sim_is_esim_without_profiles (MMBaseSim *self) |
| { |
| return IS_ESIM_WITHOUT_PROFILES (self); |
| } |
| |
| /*****************************************************************************/ |
| |
| void |
| mm_base_sim_export (MMBaseSim *self) |
| { |
| gchar *path; |
| |
| path = g_strdup_printf (MM_DBUS_SIM_PREFIX "/%d", self->priv->dbus_id); |
| g_object_set (self, |
| MM_BASE_SIM_PATH, path, |
| NULL); |
| g_free (path); |
| } |
| |
| /*****************************************************************************/ |
| /* Reprobe when a puk lock is discovered after pin1_retries are exhausted */ |
| |
| static void |
| reprobe_if_puk_discovered (MMBaseSim *self, |
| GError *error) |
| { |
| if (g_error_matches (error, |
| MM_MOBILE_EQUIPMENT_ERROR, |
| MM_MOBILE_EQUIPMENT_ERROR_SIM_PUK)) { |
| mm_obj_dbg (self, "Discovered PUK lock, discarding old modem..."); |
| mm_iface_modem_process_sim_event (MM_IFACE_MODEM (self->priv->modem)); |
| } |
| } |
| |
| /*****************************************************************************/ |
| /* CHANGE PIN (Generic implementation) */ |
| |
| static gboolean |
| change_pin_finish (MMBaseSim *self, |
| GAsyncResult *res, |
| GError **error) |
| { |
| return g_task_propagate_boolean (G_TASK (res), error); |
| } |
| |
| static void |
| change_pin_ready (MMBaseModem *modem, |
| GAsyncResult *res, |
| GTask *task) |
| { |
| GError *error = NULL; |
| |
| mm_base_modem_at_command_finish (modem, res, &error); |
| if (error) |
| 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) |
| { |
| GTask *task; |
| gchar *command; |
| |
| task = g_task_new (self, NULL, callback, user_data); |
| |
| command = g_strdup_printf ("+CPWD=\"SC\",\"%s\",\"%s\"", |
| old_pin, |
| new_pin); |
| mm_base_modem_at_command (self->priv->modem, |
| command, |
| 3, |
| FALSE, |
| (GAsyncReadyCallback)change_pin_ready, |
| task); |
| g_free (command); |
| } |
| |
| /*****************************************************************************/ |
| /* CHANGE PIN (DBus call handling) */ |
| |
| typedef struct { |
| MMBaseSim *self; |
| GDBusMethodInvocation *invocation; |
| gchar *old_pin; |
| gchar *new_pin; |
| GError *save_error; |
| } HandleChangePinContext; |
| |
| static void |
| handle_change_pin_context_free (HandleChangePinContext *ctx) |
| { |
| g_assert (ctx->save_error == NULL); |
| |
| g_object_unref (ctx->invocation); |
| g_object_unref (ctx->self); |
| g_free (ctx->old_pin); |
| g_free (ctx->new_pin); |
| g_free (ctx); |
| } |
| |
| static void |
| after_change_update_lock_info_ready (MMIfaceModem *modem, |
| GAsyncResult *res, |
| HandleChangePinContext *ctx) |
| { |
| /* We just want to ensure that we tried to update the unlock |
| * retries, no big issue if it failed */ |
| mm_iface_modem_update_lock_info_finish (modem, res, NULL); |
| |
| if (ctx->save_error) { |
| mm_dbus_method_invocation_return_gerror (ctx->invocation, ctx->save_error); |
| reprobe_if_puk_discovered (ctx->self, ctx->save_error); |
| g_clear_error (&ctx->save_error); |
| } else { |
| mm_gdbus_sim_complete_change_pin (MM_GDBUS_SIM (ctx->self), ctx->invocation); |
| } |
| |
| handle_change_pin_context_free (ctx); |
| } |
| |
| static void |
| handle_change_pin_ready (MMBaseSim *self, |
| GAsyncResult *res, |
| HandleChangePinContext *ctx) |
| { |
| MMModemLock known_lock = MM_MODEM_LOCK_UNKNOWN; |
| |
| if (!MM_BASE_SIM_GET_CLASS (self)->change_pin_finish (self, res, &ctx->save_error)) { |
| if (g_error_matches (ctx->save_error, |
| MM_MOBILE_EQUIPMENT_ERROR, |
| MM_MOBILE_EQUIPMENT_ERROR_SIM_PUK)) |
| known_lock = MM_MODEM_LOCK_SIM_PUK; |
| } |
| |
| mm_iface_modem_update_lock_info ( |
| MM_IFACE_MODEM (self->priv->modem), |
| known_lock, |
| (GAsyncReadyCallback)after_change_update_lock_info_ready, |
| ctx); |
| } |
| |
| static void |
| handle_change_pin_auth_ready (MMAuthProvider *authp, |
| GAsyncResult *res, |
| HandleChangePinContext *ctx) |
| { |
| GError *error = NULL; |
| |
| if (!mm_auth_provider_authorize_finish (authp, res, &error)) { |
| mm_dbus_method_invocation_take_error (ctx->invocation, error); |
| handle_change_pin_context_free (ctx); |
| return; |
| } |
| |
| /* If changing PIN is not implemented, report an error */ |
| if (!MM_BASE_SIM_GET_CLASS (ctx->self)->change_pin || |
| !MM_BASE_SIM_GET_CLASS (ctx->self)->change_pin_finish) { |
| mm_dbus_method_invocation_return_error_literal (ctx->invocation, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED, |
| "Cannot change PIN: operation not supported"); |
| handle_change_pin_context_free (ctx); |
| return; |
| } |
| |
| if (!mm_gdbus_sim_get_active (MM_GDBUS_SIM (ctx->self))) { |
| mm_dbus_method_invocation_return_error_literal (ctx->invocation, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED, |
| "Cannot change PIN: SIM not currently active"); |
| handle_change_pin_context_free (ctx); |
| return; |
| } |
| |
| if (IS_ESIM_WITHOUT_PROFILES (ctx->self)) { |
| mm_dbus_method_invocation_return_error_literal (ctx->invocation, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED, |
| "Cannot change PIN: eSIM without profiles"); |
| handle_change_pin_context_free (ctx); |
| return; |
| } |
| |
| mm_obj_info (ctx->self, "processing user request to change PIN..."); |
| MM_BASE_SIM_GET_CLASS (ctx->self)->change_pin (ctx->self, |
| ctx->old_pin, |
| ctx->new_pin, |
| (GAsyncReadyCallback)handle_change_pin_ready, |
| ctx); |
| } |
| |
| static gboolean |
| handle_change_pin (MMBaseSim *self, |
| GDBusMethodInvocation *invocation, |
| const gchar *old_pin, |
| const gchar *new_pin, |
| gboolean changed) |
| { |
| HandleChangePinContext *ctx; |
| |
| ctx = g_new0 (HandleChangePinContext, 1); |
| ctx->self = g_object_ref (self); |
| ctx->invocation = g_object_ref (invocation); |
| ctx->old_pin = g_strdup (old_pin); |
| ctx->new_pin = g_strdup (new_pin); |
| |
| mm_auth_provider_authorize (self->priv->authp, |
| invocation, |
| MM_AUTHORIZATION_DEVICE_CONTROL, |
| self->priv->authp_cancellable, |
| (GAsyncReadyCallback)handle_change_pin_auth_ready, |
| ctx); |
| return TRUE; |
| } |
| |
| /*****************************************************************************/ |
| /* ENABLE PIN (Generic implementation) */ |
| |
| static gboolean |
| enable_pin_finish (MMBaseSim *self, |
| GAsyncResult *res, |
| GError **error) |
| { |
| return g_task_propagate_boolean (G_TASK (res), error); |
| } |
| |
| static void |
| enable_pin_ready (MMBaseModem *modem, |
| GAsyncResult *res, |
| GTask *task) |
| { |
| GError *error = NULL; |
| |
| mm_base_modem_at_command_finish (modem, res, &error); |
| if (error) |
| 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) |
| { |
| GTask *task; |
| gchar *command; |
| |
| task = g_task_new (self, NULL, callback, user_data); |
| |
| command = g_strdup_printf ("+CLCK=\"SC\",%d,\"%s\"", |
| enabled ? 1 : 0, |
| pin); |
| mm_base_modem_at_command (self->priv->modem, |
| command, |
| 3, |
| FALSE, |
| (GAsyncReadyCallback)enable_pin_ready, |
| task); |
| g_free (command); |
| } |
| |
| /*****************************************************************************/ |
| /* ENABLE PIN (DBus call handling) */ |
| |
| typedef struct { |
| MMBaseSim *self; |
| GDBusMethodInvocation *invocation; |
| gchar *pin; |
| gboolean enabled; |
| GError *save_error; |
| } HandleEnablePinContext; |
| |
| static void |
| handle_enable_pin_context_free (HandleEnablePinContext *ctx) |
| { |
| g_assert (ctx->save_error == NULL); |
| |
| g_object_unref (ctx->invocation); |
| g_object_unref (ctx->self); |
| g_free (ctx->pin); |
| g_free (ctx); |
| } |
| |
| static void |
| after_enable_update_lock_info_ready (MMIfaceModem *modem, |
| GAsyncResult *res, |
| HandleEnablePinContext *ctx) |
| { |
| /* We just want to ensure that we tried to update the unlock |
| * retries, no big issue if it failed */ |
| mm_iface_modem_update_lock_info_finish (modem, res, NULL); |
| |
| if (ctx->save_error) { |
| mm_dbus_method_invocation_return_gerror (ctx->invocation, ctx->save_error); |
| reprobe_if_puk_discovered (ctx->self, ctx->save_error); |
| g_clear_error (&ctx->save_error); |
| } else { |
| /* Signal about the new lock state */ |
| g_signal_emit (ctx->self, signals[SIGNAL_PIN_LOCK_ENABLED], 0, ctx->enabled); |
| mm_gdbus_sim_complete_enable_pin (MM_GDBUS_SIM (ctx->self), ctx->invocation); |
| } |
| |
| handle_enable_pin_context_free (ctx); |
| } |
| |
| static void |
| handle_enable_pin_ready (MMBaseSim *self, |
| GAsyncResult *res, |
| HandleEnablePinContext *ctx) |
| { |
| MMModemLock known_lock = MM_MODEM_LOCK_UNKNOWN; |
| |
| if (!MM_BASE_SIM_GET_CLASS (self)->enable_pin_finish (self, res, &ctx->save_error)) { |
| if (g_error_matches (ctx->save_error, |
| MM_MOBILE_EQUIPMENT_ERROR, |
| MM_MOBILE_EQUIPMENT_ERROR_SIM_PUK)) |
| known_lock = MM_MODEM_LOCK_SIM_PUK; |
| } |
| |
| mm_iface_modem_update_lock_info ( |
| MM_IFACE_MODEM (self->priv->modem), |
| known_lock, |
| (GAsyncReadyCallback)after_enable_update_lock_info_ready, |
| ctx); |
| } |
| |
| static void |
| handle_enable_pin_auth_ready (MMAuthProvider *authp, |
| GAsyncResult *res, |
| HandleEnablePinContext *ctx) |
| { |
| GError *error = NULL; |
| |
| if (!mm_auth_provider_authorize_finish (authp, res, &error)) { |
| mm_dbus_method_invocation_take_error (ctx->invocation, error); |
| handle_enable_pin_context_free (ctx); |
| return; |
| } |
| |
| /* If changing PIN is not implemented, report an error */ |
| if (!MM_BASE_SIM_GET_CLASS (ctx->self)->enable_pin || |
| !MM_BASE_SIM_GET_CLASS (ctx->self)->enable_pin_finish) { |
| mm_dbus_method_invocation_return_error_literal (ctx->invocation, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED, |
| "Cannot enable/disable PIN: operation not supported"); |
| handle_enable_pin_context_free (ctx); |
| return; |
| } |
| |
| if (!mm_gdbus_sim_get_active (MM_GDBUS_SIM (ctx->self))) { |
| mm_dbus_method_invocation_return_error_literal (ctx->invocation, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED, |
| "Cannot enable/disable PIN: SIM not currently active"); |
| handle_enable_pin_context_free (ctx); |
| return; |
| } |
| |
| if (IS_ESIM_WITHOUT_PROFILES (ctx->self)) { |
| mm_dbus_method_invocation_return_error (ctx->invocation, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED, |
| "Cannot enable/disable PIN: eSIM without profiles"); |
| handle_enable_pin_context_free (ctx); |
| return; |
| } |
| |
| mm_obj_info (ctx->self, "processing user request to %s PIN...", ctx->enabled ? "enable" : "disable"); |
| MM_BASE_SIM_GET_CLASS (ctx->self)->enable_pin (ctx->self, |
| ctx->pin, |
| ctx->enabled, |
| (GAsyncReadyCallback)handle_enable_pin_ready, |
| ctx); |
| } |
| |
| static gboolean |
| handle_enable_pin (MMBaseSim *self, |
| GDBusMethodInvocation *invocation, |
| const gchar *pin, |
| gboolean enabled) |
| { |
| HandleEnablePinContext *ctx; |
| |
| ctx = g_new0 (HandleEnablePinContext, 1); |
| ctx->self = g_object_ref (self); |
| ctx->invocation = g_object_ref (invocation); |
| ctx->pin = g_strdup (pin); |
| ctx->enabled = enabled; |
| |
| mm_auth_provider_authorize (self->priv->authp, |
| invocation, |
| MM_AUTHORIZATION_DEVICE_CONTROL, |
| self->priv->authp_cancellable, |
| (GAsyncReadyCallback)handle_enable_pin_auth_ready, |
| ctx); |
| return TRUE; |
| } |
| |
| /*****************************************************************************/ |
| /* SEND PIN/PUK (Generic implementation) */ |
| |
| static gboolean |
| common_send_pin_puk_finish (MMBaseSim *self, |
| GAsyncResult *res, |
| GError **error) |
| { |
| return g_task_propagate_boolean (G_TASK (res), error); |
| } |
| |
| static void |
| send_pin_puk_ready (MMBaseModem *modem, |
| GAsyncResult *res, |
| GTask *task) |
| { |
| GError *error = NULL; |
| |
| mm_base_modem_at_command_finish (modem, res, &error); |
| if (error) |
| g_task_return_error (task, error); |
| else |
| g_task_return_boolean (task, TRUE); |
| |
| g_object_unref (task); |
| } |
| |
| static void |
| common_send_pin_puk (MMBaseSim *self, |
| const gchar *pin, |
| const gchar *puk, |
| GAsyncReadyCallback callback, |
| gpointer user_data) |
| { |
| GTask *task; |
| gchar *command; |
| |
| task = g_task_new (self, NULL, callback, user_data); |
| |
| command = (puk ? |
| g_strdup_printf ("+CPIN=\"%s\",\"%s\"", puk, pin) : |
| g_strdup_printf ("+CPIN=\"%s\"", pin)); |
| |
| mm_base_modem_at_command (self->priv->modem, |
| command, |
| 3, |
| FALSE, |
| (GAsyncReadyCallback)send_pin_puk_ready, |
| task); |
| g_free (command); |
| } |
| |
| static void |
| send_puk (MMBaseSim *self, |
| const gchar *puk, |
| const gchar *new_pin, |
| GAsyncReadyCallback callback, |
| gpointer user_data) |
| { |
| common_send_pin_puk (self, new_pin, puk, callback, user_data); |
| } |
| |
| static void |
| send_pin (MMBaseSim *self, |
| const gchar *pin, |
| GAsyncReadyCallback callback, |
| gpointer user_data) |
| { |
| common_send_pin_puk (self, pin, NULL, callback, user_data); |
| } |
| |
| /*****************************************************************************/ |
| /* SEND PIN/PUK (common logic) */ |
| |
| static GError * |
| error_for_unlock_check (MMModemLock lock) |
| { |
| static const MMMobileEquipmentError errors_for_locks [] = { |
| MM_MOBILE_EQUIPMENT_ERROR_UNKNOWN, /* MM_MODEM_LOCK_UNKNOWN */ |
| MM_MOBILE_EQUIPMENT_ERROR_UNKNOWN, /* MM_MODEM_LOCK_NONE */ |
| MM_MOBILE_EQUIPMENT_ERROR_SIM_PIN, /* MM_MODEM_LOCK_SIM_PIN */ |
| MM_MOBILE_EQUIPMENT_ERROR_SIM_PIN2, /* MM_MODEM_LOCK_SIM_PIN2 */ |
| MM_MOBILE_EQUIPMENT_ERROR_SIM_PUK, /* MM_MODEM_LOCK_SIM_PUK */ |
| MM_MOBILE_EQUIPMENT_ERROR_SIM_PUK2, /* MM_MODEM_LOCK_SIM_PUK2 */ |
| MM_MOBILE_EQUIPMENT_ERROR_SERVICE_PIN, /* MM_MODEM_LOCK_PH_SP_PIN */ |
| MM_MOBILE_EQUIPMENT_ERROR_SERVICE_PUK, /* MM_MODEM_LOCK_PH_SP_PUK */ |
| MM_MOBILE_EQUIPMENT_ERROR_NETWORK_PIN, /* MM_MODEM_LOCK_PH_NET_PIN */ |
| MM_MOBILE_EQUIPMENT_ERROR_NETWORK_PUK, /* MM_MODEM_LOCK_PH_NET_PUK */ |
| MM_MOBILE_EQUIPMENT_ERROR_PH_SIM_PIN, /* MM_MODEM_LOCK_PH_SIM_PIN */ |
| MM_MOBILE_EQUIPMENT_ERROR_CORP_PIN, /* MM_MODEM_LOCK_PH_CORP_PIN */ |
| MM_MOBILE_EQUIPMENT_ERROR_CORP_PUK, /* MM_MODEM_LOCK_PH_CORP_PUK */ |
| MM_MOBILE_EQUIPMENT_ERROR_PH_FSIM_PIN, /* MM_MODEM_LOCK_PH_FSIM_PIN */ |
| MM_MOBILE_EQUIPMENT_ERROR_PH_FSIM_PUK, /* MM_MODEM_LOCK_PH_FSIM_PUK */ |
| MM_MOBILE_EQUIPMENT_ERROR_NETWORK_SUBSET_PIN, /* MM_MODEM_LOCK_PH_NETSUB_PIN */ |
| MM_MOBILE_EQUIPMENT_ERROR_NETWORK_SUBSET_PUK, /* MM_MODEM_LOCK_PH_NETSUB_PUK */ |
| }; |
| |
| g_assert (lock >= MM_MODEM_LOCK_UNKNOWN); |
| g_assert (lock <= MM_MODEM_LOCK_PH_NETSUB_PUK); |
| |
| return g_error_new (MM_MOBILE_EQUIPMENT_ERROR, |
| errors_for_locks[lock], |
| "Device is locked: '%s'", |
| mm_modem_lock_get_string (lock)); |
| } |
| |
| gboolean |
| mm_base_sim_send_pin_finish (MMBaseSim *self, |
| GAsyncResult *res, |
| GError **error) |
| { |
| return g_task_propagate_boolean (G_TASK (res), error); |
| } |
| |
| gboolean |
| mm_base_sim_send_puk_finish (MMBaseSim *self, |
| GAsyncResult *res, |
| GError **error) |
| { |
| return g_task_propagate_boolean (G_TASK (res), error); |
| } |
| |
| static void |
| update_lock_info_ready (MMIfaceModem *modem, |
| GAsyncResult *res, |
| GTask *task) |
| { |
| GError *error = NULL; |
| MMModemLock lock; |
| |
| lock = mm_iface_modem_update_lock_info_finish (modem, res, &error); |
| /* Consider it only an error if SIM-PIN/PUK is locked or lock is unknown */ |
| if (lock == MM_MODEM_LOCK_UNKNOWN || |
| lock == MM_MODEM_LOCK_SIM_PIN || |
| lock == MM_MODEM_LOCK_SIM_PUK) { |
| const GError *saved_error; |
| |
| /* Device is locked. Now: |
| * - If we got an error during update_lock_info, report it. The sim might have been blocked. |
| * - If we got an error in the original send-pin action, report it. |
| * - Otherwise, build our own error from the lock code. |
| */ |
| if (!error) { |
| saved_error = g_task_get_task_data (task); |
| if (saved_error) |
| error = g_error_copy (saved_error); |
| else |
| error = error_for_unlock_check (lock); |
| } |
| g_task_return_error (task, error); |
| } else |
| g_task_return_boolean (task, TRUE); |
| |
| g_object_unref (task); |
| } |
| |
| static void |
| send_pin_ready (MMBaseSim *self, |
| GAsyncResult *res, |
| GTask *task) |
| { |
| GError *error = NULL; |
| MMModemLock known_lock = MM_MODEM_LOCK_UNKNOWN; |
| |
| if (!MM_BASE_SIM_GET_CLASS (self)->send_pin_finish (self, res, &error)) { |
| g_task_set_task_data (task, error, (GDestroyNotify)g_error_free); |
| if (g_error_matches (error, |
| MM_MOBILE_EQUIPMENT_ERROR, |
| MM_MOBILE_EQUIPMENT_ERROR_SIM_PUK)) |
| known_lock = MM_MODEM_LOCK_SIM_PUK; |
| } |
| |
| /* Once pin/puk has been sent, recheck lock */ |
| mm_iface_modem_update_lock_info ( |
| MM_IFACE_MODEM (self->priv->modem), |
| known_lock, |
| (GAsyncReadyCallback)update_lock_info_ready, |
| task); |
| } |
| |
| static void |
| send_puk_ready (MMBaseSim *self, |
| GAsyncResult *res, |
| GTask *task) |
| { |
| GError *error = NULL; |
| |
| if (!MM_BASE_SIM_GET_CLASS (self)->send_puk_finish (self, res, &error)) |
| g_task_set_task_data (task, error, (GDestroyNotify)g_error_free); |
| |
| /* Once pin/puk has been sent, recheck lock */ |
| mm_iface_modem_update_lock_info (MM_IFACE_MODEM (self->priv->modem), |
| MM_MODEM_LOCK_UNKNOWN, /* ask */ |
| (GAsyncReadyCallback)update_lock_info_ready, |
| task); |
| } |
| |
| void |
| mm_base_sim_send_pin (MMBaseSim *self, |
| const gchar *pin, |
| GAsyncReadyCallback callback, |
| gpointer user_data) |
| { |
| GTask *task; |
| |
| task = g_task_new (self, NULL, callback, user_data); |
| |
| /* If sending PIN is not implemented, report an error */ |
| if (!MM_BASE_SIM_GET_CLASS (self)->send_pin || |
| !MM_BASE_SIM_GET_CLASS (self)->send_pin_finish) { |
| g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED, |
| "Cannot send PIN: operation not supported"); |
| g_object_unref (task); |
| return; |
| } |
| |
| /* Only allow sending SIM-PIN if really SIM-PIN locked */ |
| if (mm_iface_modem_get_unlock_required (MM_IFACE_MODEM (self->priv->modem)) != MM_MODEM_LOCK_SIM_PIN) { |
| g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_WRONG_STATE, |
| "Cannot send PIN: device is not SIM-PIN locked"); |
| g_object_unref (task); |
| return; |
| } |
| |
| MM_BASE_SIM_GET_CLASS (self)->send_pin (self, |
| pin, |
| (GAsyncReadyCallback)send_pin_ready, |
| task); |
| } |
| |
| void |
| mm_base_sim_send_puk (MMBaseSim *self, |
| const gchar *puk, |
| const gchar *new_pin, |
| GAsyncReadyCallback callback, |
| gpointer user_data) |
| { |
| GTask *task; |
| |
| task = g_task_new (self, NULL, callback, user_data); |
| |
| /* If sending PIN is not implemented, report an error */ |
| if (!MM_BASE_SIM_GET_CLASS (self)->send_puk || |
| !MM_BASE_SIM_GET_CLASS (self)->send_puk_finish) { |
| g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED, |
| "Cannot send PUK: operation not supported"); |
| g_object_unref (task); |
| return; |
| } |
| |
| /* Only allow sending SIM-PUK if really SIM-PUK locked */ |
| if (mm_iface_modem_get_unlock_required (MM_IFACE_MODEM (self->priv->modem)) != MM_MODEM_LOCK_SIM_PUK) { |
| g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_WRONG_STATE, |
| "Cannot send PUK: device is not SIM-PUK locked"); |
| g_object_unref (task); |
| return; |
| } |
| |
| MM_BASE_SIM_GET_CLASS (self)->send_puk (self, |
| puk, |
| new_pin, |
| (GAsyncReadyCallback)send_puk_ready, |
| task); |
| } |
| |
| /*****************************************************************************/ |
| /* LOAD SIM IDENTIFIER */ |
| |
| gchar * |
| mm_base_sim_load_sim_identifier_finish (MMBaseSim *self, |
| GAsyncResult *res, |
| GError **error) |
| { |
| return g_task_propagate_pointer (G_TASK (res), error); |
| } |
| |
| static void |
| load_sim_identifier_ready (MMBaseSim *self, |
| GAsyncResult *res, |
| GTask *task) |
| { |
| gchar *simid; |
| GError *error = NULL; |
| |
| simid = MM_BASE_SIM_GET_CLASS (self)->load_sim_identifier_finish (self, res, &error); |
| if (!simid) |
| g_task_return_error (task, error); |
| else |
| g_task_return_pointer (task, simid, g_free); |
| g_object_unref (task); |
| } |
| |
| void |
| mm_base_sim_load_sim_identifier (MMBaseSim *self, |
| GAsyncReadyCallback callback, |
| gpointer user_data) |
| { |
| GTask *task; |
| |
| task = g_task_new (self, NULL, callback, user_data); |
| |
| if (!MM_BASE_SIM_GET_CLASS (self)->load_sim_identifier || |
| !MM_BASE_SIM_GET_CLASS (self)->load_sim_identifier_finish) { |
| g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED, |
| "not implemented"); |
| g_object_unref (task); |
| return; |
| } |
| |
| if (IS_ESIM_WITHOUT_PROFILES (self)) { |
| g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED, |
| "no SIM identifier in eSIM without profiles"); |
| g_object_unref (task); |
| return; |
| } |
| |
| MM_BASE_SIM_GET_CLASS (self)->load_sim_identifier ( |
| self, |
| (GAsyncReadyCallback)load_sim_identifier_ready, |
| task); |
| } |
| |
| /*****************************************************************************/ |
| /* SEND PIN (DBus call handling) */ |
| |
| typedef struct { |
| MMBaseSim *self; |
| GDBusMethodInvocation *invocation; |
| gchar *pin; |
| } HandleSendPinContext; |
| |
| static void |
| handle_send_pin_context_free (HandleSendPinContext *ctx) |
| { |
| g_object_unref (ctx->invocation); |
| g_object_unref (ctx->self); |
| g_free (ctx->pin); |
| g_free (ctx); |
| } |
| |
| static void |
| handle_send_pin_ready (MMBaseSim *self, |
| GAsyncResult *res, |
| HandleSendPinContext *ctx) |
| { |
| GError *error = NULL; |
| |
| if (!mm_base_sim_send_pin_finish (self, res, &error)) { |
| mm_dbus_method_invocation_return_gerror (ctx->invocation, error); |
| reprobe_if_puk_discovered (self, error); |
| g_clear_error (&error); |
| } else |
| mm_gdbus_sim_complete_send_pin (MM_GDBUS_SIM (self), ctx->invocation); |
| |
| handle_send_pin_context_free (ctx); |
| } |
| |
| static void |
| handle_send_pin_auth_ready (MMAuthProvider *authp, |
| GAsyncResult *res, |
| HandleSendPinContext *ctx) |
| { |
| GError *error = NULL; |
| |
| if (!mm_auth_provider_authorize_finish (authp, res, &error)) { |
| mm_dbus_method_invocation_take_error (ctx->invocation, error); |
| handle_send_pin_context_free (ctx); |
| return; |
| } |
| |
| if (!mm_gdbus_sim_get_active (MM_GDBUS_SIM (ctx->self))) { |
| mm_dbus_method_invocation_return_error_literal (ctx->invocation, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED, |
| "Cannot send PIN: SIM not currently active"); |
| handle_send_pin_context_free (ctx); |
| return; |
| } |
| |
| if (IS_ESIM_WITHOUT_PROFILES (ctx->self)) { |
| mm_dbus_method_invocation_return_error_literal (ctx->invocation, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED, |
| "Cannot send PIN: eSIM without profiles"); |
| handle_send_pin_context_free (ctx); |
| return; |
| } |
| |
| mm_obj_info (ctx->self, "processing user request to send PIN..."); |
| mm_base_sim_send_pin (ctx->self, |
| ctx->pin, |
| (GAsyncReadyCallback)handle_send_pin_ready, |
| ctx); |
| } |
| |
| static gboolean |
| handle_send_pin (MMBaseSim *self, |
| GDBusMethodInvocation *invocation, |
| const gchar *pin) |
| { |
| HandleSendPinContext *ctx; |
| |
| ctx = g_new0 (HandleSendPinContext, 1); |
| ctx->self = g_object_ref (self); |
| ctx->invocation = g_object_ref (invocation); |
| ctx->pin = g_strdup (pin); |
| |
| mm_auth_provider_authorize (self->priv->authp, |
| invocation, |
| MM_AUTHORIZATION_DEVICE_CONTROL, |
| self->priv->authp_cancellable, |
| (GAsyncReadyCallback)handle_send_pin_auth_ready, |
| ctx); |
| return TRUE; |
| } |
| |
| /*****************************************************************************/ |
| /* SEND PUK (DBus call handling) */ |
| |
| typedef struct { |
| MMBaseSim *self; |
| GDBusMethodInvocation *invocation; |
| gchar *puk; |
| gchar *new_pin; |
| } HandleSendPukContext; |
| |
| static void |
| handle_send_puk_context_free (HandleSendPukContext *ctx) |
| { |
| g_object_unref (ctx->invocation); |
| g_object_unref (ctx->self); |
| g_free (ctx->puk); |
| g_free (ctx->new_pin); |
| g_free (ctx); |
| } |
| |
| static void |
| handle_send_puk_ready (MMBaseSim *self, |
| GAsyncResult *res, |
| HandleSendPukContext *ctx) |
| { |
| GError *error = NULL; |
| gboolean sim_error = FALSE; |
| |
| if (!mm_base_sim_send_puk_finish (self, res, &error)) { |
| sim_error = g_error_matches (error, |
| MM_MOBILE_EQUIPMENT_ERROR, |
| MM_MOBILE_EQUIPMENT_ERROR_SIM_NOT_INSERTED) || |
| g_error_matches (error, |
| MM_MOBILE_EQUIPMENT_ERROR, |
| MM_MOBILE_EQUIPMENT_ERROR_SIM_FAILURE) || |
| g_error_matches (error, |
| MM_MOBILE_EQUIPMENT_ERROR, |
| MM_MOBILE_EQUIPMENT_ERROR_SIM_WRONG); |
| mm_dbus_method_invocation_take_error (ctx->invocation, error); |
| } else |
| mm_gdbus_sim_complete_send_puk (MM_GDBUS_SIM (self), ctx->invocation); |
| |
| if (sim_error) { |
| mm_obj_msg (self, "received critical sim error: SIM might be permanently blocked, reprobing..."); |
| mm_iface_modem_process_sim_event (MM_IFACE_MODEM (self->priv->modem)); |
| } |
| |
| handle_send_puk_context_free (ctx); |
| } |
| |
| static void |
| handle_send_puk_auth_ready (MMAuthProvider *authp, |
| GAsyncResult *res, |
| HandleSendPukContext *ctx) |
| { |
| GError *error = NULL; |
| |
| if (!mm_auth_provider_authorize_finish (authp, res, &error)) { |
| mm_dbus_method_invocation_take_error (ctx->invocation, error); |
| handle_send_puk_context_free (ctx); |
| return; |
| } |
| |
| if (!mm_gdbus_sim_get_active (MM_GDBUS_SIM (ctx->self))) { |
| mm_dbus_method_invocation_return_error_literal (ctx->invocation, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED, |
| "Cannot send PUK: SIM not currently active"); |
| handle_send_puk_context_free (ctx); |
| return; |
| } |
| |
| if (IS_ESIM_WITHOUT_PROFILES (ctx->self)) { |
| mm_dbus_method_invocation_return_error_literal (ctx->invocation, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED, |
| "Cannot send PUK: eSIM without profiles"); |
| handle_send_puk_context_free (ctx); |
| return; |
| } |
| |
| mm_obj_info (ctx->self, "processing user request to send PUK..."); |
| mm_base_sim_send_puk (ctx->self, |
| ctx->puk, |
| ctx->new_pin, |
| (GAsyncReadyCallback)handle_send_puk_ready, |
| ctx); |
| } |
| |
| static gboolean |
| handle_send_puk (MMBaseSim *self, |
| GDBusMethodInvocation *invocation, |
| const gchar *puk, |
| const gchar *new_pin) |
| { |
| HandleSendPukContext *ctx; |
| |
| ctx = g_new0 (HandleSendPukContext, 1); |
| ctx->self = g_object_ref (self); |
| ctx->invocation = g_object_ref (invocation); |
| ctx->puk = g_strdup (puk); |
| ctx->new_pin = g_strdup (new_pin); |
| |
| mm_auth_provider_authorize (self->priv->authp, |
| invocation, |
| MM_AUTHORIZATION_DEVICE_CONTROL, |
| self->priv->authp_cancellable, |
| (GAsyncReadyCallback)handle_send_puk_auth_ready, |
| ctx); |
| return TRUE; |
| } |
| |
| /*****************************************************************************/ |
| /* Check if preferred networks is supported. |
| * |
| * Modems like the Intel-based EM7345 fail very badly when CPOL? is run, even |
| * completely blocking the AT port after that. We need to avoid running any |
| * CPOL related command in these modules. |
| */ |
| |
| static gboolean |
| check_preferred_networks_disabled (MMBaseSim *self) |
| { |
| MMPort *primary; |
| |
| primary = MM_PORT (mm_base_modem_peek_port_primary (self->priv->modem)); |
| return (primary ? |
| mm_kernel_device_get_global_property_as_boolean (mm_port_peek_kernel_device (primary), |
| "ID_MM_PREFERRED_NETWORKS_CPOL_DISABLED") : |
| FALSE); |
| } |
| |
| /*****************************************************************************/ |
| /* SET PREFERRED NETWORKS (Generic implementation) */ |
| |
| /* Setting preferred network list with AT+CPOL is a complicated procedure with |
| * the following steps: |
| * 1. Using AT+CPOL=? to get SIM capacity; the capacity is checked to ensure |
| * that the list is not too large for the SIM card. |
| * 2. Reading existing preferred networks from SIM with AT+CPOL?. |
| * 3. Clearing existing networks with a series of AT+CPOL=<index> commands. |
| * 4. Setting the new list by invoking AT+CPOL for each network. |
| * |
| * There are some complications with AT+CPOL handling which makes the work more |
| * difficult for us. It seems that modems can only handle a certain exact number |
| * of access technology identifiers - and this cannot be certainly known in |
| * advance. |
| * |
| * If AT+CPOL? in step 2 returns anything, we can deduce the number of supported |
| * identifiers there. But if there were no networks configured earlier, we must |
| * start with a default based on modem capacity and work our way down from there |
| * if the AT+CPOL command fails. |
| */ |
| |
| static void set_preferred_networks_set_next (MMBaseSim *self, |
| GTask *task); |
| static void set_preferred_networks_clear_next (MMBaseSim *self, |
| GTask *task); |
| |
| typedef struct { |
| GList *set_list; |
| /* AT+CPOL indices that must be cleared before setting the networks. */ |
| GArray *clear_index; |
| /* Number of access technology identifiers we will set. */ |
| guint act_count; |
| /* If TRUE, we know that act_count is something the modem can handle */ |
| gboolean act_count_verified; |
| /* Index of preferred network currently being set (0 = first) */ |
| guint current_write_index; |
| /* Operation error code */ |
| GError *error; |
| } SetPreferredNetworksContext; |
| |
| static void |
| set_preferred_network_context_free (SetPreferredNetworksContext *ctx) |
| { |
| g_list_free_full (ctx->set_list, (GDestroyNotify) mm_sim_preferred_network_free); |
| g_clear_error (&ctx->error); |
| g_array_free (ctx->clear_index, TRUE); |
| 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 |
| parse_old_preferred_networks (const gchar *response, |
| SetPreferredNetworksContext *ctx) |
| { |
| gchar **entries; |
| gchar **iter; |
| |
| entries = g_strsplit_set (response, "\r\n", -1); |
| for (iter = entries; iter && *iter; iter++) { |
| guint index; |
| guint act_count = 0; |
| |
| g_strstrip (*iter); |
| if (strlen (*iter) == 0) |
| continue; |
| |
| if (mm_sim_parse_cpol_query_response (*iter, |
| &index, |
| NULL, NULL, NULL, NULL, NULL, NULL, |
| &act_count, |
| NULL) && index > 0) { |
| /* Remember how many access technologies the modem/SIM can take */ |
| if (!ctx->act_count_verified || act_count > ctx->act_count) { |
| ctx->act_count = act_count; |
| ctx->act_count_verified = TRUE; |
| } |
| |
| /* Store the index to be cleared */ |
| g_array_append_val (ctx->clear_index, index); |
| } |
| } |
| g_strfreev (entries); |
| } |
| |
| /* This function is called only in error case, after reloading the network list from SIM. */ |
| static void |
| set_preferred_networks_reload_ready (MMBaseSim *self, |
| GAsyncResult *res, |
| GTask *task) |
| { |
| g_autoptr(GError) error = NULL; |
| GList *preferred_nets_list; |
| SetPreferredNetworksContext *ctx; |
| |
| ctx = g_task_get_task_data (task); |
| |
| preferred_nets_list = MM_BASE_SIM_GET_CLASS (self)->load_preferred_networks_finish (self, res, &error); |
| if (error) |
| mm_obj_dbg (self, "couldn't load list of preferred networks: %s", error->message); |
| |
| mm_gdbus_sim_set_preferred_networks (MM_GDBUS_SIM (self), |
| mm_sim_preferred_network_list_get_variant (preferred_nets_list)); |
| g_list_free_full (preferred_nets_list, (GDestroyNotify) mm_sim_preferred_network_free); |
| |
| /* Return the original error stored in our context */ |
| g_task_return_error (task, g_steal_pointer (&ctx->error)); |
| g_object_unref (task); |
| } |
| |
| |
| static gboolean |
| set_preferred_networks_retry_command (MMBaseSim *self, |
| SetPreferredNetworksContext *ctx) |
| { |
| /* If we haven't yet determined the number of access technology parameters supported by |
| * the modem, try reducing the count if possible and retry with the reduced count. |
| */ |
| if (!ctx->act_count_verified && ctx->act_count > 0) { |
| ctx->act_count--; |
| mm_obj_dbg (self, "retrying operation with %u access technologies", ctx->act_count); |
| return TRUE; |
| } |
| return FALSE; |
| } |
| |
| static void |
| set_preferred_network_reload_and_return_error (MMBaseSim *self, |
| GTask *task, |
| GError *error) |
| { |
| SetPreferredNetworksContext *ctx; |
| |
| ctx = g_task_get_task_data (task); |
| |
| /* Reload the complete list from SIM card to ensure that the PreferredNetworks |
| * property matches with whatever is actually on the SIM. |
| */ |
| if (MM_BASE_SIM_GET_CLASS (self)->load_preferred_networks && |
| MM_BASE_SIM_GET_CLASS (self)->load_preferred_networks_finish) { |
| ctx->error = error; |
| MM_BASE_SIM_GET_CLASS (self)->load_preferred_networks ( |
| self, |
| (GAsyncReadyCallback)set_preferred_networks_reload_ready, |
| task); |
| } else { |
| g_task_return_error (task, error); |
| g_object_unref (task); |
| } |
| } |
| |
| static void |
| set_preferred_networks_set_ready (MMBaseModem *modem, |
| GAsyncResult *res, |
| GTask *task) |
| { |
| MMBaseSim *self; |
| SetPreferredNetworksContext *ctx; |
| GError *error = NULL; |
| |
| self = g_task_get_source_object (task); |
| ctx = g_task_get_task_data (task); |
| |
| mm_base_modem_at_command_finish (modem, res, &error); |
| /* The command may fail; check if we can retry */ |
| if (error) { |
| if (!set_preferred_networks_retry_command (self, ctx)) { |
| /* Retrying not possible, failing... */ |
| mm_obj_warn (self, "failed to set preferred networks: '%s'", error->message); |
| set_preferred_network_reload_and_return_error (self, task, error); |
| return; |
| } |
| /* Retrying possible */ |
| g_clear_error (&error); |
| } else { |
| /* Last set operation was successful, so we know for sure how many access technologies |
| * the modem can take. |
| */ |
| ctx->act_count_verified = TRUE; |
| ctx->current_write_index++; |
| } |
| |
| set_preferred_networks_set_next (self, task); |
| } |
| |
| static gboolean |
| set_preferred_networks_check_support (MMBaseSim *self, |
| SetPreferredNetworksContext *ctx, |
| MMSimPreferredNetwork *network, |
| GError **error) |
| { |
| MMModemAccessTechnology requested_act; |
| MMModemAccessTechnology supported_act = MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN; |
| |
| requested_act = mm_sim_preferred_network_get_access_technology (network); |
| if (ctx->act_count >= 1) |
| supported_act |= MM_MODEM_ACCESS_TECHNOLOGY_GSM; |
| if (ctx->act_count >= 2) |
| supported_act |= MM_MODEM_ACCESS_TECHNOLOGY_GSM_COMPACT; |
| if (ctx->act_count >= 3) |
| supported_act |= MM_MODEM_ACCESS_TECHNOLOGY_UMTS; |
| if (ctx->act_count >= 4) |
| supported_act |= MM_MODEM_ACCESS_TECHNOLOGY_LTE; |
| if (ctx->act_count >= 5) |
| supported_act |= MM_MODEM_ACCESS_TECHNOLOGY_5GNR; |
| |
| if (requested_act & ~supported_act) { |
| g_autofree gchar *act_string = NULL; |
| |
| act_string = mm_modem_access_technology_build_string_from_mask (requested_act & ~supported_act); |
| mm_obj_warn (self, "cannot set preferred net '%s'; access technology '%s' not supported by modem/SIM", |
| mm_sim_preferred_network_get_operator_code (network), act_string); |
| |
| g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED, |
| "Access technology unsupported by modem or SIM"); |
| return FALSE; |
| } |
| return TRUE; |
| } |
| |
| /* Set next preferred network in queue */ |
| static void |
| set_preferred_networks_set_next (MMBaseSim *self, |
| GTask *task) |
| { |
| SetPreferredNetworksContext *ctx; |
| g_autofree gchar *command = NULL; |
| MMSimPreferredNetwork *current_network; |
| const gchar *operator_code; |
| MMModemAccessTechnology act; |
| GError *error = NULL; |
| |
| ctx = g_task_get_task_data (task); |
| |
| current_network = (MMSimPreferredNetwork *) g_list_nth_data (ctx->set_list, ctx->current_write_index); |
| if (current_network == NULL) { |
| /* No more networks to set; we are done. */ |
| mm_obj_dbg (self, "setting preferred networks completed."); |
| g_task_return_boolean (task, TRUE); |
| g_object_unref (task); |
| return; |
| } |
| |
| if (!set_preferred_networks_check_support (self, ctx, current_network, &error)) { |
| set_preferred_network_reload_and_return_error (self, task, error); |
| return; |
| } |
| |
| operator_code = mm_sim_preferred_network_get_operator_code (current_network); |
| act = mm_sim_preferred_network_get_access_technology (current_network); |
| |
| /* Assemble the command to set the network */ |
| command = g_strdup_printf ("+CPOL=%u,2,\"%s\"%s%s%s%s%s", ctx->current_write_index + 1, operator_code, |
| ctx->act_count == 0 ? "" : ((act & MM_MODEM_ACCESS_TECHNOLOGY_GSM) ? ",1" : ",0"), |
| ctx->act_count <= 1 ? "" : ((act & MM_MODEM_ACCESS_TECHNOLOGY_GSM_COMPACT) ? ",1" : ",0"), |
| ctx->act_count <= 2 ? "" : ((act & MM_MODEM_ACCESS_TECHNOLOGY_UMTS) ? ",1" : ",0"), |
| ctx->act_count <= 3 ? "" : ((act & MM_MODEM_ACCESS_TECHNOLOGY_LTE) ? ",1" : ",0"), |
| ctx->act_count <= 4 ? "" : ((act & MM_MODEM_ACCESS_TECHNOLOGY_5GNR) ? ",1" : ",0")); |
| mm_base_modem_at_command ( |
| self->priv->modem, |
| command, |
| 20, |
| FALSE, |
| (GAsyncReadyCallback)set_preferred_networks_set_ready, |
| task); |
| } |
| |
| static void |
| set_preferred_networks_clear_ready (MMBaseModem *modem, |
| GAsyncResult *res, |
| GTask *task) |
| { |
| MMBaseSim *self; |
| GError *error = NULL; |
| |
| self = g_task_get_source_object (task); |
| |
| mm_base_modem_at_command_finish (modem, res, &error); |
| if (error) { |
| mm_obj_warn (self, "couldn't clear preferred network entry: '%s'", error->message); |
| set_preferred_network_reload_and_return_error (self, task, error); |
| return; |
| } |
| |
| set_preferred_networks_clear_next (self, task); |
| } |
| |
| /* Clear one of the networks in the clear list */ |
| static void |
| set_preferred_networks_clear_next (MMBaseSim *self, |
| GTask *task) |
| { |
| SetPreferredNetworksContext *ctx; |
| g_autofree gchar *command = NULL; |
| guint current_clear_index; |
| |
| ctx = g_task_get_task_data (task); |
| |
| /* Clear from last index to first, since some modems (e.g. u-blox) may shift up items |
| * following the cleared ones. |
| */ |
| if (ctx->clear_index->len > 0) { |
| current_clear_index = g_array_index (ctx->clear_index, guint, ctx->clear_index->len - 1); |
| g_array_remove_index (ctx->clear_index, ctx->clear_index->len - 1); |
| command = g_strdup_printf ("+CPOL=%u", current_clear_index); |
| mm_base_modem_at_command ( |
| self->priv->modem, |
| command, |
| 20, |
| FALSE, |
| (GAsyncReadyCallback)set_preferred_networks_clear_ready, |
| task); |
| return; |
| } |
| /* All clear operations done; start setting new networks */ |
| set_preferred_networks_set_next (self, task); |
| } |
| |
| static void |
| set_preferred_networks_load_existing_ready (MMBaseModem *modem, |
| GAsyncResult *res, |
| GTask *task) |
| { |
| MMBaseSim *self; |
| GError *error = NULL; |
| SetPreferredNetworksContext *ctx; |
| const gchar *response; |
| |
| self = g_task_get_source_object (task); |
| ctx = g_task_get_task_data (task); |
| |
| response = mm_base_modem_at_command_finish (modem, res, &error); |
| if (error) { |
| mm_obj_warn (self, "couldn't load existing preferred network list: '%s'", error->message); |
| g_task_return_error (task, error); |
| g_object_unref (task); |
| return; |
| } |
| |
| parse_old_preferred_networks (response, ctx); |
| set_preferred_networks_clear_next (self, task); |
| } |
| |
| static void |
| set_preferred_networks_query_sim_capacity_ready (MMBaseModem *modem, |
| GAsyncResult *res, |
| GTask *task) |
| { |
| MMBaseSim *self; |
| GError *error = NULL; |
| SetPreferredNetworksContext *ctx; |
| const gchar *response; |
| guint max_index; |
| guint networks_count; |
| |
| self = g_task_get_source_object (task); |
| ctx = g_task_get_task_data (task); |
| |
| response = mm_base_modem_at_command_finish (modem, res, &error); |
| if (error) { |
| mm_obj_warn (self, "couldn't query preferred network list capacity: '%s'", error->message); |
| g_task_return_error (task, error); |
| g_object_unref (task); |
| return; |
| } |
| |
| if (!mm_sim_parse_cpol_test_response (response, NULL, &max_index, &error)) { |
| g_task_return_error (task, error); |
| g_object_unref (task); |
| return; |
| } |
| |
| /* Compare the number of networks to maximum index returned by AT+CPOL=? */ |
| networks_count = g_list_length (ctx->set_list); |
| if (networks_count > max_index) { |
| mm_obj_warn (self, "can't set %u preferred networks; SIM capacity: %u", networks_count, max_index); |
| g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_TOO_MANY, |
| "Too many networks; SIM capacity %u", max_index); |
| g_object_unref (task); |
| return; |
| } |
| |
| mm_obj_dbg (self, "setting %u preferred networks, SIM capacity: %u", networks_count, max_index); |
| mm_base_modem_at_command ( |
| self->priv->modem, |
| "+CPOL?", |
| 20, |
| FALSE, |
| (GAsyncReadyCallback)set_preferred_networks_load_existing_ready, |
| task); |
| } |
| |
| static void |
| set_preferred_networks (MMBaseSim *self, |
| GList *preferred_network_list, |
| GAsyncReadyCallback callback, |
| gpointer user_data) |
| { |
| GTask *task; |
| SetPreferredNetworksContext *ctx; |
| |
| task = g_task_new (self, NULL, callback, user_data); |
| |
| if (check_preferred_networks_disabled (self)) { |
| g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED, |
| "setting preferred networks is unsupported"); |
| g_object_unref (task); |
| return; |
| } |
| |
| mm_obj_dbg (self, "set preferred networks: loading existing networks..."); |
| |
| ctx = g_slice_new0 (SetPreferredNetworksContext); |
| ctx->set_list = mm_sim_preferred_network_list_copy (preferred_network_list); |
| ctx->clear_index = g_array_new (FALSE, TRUE, sizeof (guint)); |
| if (mm_iface_modem_is_5g (MM_IFACE_MODEM (self->priv->modem))) |
| ctx->act_count = 5; |
| else if (mm_iface_modem_is_4g (MM_IFACE_MODEM (self->priv->modem))) |
| ctx->act_count = 4; |
| else if (mm_iface_modem_is_3g (MM_IFACE_MODEM (self->priv->modem))) |
| ctx->act_count = 3; |
| else if (mm_iface_modem_is_2g (MM_IFACE_MODEM (self->priv->modem))) |
| ctx->act_count = 2; |
| g_task_set_task_data (task, ctx, (GDestroyNotify) set_preferred_network_context_free); |
| |
| /* Query SIM capacity first to find out how many preferred networks it can take */ |
| mm_base_modem_at_command ( |
| self->priv->modem, |
| "+CPOL=?", |
| 20, |
| FALSE, /* Do not cache, the response depends on SIM card properties */ |
| (GAsyncReadyCallback)set_preferred_networks_query_sim_capacity_ready, |
| task); |
| } |
| |
| /*****************************************************************************/ |
| /* SET PREFERRED NETWORKS (DBus call handling) */ |
| |
| typedef struct { |
| MMBaseSim *self; |
| GDBusMethodInvocation *invocation; |
| GVariant *networks; |
| } HandleSetPreferredNetworksContext; |
| |
| static void |
| handle_set_preferred_networks_context_free (HandleSetPreferredNetworksContext *ctx) |
| { |
| g_object_unref (ctx->invocation); |
| g_object_unref (ctx->self); |
| g_variant_unref (ctx->networks); |
| g_free (ctx); |
| } |
| |
| static void |
| handle_set_preferred_networks_ready (MMBaseSim *self, |
| GAsyncResult *res, |
| HandleSetPreferredNetworksContext *ctx) |
| { |
| GError *error = NULL; |
| |
| MM_BASE_SIM_GET_CLASS (self)->set_preferred_networks_finish (self, res, &error); |
| if (error) { |
| mm_obj_warn (self, "couldn't set preferred networks: %s", error->message); |
| mm_dbus_method_invocation_take_error (ctx->invocation, g_steal_pointer (&error)); |
| } else { |
| mm_gdbus_sim_set_preferred_networks (MM_GDBUS_SIM (self), ctx->networks); |
| mm_gdbus_sim_complete_set_preferred_networks (MM_GDBUS_SIM (self), ctx->invocation); |
| } |
| |
| handle_set_preferred_networks_context_free (ctx); |
| } |
| |
| static void |
| handle_set_preferred_networks_auth_ready (MMAuthProvider *authp, |
| GAsyncResult *res, |
| HandleSetPreferredNetworksContext *ctx) |
| { |
| GError *error = NULL; |
| |
| if (!mm_auth_provider_authorize_finish (authp, res, &error)) { |
| mm_dbus_method_invocation_take_error (ctx->invocation, error); |
| handle_set_preferred_networks_context_free (ctx); |
| return; |
| } |
| |
| if (!mm_gdbus_sim_get_active (MM_GDBUS_SIM (ctx->self))) { |
| mm_dbus_method_invocation_return_error_literal (ctx->invocation, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED, |
| "Cannot set preferred networks: SIM not currently active"); |
| handle_set_preferred_networks_context_free (ctx); |
| return; |
| } |
| |
| if (IS_ESIM_WITHOUT_PROFILES (ctx->self)) { |
| mm_dbus_method_invocation_return_error_literal (ctx->invocation, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED, |
| "Cannot set preferred networks: eSIM without profiles"); |
| handle_set_preferred_networks_context_free (ctx); |
| return; |
| } |
| |
| if (!MM_BASE_SIM_GET_CLASS (ctx->self)->set_preferred_networks || |
| !MM_BASE_SIM_GET_CLASS (ctx->self)->set_preferred_networks_finish) { |
| mm_dbus_method_invocation_return_error_literal (ctx->invocation, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED, |
| "Cannot set preferred networks: not implemented"); |
| handle_set_preferred_networks_context_free (ctx); |
| return; |
| } |
| |
| mm_obj_info (ctx->self, "processing user request to set preferred networks..."); |
| MM_BASE_SIM_GET_CLASS (ctx->self)->set_preferred_networks ( |
| ctx->self, |
| mm_sim_preferred_network_list_new_from_variant (ctx->networks), |
| (GAsyncReadyCallback)handle_set_preferred_networks_ready, |
| ctx); |
| } |
| |
| static gboolean |
| handle_set_preferred_networks (MMBaseSim *self, |
| GDBusMethodInvocation *invocation, |
| GVariant *networks_variant) |
| { |
| HandleSetPreferredNetworksContext *ctx; |
| |
| ctx = g_new0 (HandleSetPreferredNetworksContext, 1); |
| ctx->self = g_object_ref (self); |
| ctx->invocation = g_object_ref (invocation); |
| ctx->networks = g_variant_ref (networks_variant); |
| |
| mm_auth_provider_authorize (self->priv->authp, |
| invocation, |
| MM_AUTHORIZATION_DEVICE_CONTROL, |
| self->priv->authp_cancellable, |
| (GAsyncReadyCallback)handle_set_preferred_networks_auth_ready, |
| ctx); |
| return TRUE; |
| } |
| |
| /*****************************************************************************/ |
| |
| static void |
| sim_dbus_export (MMBaseSim *self) |
| { |
| GError *error = NULL; |
| |
| /* Handle method invocations */ |
| g_signal_connect (self, |
| "handle-change-pin", |
| G_CALLBACK (handle_change_pin), |
| NULL); |
| g_signal_connect (self, |
| "handle-enable-pin", |
| G_CALLBACK (handle_enable_pin), |
| NULL); |
| g_signal_connect (self, |
| "handle-send-pin", |
| G_CALLBACK (handle_send_pin), |
| NULL); |
| g_signal_connect (self, |
| "handle-send-puk", |
| G_CALLBACK (handle_send_puk), |
| NULL); |
| g_signal_connect (self, |
| "handle-set-preferred-networks", |
| G_CALLBACK (handle_set_preferred_networks), |
| NULL); |
| |
| if (!g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (self), |
| self->priv->connection, |
| self->priv->path, |
| &error)) { |
| mm_obj_warn (self, "couldn't export SIM: %s", error->message); |
| g_error_free (error); |
| } |
| } |
| |
| static void |
| sim_dbus_unexport (MMBaseSim *self) |
| { |
| /* Only unexport if currently exported */ |
| if (g_dbus_interface_skeleton_get_object_path (G_DBUS_INTERFACE_SKELETON (self))) |
| g_dbus_interface_skeleton_unexport (G_DBUS_INTERFACE_SKELETON (self)); |
| } |
| |
| /*****************************************************************************/ |
| |
| const gchar * |
| mm_base_sim_get_path (MMBaseSim *self) |
| { |
| return self->priv->path; |
| } |
| |
| guint |
| mm_base_sim_get_slot_number (MMBaseSim *self) |
| { |
| return self->priv->slot_number; |
| } |
| |
| /*****************************************************************************/ |
| |
| gboolean |
| mm_base_sim_is_emergency_number (MMBaseSim *self, |
| const gchar *number) |
| { |
| const gchar *const *emergency_numbers; |
| guint i; |
| |
| emergency_numbers = mm_gdbus_sim_get_emergency_numbers (MM_GDBUS_SIM (self)); |
| |
| if (!emergency_numbers) |
| return FALSE; |
| |
| for (i = 0; emergency_numbers[i]; i++) { |
| if (g_strcmp0 (number, emergency_numbers[i]) == 0) |
| return TRUE; |
| } |
| |
| return FALSE; |
| } |
| |
| /*****************************************************************************/ |
| |
| #undef STR_REPLY_READY_FN |
| #define STR_REPLY_READY_FN(NAME) \ |
| static void \ |
| NAME##_command_ready (MMBaseModem *modem, \ |
| GAsyncResult *res, \ |
| GTask *task) \ |
| { \ |
| GError *error = NULL; \ |
| const gchar *response; \ |
| \ |
| response = mm_base_modem_at_command_finish (modem, res, &error); \ |
| if (error) \ |
| g_task_return_error (task, error); \ |
| else \ |
| g_task_return_pointer (task, g_strdup (response), g_free); \ |
| \ |
| g_object_unref (task); \ |
| } |
| |
| /*****************************************************************************/ |
| /* Emergency numbers */ |
| |
| static GStrv |
| parse_emergency_numbers (const gchar *response, |
| GError **error) |
| { |
| guint sw1 = 0; |
| guint sw2 = 0; |
| gchar *hex = 0; |
| GStrv ret; |
| |
| if (!mm_3gpp_parse_crsm_response (response, &sw1, &sw2, &hex, error)) |
| return NULL; |
| |
| if ((sw1 == 0x90 && sw2 == 0x00) || |
| (sw1 == 0x91) || |
| (sw1 == 0x92) || |
| (sw1 == 0x9f)) { |
| ret = mm_3gpp_parse_emergency_numbers (hex, error); |
| g_free (hex); |
| return ret; |
| } |
| |
| g_free (hex); |
| g_set_error (error, |
| MM_CORE_ERROR, |
| MM_CORE_ERROR_FAILED, |
| "SIM failed to handle CRSM request (sw1 %d sw2 %d)", |
| sw1, sw2); |
| return NULL; |
| } |
| |
| static GStrv |
| load_emergency_numbers_finish (MMBaseSim *self, |
| GAsyncResult *res, |
| GError **error) |
| { |
| gchar *result; |
| GStrv emergency_numbers; |
| guint i; |
| |
| result = g_task_propagate_pointer (G_TASK (res), error); |
| if (!result) |
| return NULL; |
| |
| emergency_numbers = parse_emergency_numbers (result, error); |
| g_free (result); |
| |
| if (!emergency_numbers) |
| return NULL; |
| |
| for (i = 0; emergency_numbers[i]; i++) |
| mm_obj_dbg (self, "loaded emergency number: %s", emergency_numbers[i]); |
| |
| return emergency_numbers; |
| } |
| |
| STR_REPLY_READY_FN (load_emergency_numbers) |
| |
| static void |
| load_emergency_numbers (MMBaseSim *self, |
| GAsyncReadyCallback callback, |
| gpointer user_data) |
| { |
| mm_obj_dbg (self, "loading emergency numbers..."); |
| |
| /* READ BINARY of EF_ECC (Emergency Call Codes) ETSI TS 51.011 section 10.3.27 */ |
| mm_base_modem_at_command ( |
| self->priv->modem, |
| "+CRSM=176,28599,0,0,15", |
| 20, |
| FALSE, |
| (GAsyncReadyCallback)load_emergency_numbers_command_ready, |
| g_task_new (self, NULL, callback, user_data)); |
| } |
| |
| /*****************************************************************************/ |
| /* Preferred networks */ |
| |
| static GList * |
| parse_preferred_networks (const gchar *response, |
| GError **error) |
| { |
| gchar **entries; |
| gchar **iter; |
| GList *result = NULL; |
| |
| entries = g_strsplit_set (response, "\r\n", -1); |
| for (iter = entries; iter && *iter; iter++) { |
| gchar *operator_code = NULL; |
| gboolean gsm_act; |
| gboolean gsm_compact_act; |
| gboolean utran_act; |
| gboolean eutran_act; |
| gboolean ngran_act; |
| MMSimPreferredNetwork *preferred_network = NULL; |
| MMModemAccessTechnology act = MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN; |
| |
| g_strstrip (*iter); |
| if (strlen (*iter) == 0) |
| continue; |
| |
| if (mm_sim_parse_cpol_query_response (*iter, |
| NULL, |
| &operator_code, |
| &gsm_act, |
| &gsm_compact_act, |
| &utran_act, |
| &eutran_act, |
| &ngran_act, |
| NULL, |
| error)) { |
| preferred_network = mm_sim_preferred_network_new (); |
| mm_sim_preferred_network_set_operator_code (preferred_network, operator_code); |
| if (gsm_act) |
| act |= MM_MODEM_ACCESS_TECHNOLOGY_GSM; |
| if (gsm_compact_act) |
| act |= MM_MODEM_ACCESS_TECHNOLOGY_GSM_COMPACT; |
| if (utran_act) |
| act |= MM_MODEM_ACCESS_TECHNOLOGY_UMTS; |
| if (eutran_act) |
| act |= MM_MODEM_ACCESS_TECHNOLOGY_LTE; |
| if (ngran_act) |
| act |= MM_MODEM_ACCESS_TECHNOLOGY_5GNR; |
| mm_sim_preferred_network_set_access_technology (preferred_network, act); |
| result = g_list_append (result, preferred_network); |
| } else |
| break; |
| g_free (operator_code); |
| } |
| g_strfreev (entries); |
| |
| return result; |
| } |
| |
| static GList * |
| load_preferred_networks_finish (MMBaseSim *self, |
| GAsyncResult *res, |
| GError **error) |
| { |
| g_autofree gchar *result = NULL; |
| |
| result = g_task_propagate_pointer (G_TASK (res), error); |
| return result ? parse_preferred_networks (result, error) : NULL; |
| } |
| |
| STR_REPLY_READY_FN (load_preferred_networks) |
| |
| static void |
| load_preferred_networks_set_format_ready (MMBaseModem *modem, |
| GAsyncResult *res, |
| GTask *task) |
| { |
| MMBaseSim *self; |
| GError *error = NULL; |
| |
| self = g_task_get_source_object (task); |
| |
| /* Ignore error */ |
| mm_base_modem_at_command_finish (modem, res, &error); |
| if (error) { |
| mm_obj_dbg (self, "setting preferred network list format failed: '%s'", error->message); |
| g_error_free (error); |
| } |
| |
| mm_obj_dbg (self, "loading preferred networks..."); |
| |
| mm_base_modem_at_command ( |
| modem, |
| "+CPOL?", |
| 20, |
| FALSE, |
| (GAsyncReadyCallback)load_preferred_networks_command_ready, |
| task); |
| } |
| |
| static void |
| load_preferred_networks_cpls_command_ready (MMBaseModem *modem, |
| GAsyncResult *res, |
| GTask *task) |
| { |
| MMBaseSim *self; |
| GError *error = NULL; |
| |
| self = g_task_get_source_object (task); |
| |
| /* AT+CPLS may not be supported so we ignore any error and proceed even if it fails */ |
| mm_base_modem_at_command_finish (modem, res, &error); |
| if (error) { |
| mm_obj_dbg (self, "selecting user-defined preferred network list failed: '%s'", error->message); |
| g_error_free (error); |
| } |
| |
| mm_obj_dbg (self, "setting preferred networks format..."); |
| |
| /* Request numeric MCCMNC format */ |
| mm_base_modem_at_command ( |
| modem, |
| "+CPOL=,2", |
| 20, |
| FALSE, |
| (GAsyncReadyCallback)load_preferred_networks_set_format_ready, |
| task); |
| } |
| |
| static void |
| load_preferred_networks (MMBaseSim *self, |
| GAsyncReadyCallback callback, |
| gpointer user_data) |
| { |
| GTask *task; |
| |
| task = g_task_new (self, NULL, callback, user_data); |
| |
| if (check_preferred_networks_disabled (self)) { |
| g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED, |
| "setting preferred networks is unsupported"); |
| g_object_unref (task); |
| return; |
| } |
| |
| /* Invoke AT+CPLS=0 first to make sure the correct (user-defined) preferred network list is selected */ |
| mm_obj_dbg (self, "selecting user-defined preferred network list..."); |
| |
| mm_base_modem_at_command ( |
| self->priv->modem, |
| "+CPLS=0", |
| 20, |
| FALSE, |
| (GAsyncReadyCallback)load_preferred_networks_cpls_command_ready, |
| task); |
| } |
| |
| /*****************************************************************************/ |
| /* ICCID */ |
| |
| static gchar * |
| parse_iccid (const gchar *response, |
| GError **error) |
| { |
| guint sw1 = 0; |
| guint sw2 = 0; |
| gchar *hex = 0; |
| gchar *ret; |
| |
| if (!mm_3gpp_parse_crsm_response (response, |
| &sw1, |
| &sw2, |
| &hex, |
| error)) |
| return NULL; |
| |
| if ((sw1 == 0x90 && sw2 == 0x00) || |
| (sw1 == 0x91) || |
| (sw1 == 0x92) || |
| (sw1 == 0x9f)) { |
| ret = mm_3gpp_parse_iccid (hex, error); |
| g_free (hex); |
| return ret; |
| } else { |
| g_free (hex); |
| g_set_error (error, |
| MM_CORE_ERROR, |
| MM_CORE_ERROR_FAILED, |
| "SIM failed to handle CRSM request (sw1 %d sw2 %d)", |
| sw1, sw2); |
| return NULL; |
| } |
| } |
| |
| static gchar * |
| load_sim_identifier_finish (MMBaseSim *self, |
| GAsyncResult *res, |
| GError **error) |
| { |
| g_autofree gchar *result = NULL; |
| |
| result = g_task_propagate_pointer (G_TASK (res), error); |
| return result ? parse_iccid (result, error) : NULL; |
| } |
| |
| STR_REPLY_READY_FN (load_sim_identifier) |
| |
| static void |
| load_sim_identifier (MMBaseSim *self, |
| GAsyncReadyCallback callback, |
| gpointer user_data) |
| { |
| mm_obj_dbg (self, "loading SIM identifier..."); |
| |
| /* READ BINARY of EFiccid (ICC Identification) ETSI TS 102.221 section 13.2 */ |
| mm_base_modem_at_command ( |
| self->priv->modem, |
| "+CRSM=176,12258,0,0,10", |
| 20, |
| FALSE, |
| (GAsyncReadyCallback)load_sim_identifier_command_ready, |
| g_task_new (self, NULL, callback, user_data)); |
| } |
| |
| /*****************************************************************************/ |
| /* IMSI */ |
| |
| static gchar * |
| parse_imsi (const gchar *response, |
| GError **error) |
| { |
| const gchar *s; |
| gint len; |
| |
| g_assert (response != NULL); |
| |
| for (s = mm_strip_tag (response, "+CIMI"), len = 0; |
| *s; |
| ++s, ++len) { |
| /* IMSI is a number with 15 or less decimal digits. */ |
| if (!isdigit (*s) || len > 15) { |
| g_set_error (error, |
| MM_CORE_ERROR, |
| MM_CORE_ERROR_FAILED, |
| "Invalid +CIMI response '%s'", response ? response : "<null>"); |
| return NULL; |
| } |
| } |
| |
| return g_strdup (response); |
| } |
| |
| static gchar * |
| load_imsi_finish (MMBaseSim *self, |
| GAsyncResult *res, |
| GError **error) |
| { |
| g_autofree gchar *result = NULL; |
| |
| result = g_task_propagate_pointer (G_TASK (res), error); |
| return result ? parse_imsi (result, error) : NULL; |
| } |
| |
| STR_REPLY_READY_FN (load_imsi) |
| |
| static void |
| load_imsi (MMBaseSim *self, |
| GAsyncReadyCallback callback, |
| gpointer user_data) |
| { |
| mm_obj_dbg (self, "loading IMSI..."); |
| |
| mm_base_modem_at_command ( |
| self->priv->modem, |
| "+CIMI", |
| 3, |
| FALSE, |
| (GAsyncReadyCallback)load_imsi_command_ready, |
| g_task_new (self, NULL, callback, user_data)); |
| } |
| |
| /*****************************************************************************/ |
| /* Operator ID */ |
| |
| static guint |
| parse_mnc_length (const gchar *response, |
| GError **error) |
| { |
| guint sw1 = 0; |
| guint sw2 = 0; |
| g_autofree gchar *hex = NULL; |
| |
| if (!mm_3gpp_parse_crsm_response (response, |
| &sw1, |
| &sw2, |
| &hex, |
| error)) |
| return 0; |
| |
| if ((sw1 == 0x90 && sw2 == 0x00) || |
| (sw1 == 0x91) || |
| (sw1 == 0x92) || |
| (sw1 == 0x9f)) { |
| gsize buflen = 0; |
| g_autofree guint8 *bin = NULL; |
| |
| /* Convert hex string to binary */ |
| bin = mm_utils_hexstr2bin (hex, -1, &buflen, error); |
| if (!bin) { |
| g_prefix_error (error, "SIM returned malformed response '%s': ", hex); |
| return 0; |
| } |
| |
| return mm_sim_validate_mnc_length (bin, buflen, error); |
| } |
| |
| g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, |
| "SIM failed to handle CRSM request (sw1 %d sw2 %d)", sw1, sw2); |
| return 0; |
| } |
| |
| static gchar * |
| load_operator_identifier_finish (MMBaseSim *self, |
| GAsyncResult *res, |
| GError **error) |
| { |
| GError *inner_error = NULL; |
| const gchar *imsi; |
| gchar *result; |
| guint mnc_length; |
| |
| result = g_task_propagate_pointer (G_TASK (res), error); |
| if (!result) |
| return NULL; |
| |
| mnc_length = parse_mnc_length (result, &inner_error); |
| g_free (result); |
| if (inner_error) { |
| g_propagate_error (error, inner_error); |
| return NULL; |
| } |
| |
| imsi = mm_gdbus_sim_get_imsi (MM_GDBUS_SIM (self)); |
| if (!imsi) { |
| g_set_error (error, |
| MM_CORE_ERROR, |
| MM_CORE_ERROR_FAILED, |
| "Cannot load Operator ID without IMSI"); |
| return NULL; |
| } |
| |
| /* Build Operator ID */ |
| return g_strndup (imsi, 3 + mnc_length); |
| } |
| |
| STR_REPLY_READY_FN (load_operator_identifier) |
| |
| static void |
| load_operator_identifier (MMBaseSim *self, |
| GAsyncReadyCallback callback, |
| gpointer user_data) |
| { |
| mm_obj_dbg (self, "loading operator ID..."); |
| |
| /* READ BINARY of EFad (Administrative Data) ETSI 51.011 section 10.3.18 |
| * SIMCOM A760xE-H modems can answer in 10s or more, so use rather big timeout |
| */ |
| mm_base_modem_at_command ( |
| self->priv->modem, |
| "+CRSM=176,28589,0,0,4", |
| 20, |
| FALSE, |
| (GAsyncReadyCallback)load_operator_identifier_command_ready, |
| g_task_new (self, NULL, callback, user_data)); |
| } |
| |
| /*****************************************************************************/ |
| /* Operator Name (Service Provider Name) */ |
| |
| static gchar * |
| parse_spn (const gchar *response, |
| GError **error) |
| { |
| guint sw1 = 0; |
| guint sw2 = 0; |
| g_autofree gchar *hex = NULL; |
| |
| if (!mm_3gpp_parse_crsm_response (response, |
| &sw1, |
| &sw2, |
| &hex, |
| error)) |
| return NULL; |
| |
| if ((sw1 == 0x90 && sw2 == 0x00) || |
| (sw1 == 0x91) || |
| (sw1 == 0x92) || |
| (sw1 == 0x9f)) { |
| g_autofree guint8 *bin = NULL; |
| gsize binlen = 0; |
| |
| /* Convert hex string to binary */ |
| bin = mm_utils_hexstr2bin (hex, -1, &binlen, error); |
| if (!bin) { |
| g_prefix_error (error, "SIM returned malformed response '%s': ", hex); |
| return NULL; |
| } |
| |
| return mm_sim_convert_spn_to_utf8 (bin, binlen, error); |
| } |
| |
| g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, |
| "SIM failed to handle CRSM request (sw1 %d sw2 %d)", sw1, sw2); |
| return NULL; |
| } |
| |
| static gchar * |
| load_operator_name_finish (MMBaseSim *self, |
| GAsyncResult *res, |
| GError **error) |
| { |
| gchar *result; |
| gchar *spn; |
| |
| result = g_task_propagate_pointer (G_TASK (res), error); |
| if (!result) |
| return NULL; |
| |
| spn = parse_spn (result, error); |
| g_free (result); |
| return spn; |
| } |
| |
| STR_REPLY_READY_FN (load_operator_name) |
| |
| static void |
| load_operator_name (MMBaseSim *self, |
| GAsyncReadyCallback callback, |
| gpointer user_data) |
| { |
| mm_obj_dbg (self, "loading operator name..."); |
| |
| /* READ BINARY of EFspn (Service Provider Name) ETSI 51.011 section 10.3.11 */ |
| mm_base_modem_at_command ( |
| self->priv->modem, |
| "+CRSM=176,28486,0,0,17", |
| 10, |
| FALSE, |
| (GAsyncReadyCallback)load_operator_name_command_ready, |
| g_task_new (self, NULL, callback, user_data)); |
| } |
| |
| /*****************************************************************************/ |
| /* GID1 and GID2 */ |
| |
| static GByteArray * |
| parse_gid (const gchar *response, |
| GError **error) |
| { |
| guint sw1 = 0; |
| guint sw2 = 0; |
| g_autofree gchar *hex = NULL; |
| |
| if (!mm_3gpp_parse_crsm_response (response, &sw1, &sw2, &hex, error)) |
| return NULL; |
| |
| if ((sw1 == 0x90 && sw2 == 0x00) || |
| (sw1 == 0x91) || |
| (sw1 == 0x92) || |
| (sw1 == 0x9f)) { |
| guint8 *bin = NULL; |
| gsize binlen = 0; |
| |
| /* Convert hex string to binary */ |
| bin = mm_utils_hexstr2bin (hex, -1, &binlen, error); |
| if (!bin) { |
| g_prefix_error (error, "SIM returned malformed response '%s': ", hex); |
| return NULL; |
| } |
| |
| /* return as bytearray */ |
| return g_byte_array_new_take (bin, binlen); |
| } |
| |
| g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, |
| "SIM failed to handle CRSM request (sw1 %d sw2 %d)", sw1, sw2); |
| return NULL; |
| } |
| |
| static GByteArray * |
| common_load_gid_finish (MMBaseSim *self, |
| GAsyncResult *res, |
| GError **error) |
| { |
| g_autofree gchar *result = NULL; |
| |
| result = g_task_propagate_pointer (G_TASK (res), error); |
| if (!result) |
| return NULL; |
| |
| return parse_gid (result, error); |
| } |
| |
| STR_REPLY_READY_FN (load_gid1) |
| STR_REPLY_READY_FN (load_gid2) |
| |
| static void |
| load_gid1 (MMBaseSim *self, |
| GAsyncReadyCallback callback, |
| gpointer user_data) |
| { |
| /* READ BINARY of EFgid1 */ |
| mm_base_modem_at_command ( |
| self->priv->modem, |
| "+CRSM=176,28478,0,0,0", |
| 10, |
| FALSE, |
| (GAsyncReadyCallback)load_gid1_command_ready, |
| g_task_new (self, NULL, callback, user_data)); |
| } |
| |
| static void |
| load_gid2 (MMBaseSim *self, |
| GAsyncReadyCallback callback, |
| gpointer user_data) |
| { |
| /* READ BINARY of EFgid2 */ |
| mm_base_modem_at_command ( |
| self->priv->modem, |
| "+CRSM=176,28479,0,0,0", |
| 10, |
| FALSE, |
| (GAsyncReadyCallback)load_gid2_command_ready, |
| g_task_new (self, NULL, callback, user_data)); |
| } |
| |
| /*****************************************************************************/ |
| |
| MMBaseSim * |
| mm_base_sim_new_initialized (MMBaseModem *modem, |
| GObject *bind_to, |
| 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_BASE_SIM, |
| MM_BASE_SIM_MODEM, modem, |
| MM_BIND_TO, bind_to, |
| 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; |
| } |
| |
| /*****************************************************************************/ |
| |
| typedef struct _InitAsyncContext InitAsyncContext; |
| static void interface_initialization_step (GTask *task); |
| |
| typedef enum { |
| INITIALIZATION_STEP_FIRST, |
| INITIALIZATION_STEP_WAIT_READY, |
| INITIALIZATION_STEP_SIM_TYPE, |
| INITIALIZATION_STEP_ESIM_STATUS, |
| INITIALIZATION_STEP_SIM_IDENTIFIER, |
| INITIALIZATION_STEP_IMSI, |
| INITIALIZATION_STEP_OPERATOR_ID, |
| INITIALIZATION_STEP_OPERATOR_NAME, |
| INITIALIZATION_STEP_EMERGENCY_NUMBERS, |
| INITIALIZATION_STEP_PREFERRED_NETWORKS, |
| INITIALIZATION_STEP_GID1, |
| INITIALIZATION_STEP_GID2, |
| INITIALIZATION_STEP_EID, |
| INITIALIZATION_STEP_REMOVABILITY, |
| INITIALIZATION_STEP_LAST |
| } InitializationStep; |
| |
| struct _InitAsyncContext { |
| InitializationStep step; |
| guint sim_identifier_tries; |
| }; |
| |
| MMBaseSim * |
| mm_base_sim_new_finish (GAsyncResult *res, |
| GError **error) |
| { |
| GObject *source; |
| GObject *sim; |
| |
| source = g_async_result_get_source_object (res); |
| sim = g_async_initable_new_finish (G_ASYNC_INITABLE (source), res, error); |
| g_object_unref (source); |
| |
| if (!sim) |
| return NULL; |
| |
| /* Only export valid SIMs */ |
| mm_base_sim_export (MM_BASE_SIM (sim)); |
| |
| return MM_BASE_SIM (sim); |
| } |
| |
| static gboolean |
| initable_init_finish (GAsyncInitable *initable, |
| GAsyncResult *result, |
| GError **error) |
| { |
| return g_task_propagate_boolean (G_TASK (result), error); |
| } |
| |
| #undef COMMON_STR_REPLY_READY_FN |
| #define COMMON_STR_REPLY_READY_FN(NAME,DISPLAY,VALUE_FORMAT) \ |
| static void \ |
| init_load_##NAME##_ready (MMBaseSim *self, \ |
| GAsyncResult *res, \ |
| GTask *task) \ |
| { \ |
| InitAsyncContext *ctx; \ |
| g_autoptr(GError) error = NULL; \ |
| g_autofree gchar *val = NULL; \ |
| \ |
| val = MM_BASE_SIM_GET_CLASS (self)->load_##NAME##_finish (self, res, &error); \ |
| mm_gdbus_sim_set_##NAME (MM_GDBUS_SIM (self), val); \ |
| \ |
| if (error) \ |
| mm_obj_dbg (self, "couldn't load %s: %s", DISPLAY, error->message); \ |
| else \ |
| mm_obj_info (self, "loaded %s: %s", DISPLAY, VALUE_FORMAT (val)); \ |
| \ |
| /* Go on to next step */ \ |
| ctx = g_task_get_task_data (task); \ |
| ctx->step++; \ |
| interface_initialization_step (task); \ |
| } |
| |
| #undef STR_REPLY_READY_FN |
| #define STR_REPLY_READY_FN(NAME,DISPLAY) COMMON_STR_REPLY_READY_FN (NAME, DISPLAY, (const gchar *)) |
| |
| #undef PERSONAL_STR_REPLY_READY_FN |
| #define PERSONAL_STR_REPLY_READY_FN(NAME,DISPLAY) COMMON_STR_REPLY_READY_FN (NAME, DISPLAY, mm_log_str_personal_info) |
| |
| #undef ENUM_REPLY_READY_FN |
| #define ENUM_REPLY_READY_FN(NAME,DISPLAY,ENUM_TYPE,ENUM_GET_STRING) \ |
| static void \ |
| init_load_##NAME##_ready (MMBaseSim *self, \ |
| GAsyncResult *res, \ |
| GTask *task) \ |
| { \ |
| InitAsyncContext *ctx; \ |
| g_autoptr(GError) error = NULL; \ |
| ENUM_TYPE val; \ |
| \ |
| val = MM_BASE_SIM_GET_CLASS (self)->load_##NAME##_finish (self, res, &error); \ |
| mm_gdbus_sim_set_##NAME (MM_GDBUS_SIM (self), (guint) val); \ |
| \ |
| if (error) \ |
| mm_obj_dbg (self, "couldn't load %s: %s", DISPLAY, error->message); \ |
| else \ |
| mm_obj_info (self, "loaded %s: %s", DISPLAY, ENUM_GET_STRING (val)); \ |
| \ |
| /* Go on to next step */ \ |
| ctx = g_task_get_task_data (task); \ |
| ctx->step++; \ |
| interface_initialization_step (task); \ |
| } |
| |
| #undef BYTEARRAY_REPLY_READY_FN |
| #define BYTEARRAY_REPLY_READY_FN(NAME,DISPLAY) \ |
| static void \ |
| init_load_##NAME##_ready (MMBaseSim *self, \ |
| GAsyncResult *res, \ |
| GTask *task) \ |
| { \ |
| InitAsyncContext *ctx; \ |
| g_autoptr(GError) error = NULL; \ |
| g_autoptr(GByteArray) bytearray = NULL; \ |
| \ |
| bytearray = MM_BASE_SIM_GET_CLASS (self)->load_##NAME##_finish (self, res, &error); \ |
| mm_gdbus_sim_set_##NAME (MM_GDBUS_SIM (self), \ |
| (bytearray ? \ |
| g_variant_new_fixed_array (G_VARIANT_TYPE_BYTE, \ |
| bytearray->data, \ |
| bytearray->len, \ |
| sizeof (guint8)) : \ |
| NULL)); \ |
| \ |
| if (error) \ |
| mm_obj_dbg (self, "couldn't load %s: %s", DISPLAY, error->message); \ |
| else { \ |
| g_autofree gchar *bytearray_str = NULL; \ |
| \ |
| bytearray_str = mm_utils_bin2hexstr (bytearray->data, bytearray->len); \ |
| mm_obj_info (self, "loaded %s: %s", DISPLAY, bytearray_str); \ |
| } \ |
| \ |
| /* Go on to next step */ \ |
| ctx = g_task_get_task_data (task); \ |
| ctx->step++; \ |
| interface_initialization_step (task); \ |
| } |
| |
| ENUM_REPLY_READY_FN (removability, "removability", MMSimRemovability, mm_sim_removability_get_string) |
| PERSONAL_STR_REPLY_READY_FN (eid, "EID") |
| BYTEARRAY_REPLY_READY_FN (gid2, "GID2") |
| BYTEARRAY_REPLY_READY_FN (gid1, "GID1") |
| |
| static void |
| init_load_preferred_networks_ready (MMBaseSim *self, |
| GAsyncResult *res, |
| GTask *task) |
| { |
| InitAsyncContext *ctx; |
| g_autoptr(GError) error = NULL; |
| GList *preferred_nets_list; |
| |
| preferred_nets_list = MM_BASE_SIM_GET_CLASS (self)->load_preferred_networks_finish (self, res, &error); |
| if (error) |
| mm_obj_dbg (self, "couldn't load list of preferred networks: %s", error->message); |
| else { |
| g_autoptr(GString) str = NULL; |
| GList *l; |
| |
| str = g_string_new (""); |
| for (l = preferred_nets_list; l; l = g_list_next (l)) { |
| MMSimPreferredNetwork *item; |
| g_autofree gchar *access_tech_str = NULL; |
| |
| item = (MMSimPreferredNetwork *)(l->data); |
| access_tech_str = mm_modem_access_technology_build_string_from_mask (mm_sim_preferred_network_get_access_technology (item)); |
| g_string_append_printf (str, "%s%s (%s)", str->len ? ", " : "", mm_sim_preferred_network_get_operator_code (item), access_tech_str); |
| } |
| |
| mm_obj_info (self, "loaded list of preferred networks: %s", str->str); |
| } |
| |
| mm_gdbus_sim_set_preferred_networks (MM_GDBUS_SIM (self), |
| mm_sim_preferred_network_list_get_variant (preferred_nets_list)); |
| |
| g_list_free_full (preferred_nets_list, (GDestroyNotify) mm_sim_preferred_network_free); |
| |
| /* Go on to next step */ |
| ctx = g_task_get_task_data (task); |
| ctx->step++; |
| interface_initialization_step (task); |
| } |
| |
| static void |
| init_load_emergency_numbers_ready (MMBaseSim *self, |
| GAsyncResult *res, |
| GTask *task) |
| { |
| InitAsyncContext *ctx; |
| g_autoptr(GError) error = NULL; |
| g_auto(GStrv) str_list = NULL; |
| |
| str_list = MM_BASE_SIM_GET_CLASS (self)->load_emergency_numbers_finish (self, res, &error); |
| if (error) |
| mm_obj_dbg (self, "couldn't load list of emergency numbers: %s", error->message); |
| else { |
| g_autoptr(GString) str = NULL; |
| guint i; |
| |
| str = g_string_new (""); |
| for (i = 0; str_list && str_list[i]; i++) |
| g_string_append_printf (str, "%s%s", str->len ? ", " : "", str_list[i]); |
| mm_obj_info (self, "loaded list of emergency numbers: %s", str->str); |
| } |
| |
| mm_gdbus_sim_set_emergency_numbers (MM_GDBUS_SIM (self), (const gchar *const *) str_list); |
| |
| /* Go on to next step */ |
| ctx = g_task_get_task_data (task); |
| ctx->step++; |
| interface_initialization_step (task); |
| } |
| |
| STR_REPLY_READY_FN (operator_name, "operator name") |
| STR_REPLY_READY_FN (operator_identifier, "operator identifier") |
| PERSONAL_STR_REPLY_READY_FN (imsi, "IMSI") |
| |
| static void |
| init_load_sim_identifier_ready (MMBaseSim *self, |
| GAsyncResult *res, |
| GTask *task) |
| { |
| InitAsyncContext *ctx; |
| GError *error = NULL; |
| gchar *simid; |
| |
| ctx = g_task_get_task_data (task); |
| simid = MM_BASE_SIM_GET_CLASS (self)->load_sim_identifier_finish (self, res, &error); |
| if (!simid) { |
| /* TODO: make the retries gobi-specific? */ |
| |
| /* Try one more time... Gobi 1K cards may reply to the first |
| * request with '+CRSM: 106,134,""' which is bogus because |
| * subsequent requests work fine. |
| */ |
| if (++ctx->sim_identifier_tries < 2) { |
| g_clear_error (&error); |
| interface_initialization_step (task); |
| return; |
| } |
| |
| mm_obj_warn (self, "couldn't load SIM identifier: %s", error ? error->message : "unknown error"); |
| g_clear_error (&error); |
| } else |
| mm_obj_info (self, "loaded SIM identifier: %s", mm_log_str_personal_info (simid)); |
| |
| mm_gdbus_sim_set_sim_identifier (MM_GDBUS_SIM (self), simid); |
| g_free (simid); |
| |
| /* Go on to next step */ |
| ctx->step++; |
| interface_initialization_step (task); |
| } |
| |
| ENUM_REPLY_READY_FN (esim_status, "esim status", MMSimEsimStatus, mm_sim_esim_status_get_string) |
| ENUM_REPLY_READY_FN (sim_type, "sim type", MMSimType, mm_sim_type_get_string) |
| |
| static void |
| init_wait_sim_ready (MMBaseSim *self, |
| GAsyncResult *res, |
| GTask *task) |
| { |
| InitAsyncContext *ctx; |
| g_autoptr(GError) error = NULL; |
| |
| if (!MM_BASE_SIM_GET_CLASS (self)->wait_sim_ready_finish (self, res, &error)) |
| mm_obj_dbg (self, "couldn't wait for SIM to be ready: %s", error->message); |
| |
| /* Go on to next step */ |
| ctx = g_task_get_task_data (task); |
| ctx->step++; |
| interface_initialization_step (task); |
| } |
| |
| static void |
| interface_initialization_step (GTask *task) |
| { |
| MMBaseSim *self; |
| InitAsyncContext *ctx; |
| |
| if (g_task_return_error_if_cancelled (task)) { |
| g_object_unref (task); |
| return; |
| } |
| |
| self = g_task_get_source_object (task); |
| ctx = g_task_get_task_data (task); |
| |
| switch (ctx->step) { |
| case INITIALIZATION_STEP_FIRST: |
| ctx->step++; |
| /* Fall through */ |
| |
| case INITIALIZATION_STEP_WAIT_READY: |
| if (MM_BASE_SIM_GET_CLASS (self)->wait_sim_ready && |
| MM_BASE_SIM_GET_CLASS (self)->wait_sim_ready_finish) { |
| MM_BASE_SIM_GET_CLASS (self)->wait_sim_ready ( |
| self, |
| (GAsyncReadyCallback)init_wait_sim_ready, |
| task); |
| return; |
| } |
| ctx->step++; |
| /* Fall through */ |
| |
| case INITIALIZATION_STEP_SIM_TYPE: |
| if (mm_gdbus_sim_get_sim_type (MM_GDBUS_SIM (self)) == MM_SIM_TYPE_UNKNOWN && |
| MM_BASE_SIM_GET_CLASS (self)->load_sim_type && |
| MM_BASE_SIM_GET_CLASS (self)->load_sim_type_finish) { |
| MM_BASE_SIM_GET_CLASS (self)->load_sim_type ( |
| self, |
| (GAsyncReadyCallback)init_load_sim_type_ready, |
| task); |
| return; |
| } |
| ctx->step++; |
| /* Fall through */ |
| |
| case INITIALIZATION_STEP_ESIM_STATUS: |
| /* Don't load eSIM status if the SIM is known to be a physical SIM */ |
| if (IS_PSIM (self)) |
| mm_obj_dbg (self, "not loading eSIM status in physical SIM"); |
| else if (mm_gdbus_sim_get_esim_status (MM_GDBUS_SIM (self)) == MM_SIM_ESIM_STATUS_UNKNOWN && |
| MM_BASE_SIM_GET_CLASS (self)->load_esim_status && |
| MM_BASE_SIM_GET_CLASS (self)->load_esim_status_finish) { |
| MM_BASE_SIM_GET_CLASS (self)->load_esim_status ( |
| self, |
| (GAsyncReadyCallback)init_load_esim_status_ready, |
| task); |
| return; |
| } |
| ctx->step++; |
| /* Fall through */ |
| |
| case INITIALIZATION_STEP_SIM_IDENTIFIER: |
| /* Don't load SIM ICCID if the SIM is known to be an eSIM without |
| * profiles; otherwise (if physical SIM, or if eSIM with profile, or if |
| * SIM type unknown) try to load it. */ |
| if (IS_ESIM_WITHOUT_PROFILES (self)) |
| mm_obj_dbg (self, "not loading SIM identifier in eSIM without profiles"); |
| else if (mm_gdbus_sim_get_sim_identifier (MM_GDBUS_SIM (self)) == NULL && |
| MM_BASE_SIM_GET_CLASS (self)->load_sim_identifier && |
| MM_BASE_SIM_GET_CLASS (self)->load_sim_identifier_finish) { |
| MM_BASE_SIM_GET_CLASS (self)->load_sim_identifier ( |
| self, |
| (GAsyncReadyCallback)init_load_sim_identifier_ready, |
| task); |
| return; |
| } |
| ctx->step++; |
| /* Fall through */ |
| |
| case INITIALIZATION_STEP_IMSI: |
| /* Don't load SIM IMSI if the SIM is known to be an eSIM without |
| * profiles; otherwise (if physical SIM, or if eSIM with profile, or if |
| * SIM type unknown) try to load it. */ |
| if (IS_ESIM_WITHOUT_PROFILES (self)) |
| mm_obj_dbg (self, "not loading IMSI in eSIM without profiles"); |
| else if (mm_gdbus_sim_get_imsi (MM_GDBUS_SIM (self)) == NULL && |
| MM_BASE_SIM_GET_CLASS (self)->load_imsi && |
| MM_BASE_SIM_GET_CLASS (self)->load_imsi_finish) { |
| MM_BASE_SIM_GET_CLASS (self)->load_imsi ( |
| self, |
| (GAsyncReadyCallback)init_load_imsi_ready, |
| task); |
| return; |
| } |
| ctx->step++; |
| /* Fall through */ |
| |
| case INITIALIZATION_STEP_OPERATOR_ID: |
| /* Don't load operator ID if the SIM is known to be an eSIM without |
| * profiles; otherwise (if physical SIM, or if eSIM with profile, or if |
| * SIM type unknown) try to load it. */ |
| if (IS_ESIM_WITHOUT_PROFILES (self)) |
| mm_obj_dbg (self, "not loading operator ID in eSIM without profiles"); |
| else if (mm_gdbus_sim_get_operator_identifier (MM_GDBUS_SIM (self)) == NULL && |
| MM_BASE_SIM_GET_CLASS (self)->load_operator_identifier && |
| MM_BASE_SIM_GET_CLASS (self)->load_operator_identifier_finish) { |
| MM_BASE_SIM_GET_CLASS (self)->load_operator_identifier ( |
| self, |
| (GAsyncReadyCallback)init_load_operator_identifier_ready, |
| task); |
| return; |
| } |
| ctx->step++; |
| /* Fall through */ |
| |
| case INITIALIZATION_STEP_OPERATOR_NAME: |
| /* Don't load operator name if the SIM is known to be an eSIM without |
| * profiles; otherwise (if physical SIM, or if eSIM with profile, or if |
| * SIM type unknown) try to load it. */ |
| if (IS_ESIM_WITHOUT_PROFILES (self)) |
| mm_obj_dbg (self, "not loading operator name in eSIM without profiles"); |
| else if (mm_gdbus_sim_get_operator_name (MM_GDBUS_SIM (self)) == NULL && |
| MM_BASE_SIM_GET_CLASS (self)->load_operator_name && |
| MM_BASE_SIM_GET_CLASS (self)->load_operator_name_finish) { |
| MM_BASE_SIM_GET_CLASS (self)->load_operator_name ( |
| self, |
| (GAsyncReadyCallback)init_load_operator_name_ready, |
| task); |
| return; |
| } |
| ctx->step++; |
| /* Fall through */ |
| |
| case INITIALIZATION_STEP_EMERGENCY_NUMBERS: |
| /* Don't load emergency numbers if the SIM is known to be an eSIM without |
| * profiles; otherwise (if physical SIM, or if eSIM with profile, or if |
| * SIM type unknown) try to load it. */ |
| if (IS_ESIM_WITHOUT_PROFILES (self)) |
| mm_obj_dbg (self, "not loading emergency numbers in eSIM without profiles"); |
| else if (mm_gdbus_sim_get_emergency_numbers (MM_GDBUS_SIM (self)) == NULL && |
| MM_BASE_SIM_GET_CLASS (self)->load_emergency_numbers && |
| MM_BASE_SIM_GET_CLASS (self)->load_emergency_numbers_finish) { |
| MM_BASE_SIM_GET_CLASS (self)->load_emergency_numbers ( |
| self, |
| (GAsyncReadyCallback)init_load_emergency_numbers_ready, |
| task); |
| return; |
| } |
| ctx->step++; |
| /* Fall through */ |
| |
| case INITIALIZATION_STEP_PREFERRED_NETWORKS: |
| /* Don't load preferred networks if the SIM is known to be an eSIM without |
| * profiles; otherwise (if physical SIM, or if eSIM with profile, or if |
| * SIM type unknown) try to load it. */ |
| if (IS_ESIM_WITHOUT_PROFILES (self)) |
| mm_obj_dbg (self, "not loading preferred networks in eSIM without profiles"); |
| else if (mm_gdbus_sim_get_preferred_networks (MM_GDBUS_SIM (self)) == NULL && |
| MM_BASE_SIM_GET_CLASS (self)->load_preferred_networks && |
| MM_BASE_SIM_GET_CLASS (self)->load_preferred_networks_finish) { |
| MM_BASE_SIM_GET_CLASS (self)->load_preferred_networks ( |
| self, |
| (GAsyncReadyCallback)init_load_preferred_networks_ready, |
| task); |
| return; |
| } |
| ctx->step++; |
| /* Fall through */ |
| |
| case INITIALIZATION_STEP_GID1: |
| /* Don't load GID1 if the SIM is known to be an eSIM without profiles; |
| * otherwise (if physical SIM, or if eSIM with profile, or if |
| * SIM type unknown) try to load it. */ |
| if (IS_ESIM_WITHOUT_PROFILES (self)) |
| mm_obj_dbg (self, "not loading GID1 in eSIM without profiles"); |
| else if (mm_gdbus_sim_get_gid1 (MM_GDBUS_SIM (self)) == NULL && |
| MM_BASE_SIM_GET_CLASS (self)->load_gid1 && |
| MM_BASE_SIM_GET_CLASS (self)->load_gid1_finish) { |
| MM_BASE_SIM_GET_CLASS (self)->load_gid1 ( |
| self, |
| (GAsyncReadyCallback)init_load_gid1_ready, |
| task); |
| return; |
| } |
| ctx->step++; |
| /* Fall through */ |
| |
| case INITIALIZATION_STEP_GID2: |
| /* Don't load GID2 if the SIM is known to be an eSIM without profiles; |
| * otherwise (if physical SIM, or if eSIM with profile, or if |
| * SIM type unknown) try to load it. */ |
| if (IS_ESIM_WITHOUT_PROFILES (self)) |
| mm_obj_dbg (self, "not loading GID2 in eSIM without profiles"); |
| else if (mm_gdbus_sim_get_gid2 (MM_GDBUS_SIM (self)) == NULL && |
| MM_BASE_SIM_GET_CLASS (self)->load_gid2 && |
| MM_BASE_SIM_GET_CLASS (self)->load_gid2_finish) { |
| MM_BASE_SIM_GET_CLASS (self)->load_gid2 ( |
| self, |
| (GAsyncReadyCallback)init_load_gid2_ready, |
| task); |
| return; |
| } |
| ctx->step++; |
| /* Fall through */ |
| |
| case INITIALIZATION_STEP_EID: |
| /* Don't load EID if the SIM is known to be a physical SIM; otherwise |
| * (if eSIM with or without profiles) try to load it. */ |
| if (IS_PSIM (self)) |
| mm_obj_dbg (self, "not loading EID in physical SIM"); |
| else if (mm_gdbus_sim_get_eid (MM_GDBUS_SIM (self)) == NULL && |
| MM_BASE_SIM_GET_CLASS (self)->load_eid && |
| MM_BASE_SIM_GET_CLASS (self)->load_eid_finish) { |
| MM_BASE_SIM_GET_CLASS (self)->load_eid ( |
| self, |
| (GAsyncReadyCallback)init_load_eid_ready, |
| task); |
| return; |
| } |
| ctx->step++; |
| /* Fall through */ |
| |
| case INITIALIZATION_STEP_REMOVABILITY: |
| /* Although not very common, there are removable eSIMs, so always try to |
| * load it, regardless of SIM type. */ |
| if (mm_gdbus_sim_get_removability (MM_GDBUS_SIM (self)) == MM_SIM_REMOVABILITY_UNKNOWN && |
| MM_BASE_SIM_GET_CLASS (self)->load_removability && |
| MM_BASE_SIM_GET_CLASS (self)->load_removability_finish) { |
| MM_BASE_SIM_GET_CLASS (self)->load_removability ( |
| self, |
| (GAsyncReadyCallback)init_load_removability_ready, |
| task); |
| return; |
| } |
| ctx->step++; |
| /* Fall through */ |
| |
| case INITIALIZATION_STEP_LAST: |
| /* We are done without errors! */ |
| g_task_return_boolean (task, TRUE); |
| g_object_unref (task); |
| return; |
| |
| default: |
| break; |
| } |
| |
| g_assert_not_reached (); |
| } |
| |
| static void |
| common_init_async (GAsyncInitable *initable, |
| GCancellable *cancellable, |
| GAsyncReadyCallback callback, |
| gpointer user_data) |
| |
| { |
| MMBaseSim *self; |
| InitAsyncContext *ctx; |
| GTask *task; |
| |
| self = MM_BASE_SIM (initable); |
| |
| ctx = g_new (InitAsyncContext, 1); |
| ctx->step = INITIALIZATION_STEP_FIRST; |
| ctx->sim_identifier_tries = 0; |
| |
| task = g_task_new (self, cancellable, callback, user_data); |
| g_task_set_task_data (task, ctx, g_free); |
| |
| interface_initialization_step (task); |
| } |
| |
| static void |
| initable_init_async (GAsyncInitable *initable, |
| int io_priority, |
| GCancellable *cancellable, |
| GAsyncReadyCallback callback, |
| gpointer user_data) |
| { |
| mm_gdbus_sim_set_sim_identifier (MM_GDBUS_SIM (initable), NULL); |
| mm_gdbus_sim_set_imsi (MM_GDBUS_SIM (initable), NULL); |
| mm_gdbus_sim_set_eid (MM_GDBUS_SIM (initable), NULL); |
| mm_gdbus_sim_set_operator_identifier (MM_GDBUS_SIM (initable), NULL); |
| mm_gdbus_sim_set_operator_name (MM_GDBUS_SIM (initable), NULL); |
| mm_gdbus_sim_set_gid1 (MM_GDBUS_SIM (initable), NULL); |
| mm_gdbus_sim_set_gid2 (MM_GDBUS_SIM (initable), NULL); |
| |
| common_init_async (initable, cancellable, callback, user_data); |
| } |
| |
| void |
| mm_base_sim_new (MMBaseModem *modem, |
| GObject *bind_to, |
| GCancellable *cancellable, |
| GAsyncReadyCallback callback, |
| gpointer user_data) |
| { |
| g_async_initable_new_async (MM_TYPE_BASE_SIM, |
| G_PRIORITY_DEFAULT, |
| cancellable, |
| callback, |
| user_data, |
| MM_BASE_SIM_MODEM, modem, |
| MM_BIND_TO, bind_to, |
| "active", TRUE, /* by default always active */ |
| NULL); |
| } |
| |
| gboolean |
| mm_base_sim_initialize_finish (MMBaseSim *self, |
| GAsyncResult *result, |
| GError **error) |
| { |
| return initable_init_finish (G_ASYNC_INITABLE (self), result, error); |
| } |
| |
| void |
| mm_base_sim_initialize (MMBaseSim *self, |
| GCancellable *cancellable, |
| GAsyncReadyCallback callback, |
| gpointer user_data) |
| { |
| common_init_async (G_ASYNC_INITABLE (self), |
| cancellable, |
| callback, |
| user_data); |
| } |
| |
| /*****************************************************************************/ |
| |
| static gchar * |
| log_object_build_id (MMLogObject *_self) |
| { |
| MMBaseSim *self; |
| |
| self = MM_BASE_SIM (_self); |
| return g_strdup_printf ("sim%u", self->priv->dbus_id); |
| } |
| |
| /*****************************************************************************/ |
| |
| static void |
| set_property (GObject *object, |
| guint prop_id, |
| const GValue *value, |
| GParamSpec *pspec) |
| { |
| MMBaseSim *self = MM_BASE_SIM (object); |
| |
| switch (prop_id) { |
| case PROP_PATH: |
| g_free (self->priv->path); |
| self->priv->path = g_value_dup_string (value); |
| |
| /* Export when we get a DBus connection AND we have a path */ |
| if (self->priv->path && |
| self->priv->connection) |
| sim_dbus_export (self); |
| break; |
| case PROP_CONNECTION: |
| g_clear_object (&self->priv->connection); |
| self->priv->connection = g_value_dup_object (value); |
| |
| /* Export when we get a DBus connection AND we have a path */ |
| if (!self->priv->connection) |
| sim_dbus_unexport (self); |
| else if (self->priv->path) |
| sim_dbus_export (self); |
| break; |
| case PROP_BIND_TO: |
| g_clear_object (&self->priv->bind_to); |
| self->priv->bind_to = g_value_dup_object (value); |
| mm_bind_to (MM_BIND (self), MM_BASE_SIM_CONNECTION, self->priv->bind_to); |
| break; |
| case PROP_MODEM: |
| g_clear_object (&self->priv->modem); |
| self->priv->modem = g_value_dup_object (value); |
| break; |
| case PROP_SLOT_NUMBER: |
| self->priv->slot_number = g_value_get_uint (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) |
| { |
| MMBaseSim *self = MM_BASE_SIM (object); |
| |
| switch (prop_id) { |
| case PROP_PATH: |
| g_value_set_string (value, self->priv->path); |
| break; |
| case PROP_CONNECTION: |
| g_value_set_object (value, self->priv->connection); |
| break; |
| case PROP_BIND_TO: |
| g_value_set_object (value, self->priv->bind_to); |
| break; |
| case PROP_MODEM: |
| g_value_set_object (value, self->priv->modem); |
| break; |
| case PROP_SLOT_NUMBER: |
| g_value_set_uint (value, self->priv->slot_number); |
| break; |
| default: |
| G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); |
| break; |
| } |
| } |
| |
| static void |
| mm_base_sim_init (MMBaseSim *self) |
| { |
| static guint id = 0; |
| |
| /* Initialize private data */ |
| self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, MM_TYPE_BASE_SIM, MMBaseSimPrivate); |
| |
| /* Each SIM is given a unique id to build its own DBus path */ |
| self->priv->dbus_id = id++; |
| |
| /* Setup authorization provider */ |
| self->priv->authp = mm_auth_provider_get (); |
| self->priv->authp_cancellable = g_cancellable_new (); |
| } |
| |
| static void |
| finalize (GObject *object) |
| { |
| MMBaseSim *self = MM_BASE_SIM (object); |
| |
| g_free (self->priv->path); |
| |
| G_OBJECT_CLASS (mm_base_sim_parent_class)->finalize (object); |
| } |
| |
| static void |
| dispose (GObject *object) |
| { |
| MMBaseSim *self = MM_BASE_SIM (object); |
| |
| if (self->priv->connection) { |
| /* If we arrived here with a valid connection, make sure we unexport |
| * the object */ |
| sim_dbus_unexport (self); |
| g_clear_object (&self->priv->connection); |
| } |
| |
| g_clear_object (&self->priv->modem); |
| g_clear_object (&self->priv->bind_to); |
| g_cancellable_cancel (self->priv->authp_cancellable); |
| g_clear_object (&self->priv->authp_cancellable); |
| |
| G_OBJECT_CLASS (mm_base_sim_parent_class)->dispose (object); |
| } |
| |
| static void |
| async_initable_iface_init (GAsyncInitableIface *iface) |
| { |
| iface->init_async = initable_init_async; |
| iface->init_finish = initable_init_finish; |
| } |
| |
| static void |
| log_object_iface_init (MMLogObjectInterface *iface) |
| { |
| iface->build_id = log_object_build_id; |
| } |
| |
| static void |
| bind_iface_init (MMBindInterface *iface) |
| { |
| } |
| |
| static void |
| mm_base_sim_class_init (MMBaseSimClass *klass) |
| { |
| GObjectClass *object_class = G_OBJECT_CLASS (klass); |
| |
| g_type_class_add_private (object_class, sizeof (MMBaseSimPrivate)); |
| |
| /* Virtual methods */ |
| object_class->get_property = get_property; |
| object_class->set_property = set_property; |
| object_class->finalize = finalize; |
| object_class->dispose = dispose; |
| |
| klass->load_sim_identifier = load_sim_identifier; |
| klass->load_sim_identifier_finish = load_sim_identifier_finish; |
| klass->load_imsi = load_imsi; |
| klass->load_imsi_finish = load_imsi_finish; |
| klass->load_operator_identifier = load_operator_identifier; |
| klass->load_operator_identifier_finish = load_operator_identifier_finish; |
| klass->load_operator_name = load_operator_name; |
| klass->load_operator_name_finish = load_operator_name_finish; |
| klass->load_emergency_numbers = load_emergency_numbers; |
| klass->load_emergency_numbers_finish = load_emergency_numbers_finish; |
| klass->load_preferred_networks = load_preferred_networks; |
| klass->load_preferred_networks_finish = load_preferred_networks_finish; |
| klass->load_gid1 = load_gid1; |
| klass->load_gid1_finish = common_load_gid_finish; |
| klass->load_gid2 = load_gid2; |
| klass->load_gid2_finish = common_load_gid_finish; |
| klass->set_preferred_networks = set_preferred_networks; |
| klass->set_preferred_networks_finish = set_preferred_networks_finish; |
| klass->send_pin = send_pin; |
| klass->send_pin_finish = common_send_pin_puk_finish; |
| klass->send_puk = send_puk; |
| klass->send_puk_finish = common_send_pin_puk_finish; |
| klass->enable_pin = enable_pin; |
| klass->enable_pin_finish = enable_pin_finish; |
| klass->change_pin = change_pin; |
| klass->change_pin_finish = change_pin_finish; |
| |
| properties[PROP_CONNECTION] = |
| g_param_spec_object (MM_BASE_SIM_CONNECTION, |
| "Connection", |
| "GDBus connection to the system bus.", |
| G_TYPE_DBUS_CONNECTION, |
| G_PARAM_READWRITE); |
| g_object_class_install_property (object_class, PROP_CONNECTION, properties[PROP_CONNECTION]); |
| |
| properties[PROP_PATH] = |
| g_param_spec_string (MM_BASE_SIM_PATH, |
| "Path", |
| "DBus path of the SIM", |
| NULL, |
| G_PARAM_READWRITE); |
| g_object_class_install_property (object_class, PROP_PATH, properties[PROP_PATH]); |
| |
| properties[PROP_MODEM] = |
| g_param_spec_object (MM_BASE_SIM_MODEM, |
| "Modem", |
| "The Modem which owns this SIM", |
| MM_TYPE_BASE_MODEM, |
| G_PARAM_READWRITE); |
| g_object_class_install_property (object_class, PROP_MODEM, properties[PROP_MODEM]); |
| |
| g_object_class_override_property (object_class, PROP_BIND_TO, MM_BIND_TO); |
| |
| properties[PROP_SLOT_NUMBER] = |
| g_param_spec_uint (MM_BASE_SIM_SLOT_NUMBER, |
| "Slot number", |
| "The slot number where the SIM is inserted", |
| 0, G_MAXUINT, 0, |
| G_PARAM_READWRITE); |
| g_object_class_install_property (object_class, PROP_SLOT_NUMBER, properties[PROP_SLOT_NUMBER]); |
| |
| /* Signals */ |
| signals[SIGNAL_PIN_LOCK_ENABLED] = |
| g_signal_new (MM_BASE_SIM_PIN_LOCK_ENABLED, |
| G_OBJECT_CLASS_TYPE (object_class), |
| G_SIGNAL_RUN_FIRST, |
| G_STRUCT_OFFSET (MMBaseSimClass, pin_lock_enabled), |
| NULL, NULL, |
| g_cclosure_marshal_generic, |
| G_TYPE_NONE, 1, G_TYPE_BOOLEAN); |
| } |