Files
ModemManager/src/mm-iface-modem-simple.c
T
Aleksander Morgado 35e20a48c8 api,modem: deprecate the 'MaxBearers' property
This property was never initialized with a correct value, we would
always use the same value as used for MaxActiveBearers, which isn't
technically right. Just suggest to ignore this property, and flag
it as deprecated.

In addition to deprecating the API property, we will internally no
longer limit the amount of bearer objects that may be created with
different connection settings. The bearer object exposed in DBus is a
representation of the connection that may be setup, and so we can hold
as many representations as we want as long as the amount of connected
bearers doesn't exceed the maximum amount for each modem.

Leaving around the disconnected modems also serves another purpose.
Each bearer holds information about the amount of times a connection
with it has been attempted, and the amount of data transferred through
it. If we use the Simple.Connect() method to connect the modem using
different bearer settings, we won't lose the information of the past
connection attempts with different settings.
2021-03-10 10:59:22 +01:00

1077 lines
34 KiB
C

/* -*- 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.
*/
#include <ModemManager.h>
#define _LIBMM_INSIDE_MM
#include <libmm-glib.h>
#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"
/*****************************************************************************/
/* 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;
}
/*****************************************************************************/
/* 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_free (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! */
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! */
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 (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 */
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,
GAsyncReadyCallback callback,
gpointer user_data)
{
RegisterInNetworkContext *ctx;
GTask *task;
ctx = g_new0 (RegisterInNetworkContext, 1);
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, NULL, callback, user_data);
g_task_set_task_data (task,
ctx,
(GDestroyNotify)register_in_network_context_free);
check_next_registration (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_REGISTER,
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_free (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_dbg (ctx->self, "couldn't connect bearer: %s", error->message);
g_dbus_method_invocation_take_error (ctx->invocation, error);
connection_context_free (ctx);
return;
}
/* Bearer connected.... all done!!!!! */
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) {
g_dbus_method_invocation_take_error (ctx->invocation, error);
connection_context_free (ctx);
return;
}
/* Bearer available! */
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)) {
g_dbus_method_invocation_take_error (ctx->invocation, error);
connection_context_free (ctx);
return;
}
/* Registered now! */
ctx->step++;
connection_step (ctx);
}
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) {
g_dbus_method_invocation_take_error (ctx->invocation, error);
connection_context_free (ctx);
return;
}
if (state < MM_MODEM_STATE_ENABLED) {
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;
}
/* Enabled now, cool. */
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)) {
g_dbus_method_invocation_take_error (ctx->invocation, error);
connection_context_free (ctx);
return;
}
/* Enabling done!, keep on!!! */
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) {
g_dbus_method_invocation_take_error (ctx->invocation, error);
connection_context_free (ctx);
return;
}
/* Initialized now, cool. */
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)) {
g_dbus_method_invocation_take_error (ctx->invocation, error);
connection_context_free (ctx);
return;
}
/* Sent pin, cool. */
ctx->step++;
connection_step (ctx);
}
static void
update_lock_info_ready (MMIfaceModem *self,
GAsyncResult *res,
ConnectionContext *ctx)
{
GError *error = NULL;
MMModemLock lock;
MMBaseSim *sim;
lock = mm_iface_modem_update_lock_info_finish (self, res, &error);
if (error) {
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)) {
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 */
sim = NULL;
g_object_get (ctx->self,
MM_IFACE_MODEM_SIM, &sim,
NULL);
if (!sim) {
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);
g_object_unref (sim);
}
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 */
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_info (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_info (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_info (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_info (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_REGISTER:
mm_obj_info (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),
(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_BEARER: {
g_autoptr(MMBearerProperties) bearer_properties = NULL;
mm_obj_info (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_info (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_dbg (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_info (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;
}
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, &current,
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_info (self, "simple connect started...");
/* Log about all the parameters being used for the simple connect */
{
MMBearerAllowedAuth allowed_auth;
gchar *str;
MMBearerIpFamily ip_family;
#define VALIDATE_UNSPECIFIED(str) (str ? str : "unspecified")
mm_obj_dbg (self, " PIN: %s", VALIDATE_UNSPECIFIED (mm_simple_connect_properties_get_pin (ctx->properties)));
mm_obj_dbg (self, " operator ID: %s", VALIDATE_UNSPECIFIED (mm_simple_connect_properties_get_operator_id (ctx->properties)));
mm_obj_dbg (self, " allowed roaming: %s", mm_simple_connect_properties_get_allow_roaming (ctx->properties) ? "yes" : "no");
mm_obj_dbg (self, " APN: %s", VALIDATE_UNSPECIFIED (mm_simple_connect_properties_get_apn (ctx->properties)));
ip_family = mm_simple_connect_properties_get_ip_type (ctx->properties);
if (ip_family != MM_BEARER_IP_FAMILY_NONE) {
str = mm_bearer_ip_family_build_string_from_mask (ip_family);
mm_obj_dbg (self, " IP family: %s", str);
g_free (str);
} else
mm_obj_dbg (self, " IP family: %s", VALIDATE_UNSPECIFIED (NULL));
allowed_auth = mm_simple_connect_properties_get_allowed_auth (ctx->properties);
if (allowed_auth != MM_BEARER_ALLOWED_AUTH_UNKNOWN) {
str = mm_bearer_allowed_auth_build_string_from_mask (allowed_auth);
mm_obj_dbg (self, " allowed authentication: %s", str);
g_free (str);
} else
mm_obj_dbg (self, " allowed authentication: %s", VALIDATE_UNSPECIFIED (NULL));
mm_obj_dbg (self, " User: %s", VALIDATE_UNSPECIFIED (mm_simple_connect_properties_get_user (ctx->properties)));
mm_obj_dbg (self, " Password: %s", VALIDATE_UNSPECIFIED (mm_simple_connect_properties_get_password (ctx->properties)));
#undef VALIDATE_UNSPECIFIED
}
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:
/* If we are transitioning to the ENABLED/REGISTERED state,
* wait to get there before going on */
ctx->step = CONNECTION_STEP_WAIT_FOR_ENABLED;
break;
case MM_MODEM_STATE_ENABLED:
case MM_MODEM_STATE_SEARCHING:
case MM_MODEM_STATE_REGISTERED:
case MM_MODEM_STATE_CONNECTING:
case MM_MODEM_STATE_CONNECTED:
ctx->step = CONNECTION_STEP_ENABLE + 1;
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_new0 (ConnectionContext, 1);
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_obj_dbg (self, "user request to connect modem");
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;
GList *bearers;
MMBaseBearer *current;
} 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);
if (ctx->current)
g_object_unref (ctx->current);
g_list_free_full (ctx->bearers, g_object_unref);
g_free (ctx);
}
static void disconnect_next_bearer (DisconnectionContext *ctx);
static void
disconnect_ready (MMBaseBearer *bearer,
GAsyncResult *res,
DisconnectionContext *ctx)
{
GError *error = NULL;
if (!mm_base_bearer_disconnect_finish (bearer, res, &error)) {
g_dbus_method_invocation_take_error (ctx->invocation, error);
disconnection_context_free (ctx);
return;
}
disconnect_next_bearer (ctx);
}
static void
disconnect_next_bearer (DisconnectionContext *ctx)
{
if (ctx->current)
g_clear_object (&ctx->current);
/* No more bearers? all done! */
if (!ctx->bearers) {
mm_gdbus_modem_simple_complete_disconnect (ctx->skeleton,
ctx->invocation);
disconnection_context_free (ctx);
return;
}
ctx->current = MM_BASE_BEARER (ctx->bearers->data);
ctx->bearers = g_list_delete_link (ctx->bearers, ctx->bearers);
mm_base_bearer_disconnect (ctx->current,
(GAsyncReadyCallback)disconnect_ready,
ctx);
}
static void
build_connected_bearer_list (MMBaseBearer *bearer,
DisconnectionContext *ctx)
{
if (!ctx->bearer_path ||
g_str_equal (ctx->bearer_path, mm_base_bearer_get_path (bearer)))
ctx->bearers = g_list_prepend (ctx->bearers, g_object_ref (bearer));
}
static void
disconnect_auth_ready (MMBaseModem *self,
GAsyncResult *res,
DisconnectionContext *ctx)
{
GError *error = NULL;
MMBearerList *list = NULL;
Private *priv;
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. */
priv = get_private (MM_IFACE_MODEM_SIMPLE (self));
if (!ctx->bearer_path && priv->ongoing_connect) {
g_cancellable_cancel (priv->ongoing_connect);
g_clear_object (&priv->ongoing_connect);
}
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;
}
mm_bearer_list_foreach (list,
(MMBearerListForeachFunc)build_connected_bearer_list,
ctx);
g_object_unref (list);
if (ctx->bearer_path &&
!ctx->bearers) {
g_dbus_method_invocation_return_error (
ctx->invocation,
MM_CORE_ERROR,
MM_CORE_ERROR_INVALID_ARGS,
"Couldn't disconnect bearer '%s': not found",
ctx->bearer_path);
disconnection_context_free (ctx);
return;
}
/* Go on disconnecting bearers */
disconnect_next_bearer (ctx);
}
static gboolean
handle_disconnect (MmGdbusModemSimple *skeleton,
GDBusMethodInvocation *invocation,
const gchar *bearer_path,
MMIfaceModemSimple *self)
{
DisconnectionContext *ctx;
ctx = g_new0 (DisconnectionContext, 1);
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) {
mm_obj_dbg (self, "user request to disconnect modem (bearer '%s')", bearer_path);
ctx->bearer_path = g_strdup (bearer_path);
} else
mm_obj_dbg (self, "user request to disconnect modem (all bearers)");
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;
}