/* -*- 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) 2009 Novell, Inc. * Copyright (C) 2009 - 2011 Red Hat, Inc. * Copyright (C) 2011 Google, Inc. * Copyright (c) 2022 Qualcomm Innovation Center, Inc. */ #include #define _LIBMM_INSIDE_MM #include #include "mm-bearer-list.h" #include "mm-base-sim.h" #include "mm-error-helpers.h" #include "mm-iface-modem.h" #include "mm-iface-modem-3gpp.h" #include "mm-iface-modem-cdma.h" #include "mm-iface-modem-simple.h" #include "mm-log-object.h" #include "mm-log-helpers.h" /*****************************************************************************/ /* Private data context */ #define PRIVATE_TAG "iface-modem-simple-private-tag" static GQuark private_quark; typedef struct { GCancellable *ongoing_connect; } Private; static void private_free (Private *priv) { g_clear_object (&priv->ongoing_connect); g_slice_free (Private, priv); } static Private * get_private (MMIfaceModemSimple *self) { Private *priv; if (G_UNLIKELY (!private_quark)) private_quark = g_quark_from_static_string (PRIVATE_TAG); priv = g_object_get_qdata (G_OBJECT (self), private_quark); if (!priv) { priv = g_slice_new0 (Private); g_object_set_qdata_full (G_OBJECT (self), private_quark, priv, (GDestroyNotify)private_free); } return priv; } /*****************************************************************************/ /* Abort ongoing requests, if any */ void mm_iface_modem_simple_abort_ongoing (MMIfaceModemSimple *self) { Private *priv; priv = get_private (self); if (priv->ongoing_connect) { g_cancellable_cancel (priv->ongoing_connect); g_clear_object (&priv->ongoing_connect); } } /*****************************************************************************/ /* Register in either a CDMA or a 3GPP network (or both) */ typedef struct { gchar *operator_id; guint remaining_tries_cdma; guint remaining_tries_3gpp; guint max_try_time; } RegisterInNetworkContext; static void register_in_network_context_free (RegisterInNetworkContext *ctx) { g_free (ctx->operator_id); g_slice_free (RegisterInNetworkContext, ctx); } static gboolean register_in_3gpp_or_cdma_network_finish (MMIfaceModemSimple *self, GAsyncResult *res, GError **error) { return g_task_propagate_boolean (G_TASK (res), error); } static void check_next_registration (GTask *task); static void register_in_cdma_network_ready (MMIfaceModemCdma *self, GAsyncResult *res, GTask *task) { RegisterInNetworkContext *ctx; ctx = g_task_get_task_data (task); ctx->remaining_tries_cdma--; if (!mm_iface_modem_cdma_register_in_network_finish (self, res, NULL)) { /* Retry check */ check_next_registration (task); return; } /* Registered we are! */ mm_obj_dbg (self, "registration in network: successful (cdma)"); g_task_return_boolean (task, TRUE); g_object_unref (task); } static void register_in_3gpp_network_ready (MMIfaceModem3gpp *self, GAsyncResult *res, GTask *task) { RegisterInNetworkContext *ctx; ctx = g_task_get_task_data (task); ctx->remaining_tries_3gpp--; if (!mm_iface_modem_3gpp_register_in_network_finish (self, res, NULL)) { /* Retry check */ check_next_registration (task); return; } /* Registered we are! */ mm_obj_dbg (self, "registration in network: successful (3gpp)"); g_task_return_boolean (task, TRUE); g_object_unref (task); } static void check_next_registration (GTask *task) { RegisterInNetworkContext *ctx; MMIfaceModemSimple *self; self = MM_IFACE_MODEM_SIMPLE (g_task_get_source_object (task)); ctx = g_task_get_task_data (task); if (g_task_return_error_if_cancelled (task)) { mm_obj_dbg (self, "registration in network: cancelled"); g_object_unref (task); return; } if (ctx->remaining_tries_cdma > ctx->remaining_tries_3gpp && ctx->remaining_tries_cdma > 0) { mm_iface_modem_cdma_register_in_network ( MM_IFACE_MODEM_CDMA (self), ctx->max_try_time, (GAsyncReadyCallback)register_in_cdma_network_ready, task); return; } if (ctx->remaining_tries_3gpp > 0) { mm_iface_modem_3gpp_register_in_network ( MM_IFACE_MODEM_3GPP (self), ctx->operator_id, FALSE, /* if already registered with same settings, do nothing */ ctx->max_try_time, (GAsyncReadyCallback)register_in_3gpp_network_ready, task); return; } /* No more tries of anything */ mm_obj_dbg (self, "registration in network: timed out"); g_task_return_error ( task, mm_mobile_equipment_error_for_code (MM_MOBILE_EQUIPMENT_ERROR_NETWORK_TIMEOUT, self)); g_object_unref (task); } static void register_in_3gpp_or_cdma_network (MMIfaceModemSimple *self, const gchar *operator_id, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data) { RegisterInNetworkContext *ctx; GTask *task; ctx = g_slice_new0 (RegisterInNetworkContext); ctx->operator_id = g_strdup (operator_id); /* 3GPP-only modems... */ if (mm_iface_modem_is_3gpp_only (MM_IFACE_MODEM (self))) { ctx->max_try_time = 60; ctx->remaining_tries_cdma = 0; ctx->remaining_tries_3gpp = 1; } /* CDMA-only modems... */ else if (mm_iface_modem_is_cdma_only (MM_IFACE_MODEM (self))) { ctx->max_try_time = 60; ctx->remaining_tries_cdma = 1; ctx->remaining_tries_3gpp = 0; } /* Mixed 3GPP+CDMA modems... */ else { ctx->max_try_time = 10; ctx->remaining_tries_cdma = 6; ctx->remaining_tries_3gpp = 6; } task = g_task_new (self, cancellable, callback, user_data); g_task_set_task_data (task, ctx, (GDestroyNotify)register_in_network_context_free); check_next_registration (task); } /*****************************************************************************/ /* Packet service attach in 3GPP network */ typedef enum { PACKET_SERVICE_ATTACH_IN_3GPP_NETWORK_STEP_FIRST, PACKET_SERVICE_ATTACH_IN_3GPP_NETWORK_STEP_WAIT_BEFORE, PACKET_SERVICE_ATTACH_IN_3GPP_NETWORK_STEP_SET, PACKET_SERVICE_ATTACH_IN_3GPP_NETWORK_STEP_WAIT_AFTER, PACKET_SERVICE_ATTACH_IN_3GPP_NETWORK_STEP_LAST, } PacketServiceAttachIn3gppNetworkStep; typedef struct { PacketServiceAttachIn3gppNetworkStep step; GError *error; } PacketServiceAttachIn3gppNetworkContext; static void packet_service_attach_in_3gpp_network_context_free (PacketServiceAttachIn3gppNetworkContext *ctx) { g_clear_error (&ctx->error); g_slice_free (PacketServiceAttachIn3gppNetworkContext, ctx); } static gboolean packet_service_attach_in_3gpp_network_finish (MMIfaceModemSimple *self, GAsyncResult *res, GError **error) { return g_task_propagate_boolean (G_TASK (res), error); } static void packet_service_attach_in_3gpp_network_step (GTask *task); static void set_packet_service_state_ready (MMIfaceModem3gpp *self, GAsyncResult *res, GTask *task) { PacketServiceAttachIn3gppNetworkContext *ctx; g_autoptr(GError) error = NULL; ctx = g_task_get_task_data (task); g_assert (ctx->error); if (!mm_iface_modem_3gpp_set_packet_service_state_finish (self, res, &error)) { /* On an unsupported error, return the original wait failure; otherwise * return the set error. */ if (!g_error_matches (error, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED)) { g_error_free (ctx->error); ctx->error = g_steal_pointer (&error); } /* Failures in the set are always fatal right away */ ctx->step = PACKET_SERVICE_ATTACH_IN_3GPP_NETWORK_STEP_LAST; } else { /* Retry if possible */ g_clear_error (&ctx->error); ctx->step++; } packet_service_attach_in_3gpp_network_step (task); } static void wait_for_packet_service_state_ready (MMIfaceModem3gpp *self, GAsyncResult *res, GTask *task) { PacketServiceAttachIn3gppNetworkContext *ctx; ctx = g_task_get_task_data (task); g_assert (!ctx->error); if (!mm_iface_modem_3gpp_wait_for_packet_service_state_finish (self, res, &ctx->error)) ctx->step++; else ctx->step = PACKET_SERVICE_ATTACH_IN_3GPP_NETWORK_STEP_LAST; packet_service_attach_in_3gpp_network_step (task); } static void packet_service_attach_in_3gpp_network_step (GTask *task) { PacketServiceAttachIn3gppNetworkContext *ctx; MMIfaceModemSimple *self; self = g_task_get_source_object (task); ctx = g_task_get_task_data (task); if (g_task_return_error_if_cancelled (task)) { mm_obj_dbg (self, "packet service attach in 3gpp network: cancelled"); g_object_unref (task); return; } switch (ctx->step) { case PACKET_SERVICE_ATTACH_IN_3GPP_NETWORK_STEP_FIRST: ctx->step++; /* fall through */ case PACKET_SERVICE_ATTACH_IN_3GPP_NETWORK_STEP_WAIT_BEFORE: g_assert (!ctx->error); mm_iface_modem_3gpp_wait_for_packet_service_state (MM_IFACE_MODEM_3GPP (self), MM_MODEM_3GPP_PACKET_SERVICE_STATE_ATTACHED, g_task_get_cancellable (task), (GAsyncReadyCallback)wait_for_packet_service_state_ready, task); return; case PACKET_SERVICE_ATTACH_IN_3GPP_NETWORK_STEP_SET: /* An explicit set will only be attempted if the packet service state wait failed */ g_assert (ctx->error); mm_iface_modem_3gpp_set_packet_service_state (MM_IFACE_MODEM_3GPP (self), MM_MODEM_3GPP_PACKET_SERVICE_STATE_ATTACHED, (GAsyncReadyCallback)set_packet_service_state_ready, task); return; case PACKET_SERVICE_ATTACH_IN_3GPP_NETWORK_STEP_WAIT_AFTER: g_assert (!ctx->error); mm_iface_modem_3gpp_wait_for_packet_service_state (MM_IFACE_MODEM_3GPP (self), MM_MODEM_3GPP_PACKET_SERVICE_STATE_ATTACHED, g_task_get_cancellable (task), (GAsyncReadyCallback)wait_for_packet_service_state_ready, task); return; case PACKET_SERVICE_ATTACH_IN_3GPP_NETWORK_STEP_LAST: if (ctx->error) { mm_obj_dbg (self, "packet service attach in 3gpp network: failed: %s", ctx->error->message); g_task_return_error (task, g_steal_pointer (&ctx->error)); } else { mm_obj_dbg (self, "packet service attach in 3gpp network: finished"); g_task_return_boolean (task, TRUE); } g_object_unref (task); return; default: g_assert_not_reached (); } } static void packet_service_attach_in_3gpp_network (MMIfaceModemSimple *self, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data) { PacketServiceAttachIn3gppNetworkContext *ctx; GTask *task; task = g_task_new (self, cancellable, callback, user_data); ctx = g_slice_new0 (PacketServiceAttachIn3gppNetworkContext); ctx->step = PACKET_SERVICE_ATTACH_IN_3GPP_NETWORK_STEP_FIRST; g_task_set_task_data (task, ctx, (GDestroyNotify)packet_service_attach_in_3gpp_network_context_free); packet_service_attach_in_3gpp_network_step (task); } /*****************************************************************************/ typedef enum { CONNECTION_STEP_FIRST, CONNECTION_STEP_UNLOCK_CHECK, CONNECTION_STEP_WAIT_FOR_INITIALIZED, CONNECTION_STEP_ENABLE, CONNECTION_STEP_WAIT_FOR_ENABLED, CONNECTION_STEP_WAIT_AFTER_ENABLED, CONNECTION_STEP_REGISTER, CONNECTION_STEP_PACKET_SERVICE_ATTACH, CONNECTION_STEP_BEARER, CONNECTION_STEP_CONNECT, CONNECTION_STEP_LAST } ConnectionStep; typedef struct { MmGdbusModemSimple *skeleton; GDBusMethodInvocation *invocation; MMIfaceModemSimple *self; ConnectionStep step; MMBearerList *bearer_list; /* Expected input properties */ GVariant *dictionary; MMSimpleConnectProperties *properties; /* Results to set */ MMBaseBearer *bearer; /* Cancellation */ GCancellable *cancellable; } ConnectionContext; static void cleanup_cancellation (ConnectionContext *ctx) { Private *priv; /* The ongoing connect cancellable and the one in the connection context * must be the same, as they're set together, so if the one in the * context doesn't exist, do nothing. */ if (!ctx->cancellable) return; /* If the ongoing connect cancellable is cancelled via the Simple.Disconnect * method, it won't exist in the private struct, so don't assume they * both exist. */ priv = get_private (ctx->self); g_clear_object (&priv->ongoing_connect); g_clear_object (&ctx->cancellable); } static void connection_context_free (ConnectionContext *ctx) { cleanup_cancellation (ctx); g_clear_object (&ctx->properties); g_clear_object (&ctx->bearer); g_clear_object (&ctx->bearer_list); g_variant_unref (ctx->dictionary); g_object_unref (ctx->skeleton); g_object_unref (ctx->invocation); g_object_unref (ctx->self); g_slice_free (ConnectionContext, ctx); } static void connection_step (ConnectionContext *ctx); static void connect_bearer_ready (MMBaseBearer *bearer, GAsyncResult *res, ConnectionContext *ctx) { GError *error = NULL; if (!mm_base_bearer_connect_finish (bearer, res, &error)) { mm_obj_warn (ctx->self, "couldn't connect bearer: %s", error->message); g_dbus_method_invocation_take_error (ctx->invocation, error); connection_context_free (ctx); return; } ctx->step++; connection_step (ctx); } static void create_bearer_ready (MMIfaceModem *self, GAsyncResult *res, ConnectionContext *ctx) { GError *error = NULL; /* ownership for the caller */ ctx->bearer = mm_iface_modem_create_bearer_finish (self, res, &error); if (!ctx->bearer) { mm_obj_warn (ctx->self, "couldn't create bearer: %s", error->message); g_dbus_method_invocation_take_error (ctx->invocation, error); connection_context_free (ctx); return; } ctx->step++; connection_step (ctx); } static void packet_service_attach_in_3gpp_network_ready (MMIfaceModemSimple *self, GAsyncResult *res, ConnectionContext *ctx) { GError *error = NULL; if (!packet_service_attach_in_3gpp_network_finish (self, res, &error)) { mm_obj_warn (ctx->self, "packet service attach in 3GPP network failed: %s", error->message); g_dbus_method_invocation_take_error (ctx->invocation, error); connection_context_free (ctx); return; } ctx->step++; connection_step (ctx); } static void register_in_3gpp_or_cdma_network_ready (MMIfaceModemSimple *self, GAsyncResult *res, ConnectionContext *ctx) { GError *error = NULL; if (!register_in_3gpp_or_cdma_network_finish (self, res, &error)) { mm_obj_warn (ctx->self, "registration in network failed: %s", error->message); g_dbus_method_invocation_take_error (ctx->invocation, error); connection_context_free (ctx); return; } ctx->step++; connection_step (ctx); } static gboolean wait_after_enabled_ready (ConnectionContext *ctx) { /* Just go on now */ ctx->step++; connection_step (ctx); return G_SOURCE_REMOVE; } static void wait_for_enabled_ready (MMIfaceModem *self, GAsyncResult *res, ConnectionContext *ctx) { GError *error = NULL; MMModemState state; state = mm_iface_modem_wait_for_final_state_finish (self, res, &error); if (error) { mm_obj_warn (ctx->self, "failed waiting for final state: %s", error->message); g_dbus_method_invocation_take_error (ctx->invocation, error); connection_context_free (ctx); return; } if (state < MM_MODEM_STATE_ENABLED) { mm_obj_warn (ctx->self, "failed waiting for enabled state: %s", mm_modem_state_get_string (state)); g_dbus_method_invocation_return_error ( ctx->invocation, MM_CORE_ERROR, MM_CORE_ERROR_WRONG_STATE, "Couldn't wait for 'enabled': new state is '%s'", mm_modem_state_get_string (state)); connection_context_free (ctx); return; } ctx->step++; connection_step (ctx); } static void enable_ready (MMBaseModem *self, GAsyncResult *res, ConnectionContext *ctx) { GError *error = NULL; if (!mm_base_modem_enable_finish (MM_BASE_MODEM (self), res, &error)) { mm_obj_warn (ctx->self, "failed enabling modem: %s", error->message); g_dbus_method_invocation_take_error (ctx->invocation, error); connection_context_free (ctx); return; } ctx->step++; connection_step (ctx); } static void wait_for_initialized_ready (MMIfaceModem *self, GAsyncResult *res, ConnectionContext *ctx) { GError *error = NULL; mm_iface_modem_wait_for_final_state_finish (self, res, &error); if (error) { mm_obj_warn (ctx->self, "failed waiting for final state: %s", error->message); g_dbus_method_invocation_take_error (ctx->invocation, error); connection_context_free (ctx); return; } ctx->step++; connection_step (ctx); } static void send_pin_ready (MMBaseSim *sim, GAsyncResult *res, ConnectionContext *ctx) { GError *error = NULL; if (!mm_base_sim_send_pin_finish (sim, res, &error)) { mm_obj_warn (ctx->self, "failed sending SIM PIN: %s", error->message); g_dbus_method_invocation_take_error (ctx->invocation, error); connection_context_free (ctx); return; } ctx->step++; connection_step (ctx); } static void update_lock_info_ready (MMIfaceModem *self, GAsyncResult *res, ConnectionContext *ctx) { GError *error = NULL; MMModemLock lock; g_autoptr(MMBaseSim) sim = NULL; lock = mm_iface_modem_update_lock_info_finish (self, res, &error); if (error) { mm_obj_warn (ctx->self, "failed updating lock info: %s", error->message); g_dbus_method_invocation_take_error (ctx->invocation, error); connection_context_free (ctx); return; } /* If we are already unlocked, go on to next step. Note that we do also * allow SIM-PIN2/SIM-PUK2, as we don't need to unlock that in order to get * connected. */ if (lock == MM_MODEM_LOCK_NONE || lock == MM_MODEM_LOCK_SIM_PIN2 || lock == MM_MODEM_LOCK_SIM_PUK2) { ctx->step++; connection_step (ctx); return; } /* During simple connect we are only allowed to use SIM PIN */ if (lock != MM_MODEM_LOCK_SIM_PIN || !mm_simple_connect_properties_get_pin (ctx->properties)) { mm_obj_warn (ctx->self, "modem is locked with '%s'", mm_modem_lock_get_string (lock)); g_dbus_method_invocation_return_error ( ctx->invocation, MM_CORE_ERROR, MM_CORE_ERROR_UNAUTHORIZED, "Modem is locked with '%s' code; cannot unlock it", mm_modem_lock_get_string (lock)); connection_context_free (ctx); return; } /* Try to unlock the modem providing the PIN */ g_object_get (ctx->self, MM_IFACE_MODEM_SIM, &sim, NULL); if (!sim) { mm_obj_warn (ctx->self, "SIM is not accessible"); g_dbus_method_invocation_return_error ( ctx->invocation, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Cannot unlock modem, couldn't get access to the SIM"); connection_context_free (ctx); return; } mm_base_sim_send_pin (sim, mm_simple_connect_properties_get_pin (ctx->properties), (GAsyncReadyCallback)send_pin_ready, ctx); } static gboolean completed_if_cancelled (ConnectionContext *ctx) { /* Do nothing if not cancelled */ if (!g_cancellable_is_cancelled (ctx->cancellable)) return FALSE; /* Otherwise cancellable is valid and it's cancelled */ mm_obj_warn (ctx->self, "connection attempt cancelled"); g_dbus_method_invocation_return_error ( ctx->invocation, G_IO_ERROR, G_IO_ERROR_CANCELLED, "Connection attempt cancelled"); connection_context_free (ctx); return TRUE; } static gboolean setup_cancellation (ConnectionContext *ctx, GError **error) { Private *priv; /* Only one connection attempt by Simple.Connect() may be handled at a time */ priv = get_private (ctx->self); if (priv->ongoing_connect) { g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_IN_PROGRESS, "Connection request forbidden: operation already in progress"); return FALSE; } g_assert (!ctx->cancellable); ctx->cancellable = g_cancellable_new (); priv->ongoing_connect = g_object_ref (ctx->cancellable); return TRUE; } static void connection_step (ConnectionContext *ctx) { /* Early abort if operation is cancelled */ if (completed_if_cancelled (ctx)) return; switch (ctx->step) { case CONNECTION_STEP_FIRST: ctx->step++; /* fall through */ case CONNECTION_STEP_UNLOCK_CHECK: mm_obj_msg (ctx->self, "simple connect state (%d/%d): unlock check", ctx->step, CONNECTION_STEP_LAST); mm_iface_modem_update_lock_info (MM_IFACE_MODEM (ctx->self), MM_MODEM_LOCK_UNKNOWN, /* ask */ (GAsyncReadyCallback)update_lock_info_ready, ctx); return; case CONNECTION_STEP_WAIT_FOR_INITIALIZED: mm_obj_msg (ctx->self, "simple connect state (%d/%d): wait to get fully initialized", ctx->step, CONNECTION_STEP_LAST); mm_iface_modem_wait_for_final_state (MM_IFACE_MODEM (ctx->self), MM_MODEM_STATE_DISABLED, /* disabled == initialized */ (GAsyncReadyCallback)wait_for_initialized_ready, ctx); return; case CONNECTION_STEP_ENABLE: mm_obj_msg (ctx->self, "simple connect state (%d/%d): enable", ctx->step, CONNECTION_STEP_LAST); mm_base_modem_enable (MM_BASE_MODEM (ctx->self), (GAsyncReadyCallback)enable_ready, ctx); return; case CONNECTION_STEP_WAIT_FOR_ENABLED: mm_obj_msg (ctx->self, "simple connect state (%d/%d): wait to get fully enabled", ctx->step, CONNECTION_STEP_LAST); mm_iface_modem_wait_for_final_state (MM_IFACE_MODEM (ctx->self), MM_MODEM_STATE_UNKNOWN, /* just a final state */ (GAsyncReadyCallback)wait_for_enabled_ready, ctx); return; case CONNECTION_STEP_WAIT_AFTER_ENABLED: mm_obj_msg (ctx->self, "simple connect state (%d/%d): wait after enabled", ctx->step, CONNECTION_STEP_LAST); /* When we have just enabled, we want to give it some time before starting * the registration process, so that any pending registration update that may * have been scheduled during the enabling phase is applied. We don't want to * trigger a new automatic registration if e.g. we're already registered. */ g_timeout_add (100, (GSourceFunc)wait_after_enabled_ready, ctx); return; case CONNECTION_STEP_REGISTER: mm_obj_msg (ctx->self, "simple connect state (%d/%d): register", ctx->step, CONNECTION_STEP_LAST); if (mm_iface_modem_is_3gpp (MM_IFACE_MODEM (ctx->self)) || mm_iface_modem_is_cdma (MM_IFACE_MODEM (ctx->self))) { /* 3GPP or CDMA registration */ register_in_3gpp_or_cdma_network ( ctx->self, mm_simple_connect_properties_get_operator_id (ctx->properties), ctx->cancellable, (GAsyncReadyCallback)register_in_3gpp_or_cdma_network_ready, ctx); return; } /* If not 3GPP and not CDMA, this will possibly be a POTS modem, * which won't require any specific registration anywhere. */ ctx->step++; /* fall through */ case CONNECTION_STEP_PACKET_SERVICE_ATTACH: mm_obj_msg (ctx->self, "simple connect state (%d/%d): wait to get packet service state attached", ctx->step, CONNECTION_STEP_LAST); if (mm_iface_modem_is_3gpp (MM_IFACE_MODEM (ctx->self))) { packet_service_attach_in_3gpp_network ( ctx->self, ctx->cancellable, (GAsyncReadyCallback)packet_service_attach_in_3gpp_network_ready, ctx); return; } /* If not 3GPP, just go on */ ctx->step++; /* fall through */ case CONNECTION_STEP_BEARER: { g_autoptr(MMBearerProperties) bearer_properties = NULL; mm_obj_msg (ctx->self, "simple connect state (%d/%d): bearer", ctx->step, CONNECTION_STEP_LAST); bearer_properties = mm_simple_connect_properties_get_bearer_properties (ctx->properties); /* Check if the bearer we want to create is already in the list */ ctx->bearer = mm_bearer_list_find_by_properties (ctx->bearer_list, bearer_properties); if (!ctx->bearer) { mm_obj_dbg (ctx->self, "creating new bearer..."); mm_iface_modem_create_bearer (MM_IFACE_MODEM (ctx->self), bearer_properties, (GAsyncReadyCallback)create_bearer_ready, ctx); return; } mm_obj_dbg (ctx->self, "Using already existing bearer at '%s'...", mm_base_bearer_get_path (ctx->bearer)); ctx->step++; } /* fall through */ case CONNECTION_STEP_CONNECT: mm_obj_msg (ctx->self, "simple connect state (%d/%d): connect", ctx->step, CONNECTION_STEP_LAST); /* At this point, we can cleanup the cancellation point in the Simple interface, * because the bearer connection has its own cancellation setup. */ cleanup_cancellation (ctx); /* Wait... if we're already using an existing bearer, we need to check if it is * already connected; and if so, just don't do anything else */ if (mm_base_bearer_get_status (ctx->bearer) != MM_BEARER_STATUS_CONNECTED) { mm_base_bearer_connect (ctx->bearer, (GAsyncReadyCallback)connect_bearer_ready, ctx); return; } mm_obj_info (ctx->self, "bearer at '%s' is already connected...", mm_base_bearer_get_path (ctx->bearer)); ctx->step++; /* fall through */ case CONNECTION_STEP_LAST: mm_obj_msg (ctx->self, "simple connect state (%d/%d): all done", ctx->step, CONNECTION_STEP_LAST); /* All done, yey! */ mm_gdbus_modem_simple_complete_connect ( ctx->skeleton, ctx->invocation, mm_base_bearer_get_path (ctx->bearer)); connection_context_free (ctx); return; default: break; } g_assert_not_reached (); } static void connect_auth_ready (MMBaseModem *self, GAsyncResult *res, ConnectionContext *ctx) { GError *error = NULL; MMModemState current = MM_MODEM_STATE_UNKNOWN; if (!mm_base_modem_authorize_finish (self, res, &error)) { g_dbus_method_invocation_take_error (ctx->invocation, error); connection_context_free (ctx); return; } ctx->properties = mm_simple_connect_properties_new_from_dictionary (ctx->dictionary, &error); if (!ctx->properties) { g_dbus_method_invocation_take_error (ctx->invocation, error); connection_context_free (ctx); return; } mm_obj_info (self, "processing user request to connect modem..."); mm_log_simple_connect_properties (self, MM_LOG_LEVEL_INFO, " ", ctx->properties); if (!setup_cancellation (ctx, &error)) { g_dbus_method_invocation_take_error (ctx->invocation, error); connection_context_free (ctx); return; } /* We may be able to skip some steps, so check that before doing anything */ g_object_get (self, MM_IFACE_MODEM_STATE, ¤t, MM_IFACE_MODEM_BEARER_LIST, &ctx->bearer_list, NULL); if (!ctx->bearer_list) { g_dbus_method_invocation_return_error ( ctx->invocation, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Couldn't get the bearer list"); connection_context_free (ctx); return; } mm_obj_msg (self, "simple connect started..."); switch (current) { case MM_MODEM_STATE_FAILED: case MM_MODEM_STATE_UNKNOWN: case MM_MODEM_STATE_LOCKED: /* If we need unlocking, start from the very beginning */ ctx->step = CONNECTION_STEP_FIRST; break; case MM_MODEM_STATE_INITIALIZING: case MM_MODEM_STATE_DISABLING: /* If we are transitioning to the DISABLED (initialized) state, * wait to get there before going on */ ctx->step = CONNECTION_STEP_WAIT_FOR_INITIALIZED; break; case MM_MODEM_STATE_DISABLED: ctx->step = CONNECTION_STEP_ENABLE; break; case MM_MODEM_STATE_ENABLING: case MM_MODEM_STATE_DISCONNECTING: case MM_MODEM_STATE_SEARCHING: case MM_MODEM_STATE_CONNECTING: /* Wait to get to a final state before going on */ ctx->step = CONNECTION_STEP_WAIT_FOR_ENABLED; break; case MM_MODEM_STATE_ENABLED: case MM_MODEM_STATE_REGISTERED: case MM_MODEM_STATE_CONNECTED: /* If we are at least already enabled, start at the registration check * right away */ ctx->step = CONNECTION_STEP_REGISTER; break; default: g_assert_not_reached (); break; } connection_step (ctx); } static gboolean handle_connect (MmGdbusModemSimple *skeleton, GDBusMethodInvocation *invocation, GVariant *dictionary, MMIfaceModemSimple *self) { ConnectionContext *ctx; ctx = g_slice_new0 (ConnectionContext); ctx->skeleton = g_object_ref (skeleton); ctx->invocation = g_object_ref (invocation); ctx->self = g_object_ref (self); ctx->dictionary = g_variant_ref (dictionary); mm_base_modem_authorize (MM_BASE_MODEM (self), invocation, MM_AUTHORIZATION_DEVICE_CONTROL, (GAsyncReadyCallback)connect_auth_ready, ctx); return TRUE; } /*****************************************************************************/ typedef struct { MMIfaceModemSimple *self; MmGdbusModemSimple *skeleton; GDBusMethodInvocation *invocation; gchar *bearer_path; } DisconnectionContext; static void disconnection_context_free (DisconnectionContext *ctx) { g_object_unref (ctx->skeleton); g_object_unref (ctx->invocation); g_object_unref (ctx->self); g_free (ctx->bearer_path); g_slice_free (DisconnectionContext, ctx); } static void bearer_list_disconnect_bearers_ready (MMBearerList *bearer_list, GAsyncResult *res, DisconnectionContext *ctx) { GError *error = NULL; if (!mm_bearer_list_disconnect_bearers_finish (bearer_list, res, &error)) { mm_obj_warn (ctx->self, "failed to disconnect bearers: %s", error->message); g_dbus_method_invocation_take_error (ctx->invocation, error); } else { mm_obj_info (ctx->self, "all requested bearers disconnected"); mm_gdbus_modem_simple_complete_disconnect (ctx->skeleton, ctx->invocation); } disconnection_context_free (ctx); } static void disconnect_auth_ready (MMBaseModem *self, GAsyncResult *res, DisconnectionContext *ctx) { g_autoptr(MMBearerList) list = NULL; GError *error = NULL; if (!mm_base_modem_authorize_finish (self, res, &error)) { g_dbus_method_invocation_take_error (ctx->invocation, error); disconnection_context_free (ctx); return; } /* If not disconnecting a specific bearer, also cancel any ongoing * connection attempt. */ if (!ctx->bearer_path) mm_iface_modem_simple_abort_ongoing (MM_IFACE_MODEM_SIMPLE (self)); g_object_get (self, MM_IFACE_MODEM_BEARER_LIST, &list, NULL); if (!list) { g_dbus_method_invocation_return_error ( ctx->invocation, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Couldn't get the bearer list"); disconnection_context_free (ctx); return; } if (ctx->bearer_path) mm_obj_info (self, "processing user request to disconnect modem: bearer '%s'", ctx->bearer_path); else mm_obj_info (self, "processing user request to disconnect modem: all bearers"); mm_bearer_list_disconnect_bearers (list, ctx->bearer_path, (GAsyncReadyCallback)bearer_list_disconnect_bearers_ready, ctx); } static gboolean handle_disconnect (MmGdbusModemSimple *skeleton, GDBusMethodInvocation *invocation, const gchar *bearer_path, MMIfaceModemSimple *self) { DisconnectionContext *ctx; ctx = g_slice_new0 (DisconnectionContext); ctx->skeleton = g_object_ref (skeleton); ctx->self = g_object_ref (self); ctx->invocation = g_object_ref (invocation); /* The Disconnect() method expects a valid object path given in bearer path, * it cannot be NULL, so we assume we get '/' when we're asked to disconnect * all connected bearers, as that is what mm_modem_simple_disconnect() does * when a NULL bearer path is given to that method. * * We will detect the '/' string and set the bearer path as NULL in the * context if so, and otherwise use the given input string as path */ if (g_strcmp0 (bearer_path, "/") != 0) ctx->bearer_path = g_strdup (bearer_path); mm_base_modem_authorize (MM_BASE_MODEM (self), invocation, MM_AUTHORIZATION_DEVICE_CONTROL, (GAsyncReadyCallback)disconnect_auth_ready, ctx); return TRUE; } /*****************************************************************************/ static gboolean handle_get_status (MmGdbusModemSimple *skeleton, GDBusMethodInvocation *invocation, MMIfaceModemSimple *self) { MMSimpleStatus *status = NULL; GVariant *dictionary; g_object_get (self, MM_IFACE_MODEM_SIMPLE_STATUS, &status, NULL); dictionary = mm_simple_status_get_dictionary (status); mm_gdbus_modem_simple_complete_get_status (skeleton, invocation, dictionary); g_variant_unref (dictionary); g_object_unref (status); return TRUE; } /*****************************************************************************/ void mm_iface_modem_simple_initialize (MMIfaceModemSimple *self) { MmGdbusModemSimple *skeleton = NULL; /* Did we already create it? */ g_object_get (self, MM_IFACE_MODEM_SIMPLE_DBUS_SKELETON, &skeleton, NULL); if (!skeleton) { skeleton = mm_gdbus_modem_simple_skeleton_new (); g_object_set (self, MM_IFACE_MODEM_SIMPLE_DBUS_SKELETON, skeleton, NULL); /* Handle method invocations */ g_signal_connect (skeleton, "handle-connect", G_CALLBACK (handle_connect), self); g_signal_connect (skeleton, "handle-disconnect", G_CALLBACK (handle_disconnect), self); g_signal_connect (skeleton, "handle-get-status", G_CALLBACK (handle_get_status), self); /* Finally, export the new interface */ mm_gdbus_object_skeleton_set_modem_simple (MM_GDBUS_OBJECT_SKELETON (self), MM_GDBUS_MODEM_SIMPLE (skeleton)); } g_object_unref (skeleton); } void mm_iface_modem_simple_shutdown (MMIfaceModemSimple *self) { /* Unexport DBus interface and remove the skeleton */ mm_gdbus_object_skeleton_set_modem_simple (MM_GDBUS_OBJECT_SKELETON (self), NULL); g_object_set (self, MM_IFACE_MODEM_SIMPLE_DBUS_SKELETON, NULL, NULL); } /*****************************************************************************/ static void iface_modem_simple_init (gpointer g_iface) { static gboolean initialized = FALSE; if (initialized) return; /* Properties */ g_object_interface_install_property (g_iface, g_param_spec_object (MM_IFACE_MODEM_SIMPLE_DBUS_SKELETON, "Simple DBus skeleton", "DBus skeleton for the Simple interface", MM_GDBUS_TYPE_MODEM_SIMPLE_SKELETON, G_PARAM_READWRITE)); g_object_interface_install_property (g_iface, g_param_spec_object (MM_IFACE_MODEM_SIMPLE_STATUS, "Simple status", "Compilation of status values", MM_TYPE_SIMPLE_STATUS, G_PARAM_READWRITE)); initialized = TRUE; } GType mm_iface_modem_simple_get_type (void) { static GType iface_modem_simple_type = 0; if (!G_UNLIKELY (iface_modem_simple_type)) { static const GTypeInfo info = { sizeof (MMIfaceModemSimple), /* class_size */ iface_modem_simple_init, /* base_init */ NULL, /* base_finalize */ }; iface_modem_simple_type = g_type_register_static (G_TYPE_INTERFACE, "MMIfaceModemSimple", &info, 0); g_type_interface_add_prerequisite (iface_modem_simple_type, MM_TYPE_IFACE_MODEM); } return iface_modem_simple_type; }