aboutsummaryrefslogtreecommitdiff
path: root/src/mm-bearer.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/mm-bearer.c')
-rw-r--r--src/mm-bearer.c1283
1 files changed, 1283 insertions, 0 deletions
diff --git a/src/mm-bearer.c b/src/mm-bearer.c
new file mode 100644
index 0000000..c057f64
--- /dev/null
+++ b/src/mm-bearer.c
@@ -0,0 +1,1283 @@
+/* -*- 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 Google, Inc.
+ * Copyright (C) 2011 - 2013 Aleksander Morgado <aleksander@gnu.org>
+ */
+
+#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-daemon-enums-types.h"
+#include "mm-iface-modem.h"
+#include "mm-iface-modem-3gpp.h"
+#include "mm-iface-modem-cdma.h"
+#include "mm-bearer.h"
+#include "mm-base-modem-at.h"
+#include "mm-base-modem.h"
+#include "mm-log.h"
+#include "mm-modem-helpers.h"
+
+/* We require up to 20s to get a proper IP when using PPP */
+#define MM_BEARER_IP_TIMEOUT_DEFAULT 20
+
+#define MM_BEARER_DEFERRED_UNREGISTRATION_TIMEOUT 15
+
+G_DEFINE_TYPE (MMBearer, mm_bearer, MM_GDBUS_TYPE_BEARER_SKELETON);
+
+
+typedef enum {
+ CONNECTION_FORBIDDEN_REASON_NONE,
+ CONNECTION_FORBIDDEN_REASON_UNREGISTERED,
+ CONNECTION_FORBIDDEN_REASON_ROAMING,
+ CONNECTION_FORBIDDEN_REASON_LAST
+} ConnectionForbiddenReason;
+
+enum {
+ PROP_0,
+ PROP_PATH,
+ PROP_CONNECTION,
+ PROP_MODEM,
+ PROP_STATUS,
+ PROP_CONFIG,
+ PROP_DEFAULT_IP_FAMILY,
+ PROP_LAST
+};
+
+static GParamSpec *properties[PROP_LAST];
+
+struct _MMBearerPrivate {
+ /* The connection to the system bus */
+ GDBusConnection *connection;
+ /* The modem which owns this BEARER */
+ MMBaseModem *modem;
+ /* The path where the BEARER object is exported */
+ gchar *path;
+ /* Status of this bearer */
+ MMBearerStatus status;
+ /* Configuration of the bearer */
+ MMBearerProperties *config;
+ /* Default IP family of this bearer */
+ MMBearerIpFamily default_ip_family;
+
+ /* Cancellable for connect() */
+ GCancellable *connect_cancellable;
+ /* handler id for the disconnect + cancel connect request */
+ gulong disconnect_signal_handler;
+
+ /*-- 3GPP specific --*/
+ guint deferred_3gpp_unregistration_id;
+ /* Reason if 3GPP connection is forbidden */
+ ConnectionForbiddenReason reason_3gpp;
+ /* Handler ID for the registration state change signals */
+ guint id_3gpp_registration_change;
+
+ /*-- CDMA specific --*/
+ guint deferred_cdma_unregistration_id;
+ /* Reason if CDMA connection is forbidden */
+ ConnectionForbiddenReason reason_cdma;
+ /* Handler IDs for the registration state change signals */
+ guint id_cdma1x_registration_change;
+ guint id_evdo_registration_change;
+};
+
+/*****************************************************************************/
+
+static const gchar *connection_forbidden_reason_str [CONNECTION_FORBIDDEN_REASON_LAST] = {
+ "none",
+ "Not registered in the network",
+ "Registered in roaming network, and roaming not allowed"
+};
+
+/*****************************************************************************/
+
+void
+mm_bearer_export (MMBearer *self)
+{
+ static guint id = 0;
+ gchar *path;
+
+ path = g_strdup_printf (MM_DBUS_BEARER_PREFIX "/%d", id++);
+ g_object_set (self,
+ MM_BEARER_PATH, path,
+ NULL);
+ g_free (path);
+}
+
+/*****************************************************************************/
+
+static void
+bearer_reset_interface_status (MMBearer *self)
+{
+ mm_gdbus_bearer_set_connected (MM_GDBUS_BEARER (self), FALSE);
+ mm_gdbus_bearer_set_suspended (MM_GDBUS_BEARER (self), FALSE);
+ mm_gdbus_bearer_set_interface (MM_GDBUS_BEARER (self), NULL);
+ mm_gdbus_bearer_set_ip4_config (
+ MM_GDBUS_BEARER (self),
+ mm_bearer_ip_config_get_dictionary (NULL));
+ mm_gdbus_bearer_set_ip6_config (
+ MM_GDBUS_BEARER (self),
+ mm_bearer_ip_config_get_dictionary (NULL));
+}
+
+static void
+bearer_update_status (MMBearer *self,
+ MMBearerStatus status)
+{
+ /* NOTE: we do allow status 'CONNECTED' here; it may happen if we go into
+ * DISCONNECTING and we cannot disconnect */
+
+ /* Update the property value */
+ self->priv->status = status;
+ g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_STATUS]);
+
+ /* Ensure that we don't expose any connection related data in the
+ * interface when going into disconnected state. */
+ if (self->priv->status == MM_BEARER_STATUS_DISCONNECTED)
+ bearer_reset_interface_status (self);
+}
+
+static void
+bearer_update_status_connected (MMBearer *self,
+ const gchar *interface,
+ MMBearerIpConfig *ipv4_config,
+ MMBearerIpConfig *ipv6_config)
+{
+ mm_gdbus_bearer_set_connected (MM_GDBUS_BEARER (self), TRUE);
+ mm_gdbus_bearer_set_suspended (MM_GDBUS_BEARER (self), FALSE);
+ mm_gdbus_bearer_set_interface (MM_GDBUS_BEARER (self), interface);
+ mm_gdbus_bearer_set_ip4_config (
+ MM_GDBUS_BEARER (self),
+ mm_bearer_ip_config_get_dictionary (ipv4_config));
+ mm_gdbus_bearer_set_ip6_config (
+ MM_GDBUS_BEARER (self),
+ mm_bearer_ip_config_get_dictionary (ipv6_config));
+
+ /* Update the property value */
+ self->priv->status = MM_BEARER_STATUS_CONNECTED;
+ g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_STATUS]);
+}
+
+/*****************************************************************************/
+
+static void
+reset_deferred_unregistration (MMBearer *self)
+{
+ if (self->priv->deferred_cdma_unregistration_id) {
+ g_source_remove (self->priv->deferred_cdma_unregistration_id);
+ self->priv->deferred_cdma_unregistration_id = 0;
+ }
+
+ if (self->priv->deferred_3gpp_unregistration_id) {
+ g_source_remove (self->priv->deferred_3gpp_unregistration_id);
+ self->priv->deferred_3gpp_unregistration_id = 0;
+ }
+}
+
+static gboolean
+deferred_3gpp_unregistration_cb (MMBearer *self)
+{
+ g_warn_if_fail (self->priv->reason_3gpp == CONNECTION_FORBIDDEN_REASON_UNREGISTERED);
+ self->priv->deferred_3gpp_unregistration_id = 0;
+
+ mm_dbg ("Forcing bearer disconnection, not registered in 3GPP network");
+ mm_bearer_disconnect_force (MM_BEARER (self));
+ return FALSE;
+}
+
+static void
+modem_3gpp_registration_state_changed (MMIfaceModem3gpp *modem,
+ GParamSpec *pspec,
+ MMBearer *self)
+{
+ MMModem3gppRegistrationState state = MM_MODEM_3GPP_REGISTRATION_STATE_UNKNOWN;
+
+ g_object_get (modem,
+ MM_IFACE_MODEM_3GPP_REGISTRATION_STATE, &state,
+ NULL);
+
+ switch (state) {
+ case MM_MODEM_3GPP_REGISTRATION_STATE_IDLE:
+ case MM_MODEM_3GPP_REGISTRATION_STATE_DENIED:
+ case MM_MODEM_3GPP_REGISTRATION_STATE_UNKNOWN:
+ self->priv->reason_3gpp = CONNECTION_FORBIDDEN_REASON_UNREGISTERED;
+ break;
+ case MM_MODEM_3GPP_REGISTRATION_STATE_HOME:
+ case MM_MODEM_3GPP_REGISTRATION_STATE_SEARCHING:
+ self->priv->reason_3gpp = CONNECTION_FORBIDDEN_REASON_NONE;
+ break;
+ case MM_MODEM_3GPP_REGISTRATION_STATE_ROAMING:
+ if (mm_bearer_properties_get_allow_roaming (mm_bearer_peek_config (MM_BEARER (self))))
+ self->priv->reason_3gpp = CONNECTION_FORBIDDEN_REASON_NONE;
+ else
+ self->priv->reason_3gpp = CONNECTION_FORBIDDEN_REASON_ROAMING;
+ break;
+ }
+
+ /* If no reason to disconnect, or if it's a mixed CDMA+LTE modem without a CDMA reason,
+ * just don't do anything. */
+ if (self->priv->reason_3gpp == CONNECTION_FORBIDDEN_REASON_NONE ||
+ (mm_iface_modem_is_cdma (MM_IFACE_MODEM (modem)) &&
+ self->priv->reason_cdma == CONNECTION_FORBIDDEN_REASON_NONE)) {
+ reset_deferred_unregistration (self);
+ return;
+ }
+
+ /* Modem is roaming and roaming not allowed, report right away */
+ if (self->priv->reason_3gpp == CONNECTION_FORBIDDEN_REASON_ROAMING) {
+ mm_dbg ("Bearer not allowed to connect, registered in roaming 3GPP network");
+ reset_deferred_unregistration (self);
+ mm_bearer_disconnect_force (MM_BEARER (self));
+ return;
+ }
+
+ /* Modem reports being unregistered */
+ if (self->priv->reason_3gpp == CONNECTION_FORBIDDEN_REASON_UNREGISTERED) {
+ /* If there is already a notification pending, just return */
+ if (self->priv->deferred_3gpp_unregistration_id)
+ return;
+
+ /* If the bearer is not connected, report right away */
+ if (self->priv->status != MM_BEARER_STATUS_CONNECTED) {
+ mm_dbg ("Bearer not allowed to connect, not registered in 3GPP network");
+ mm_bearer_disconnect_force (MM_BEARER (self));
+ return;
+ }
+
+ /* Otherwise, setup the new timeout */
+ mm_dbg ("Connected bearer not registered in 3GPP network");
+ self->priv->deferred_3gpp_unregistration_id =
+ g_timeout_add_seconds (MM_BEARER_DEFERRED_UNREGISTRATION_TIMEOUT,
+ (GSourceFunc) deferred_3gpp_unregistration_cb,
+ self);
+ return;
+ }
+
+ g_assert_not_reached ();
+}
+
+static gboolean
+deferred_cdma_unregistration_cb (MMBearer *self)
+{
+ g_warn_if_fail (self->priv->reason_cdma == CONNECTION_FORBIDDEN_REASON_UNREGISTERED);
+ self->priv->deferred_cdma_unregistration_id = 0;
+
+ mm_dbg ("Forcing bearer disconnection, not registered in CDMA network");
+ mm_bearer_disconnect_force (MM_BEARER (self));
+ return FALSE;
+}
+
+static void
+modem_cdma_registration_state_changed (MMIfaceModemCdma *modem,
+ GParamSpec *pspec,
+ MMBearer *self)
+{
+ MMModemCdmaRegistrationState cdma1x_state = MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN;
+ MMModemCdmaRegistrationState evdo_state = MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN;
+
+ g_object_get (modem,
+ MM_IFACE_MODEM_CDMA_CDMA1X_REGISTRATION_STATE, &cdma1x_state,
+ MM_IFACE_MODEM_CDMA_EVDO_REGISTRATION_STATE, &evdo_state,
+ NULL);
+
+ if (cdma1x_state == MM_MODEM_CDMA_REGISTRATION_STATE_ROAMING ||
+ evdo_state == MM_MODEM_CDMA_REGISTRATION_STATE_ROAMING) {
+ if (mm_bearer_properties_get_allow_roaming (mm_bearer_peek_config (MM_BEARER (self))))
+ self->priv->reason_cdma = CONNECTION_FORBIDDEN_REASON_NONE;
+ else
+ self->priv->reason_cdma = CONNECTION_FORBIDDEN_REASON_ROAMING;
+ } else if (cdma1x_state != MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN ||
+ evdo_state != MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN) {
+ self->priv->reason_cdma = CONNECTION_FORBIDDEN_REASON_NONE;
+ } else {
+ self->priv->reason_cdma = CONNECTION_FORBIDDEN_REASON_UNREGISTERED;
+ }
+
+ /* If no reason to disconnect, or if it's a mixed CDMA+LTE modem without a 3GPP reason,
+ * just don't do anything. */
+ if (self->priv->reason_cdma == CONNECTION_FORBIDDEN_REASON_NONE ||
+ (mm_iface_modem_is_3gpp (MM_IFACE_MODEM (modem)) &&
+ self->priv->reason_3gpp == CONNECTION_FORBIDDEN_REASON_NONE)) {
+ reset_deferred_unregistration (self);
+ return;
+ }
+
+ /* Modem is roaming and roaming not allowed, report right away */
+ if (self->priv->reason_cdma == CONNECTION_FORBIDDEN_REASON_ROAMING) {
+ mm_dbg ("Bearer not allowed to connect, registered in roaming CDMA network");
+ reset_deferred_unregistration (self);
+ mm_bearer_disconnect_force (MM_BEARER (self));
+ return;
+ }
+
+ /* Modem reports being unregistered */
+ if (self->priv->reason_cdma == CONNECTION_FORBIDDEN_REASON_UNREGISTERED) {
+ /* If there is already a notification pending, just return */
+ if (self->priv->deferred_cdma_unregistration_id)
+ return;
+
+ /* If the bearer is not connected, report right away */
+ if (self->priv->status != MM_BEARER_STATUS_CONNECTED) {
+ mm_dbg ("Bearer not allowed to connect, not registered in CDMA network");
+ mm_bearer_disconnect_force (MM_BEARER (self));
+ return;
+ }
+
+ /* Otherwise, setup the new timeout */
+ mm_dbg ("Connected bearer not registered in CDMA network");
+ self->priv->deferred_cdma_unregistration_id =
+ g_timeout_add_seconds (MM_BEARER_DEFERRED_UNREGISTRATION_TIMEOUT,
+ (GSourceFunc) deferred_cdma_unregistration_cb,
+ self);
+ return;
+ }
+
+ g_assert_not_reached ();
+}
+
+static void
+set_signal_handlers (MMBearer *self)
+{
+ g_assert (self->priv->modem != NULL);
+ g_assert (self->priv->config != NULL);
+
+ /* Don't set the 3GPP registration change signal handlers if they
+ * are already set. */
+ if (mm_iface_modem_is_3gpp (MM_IFACE_MODEM (self->priv->modem)) &&
+ !self->priv->id_3gpp_registration_change) {
+ self->priv->id_3gpp_registration_change =
+ g_signal_connect (self->priv->modem,
+ "notify::" MM_IFACE_MODEM_3GPP_REGISTRATION_STATE,
+ G_CALLBACK (modem_3gpp_registration_state_changed),
+ self);
+ modem_3gpp_registration_state_changed (MM_IFACE_MODEM_3GPP (self->priv->modem), NULL, self);
+ }
+
+ /* Don't set the CDMA1x/EV-DO registration change signal handlers if they
+ * are already set. */
+ if (mm_iface_modem_is_cdma (MM_IFACE_MODEM (self->priv->modem)) &&
+ !self->priv->id_cdma1x_registration_change &&
+ !self->priv->id_evdo_registration_change) {
+ self->priv->id_cdma1x_registration_change =
+ g_signal_connect (self->priv->modem,
+ "notify::" MM_IFACE_MODEM_CDMA_CDMA1X_REGISTRATION_STATE,
+ G_CALLBACK (modem_cdma_registration_state_changed),
+ self);
+ self->priv->id_evdo_registration_change =
+ g_signal_connect (self->priv->modem,
+ "notify::" MM_IFACE_MODEM_CDMA_EVDO_REGISTRATION_STATE,
+ G_CALLBACK (modem_cdma_registration_state_changed),
+ self);
+ modem_cdma_registration_state_changed (MM_IFACE_MODEM_CDMA (self->priv->modem), NULL, self);
+ }
+}
+
+static void
+reset_signal_handlers (MMBearer *self)
+{
+ if (!self->priv->modem)
+ return;
+
+ if (self->priv->id_3gpp_registration_change) {
+ if (g_signal_handler_is_connected (self->priv->modem, self->priv->id_3gpp_registration_change))
+ g_signal_handler_disconnect (self->priv->modem, self->priv->id_3gpp_registration_change);
+ self->priv->id_3gpp_registration_change = 0;
+ }
+ if (self->priv->id_cdma1x_registration_change) {
+ if (g_signal_handler_is_connected (self->priv->modem, self->priv->id_cdma1x_registration_change))
+ g_signal_handler_disconnect (self->priv->modem, self->priv->id_cdma1x_registration_change);
+ self->priv->id_cdma1x_registration_change = 0;
+ }
+ if (self->priv->id_evdo_registration_change) {
+ if (g_signal_handler_is_connected (self->priv->modem, self->priv->id_evdo_registration_change))
+ g_signal_handler_disconnect (self->priv->modem, self->priv->id_evdo_registration_change);
+ self->priv->id_evdo_registration_change = 0;
+ }
+}
+
+/*****************************************************************************/
+/* CONNECT */
+
+gboolean
+mm_bearer_connect_finish (MMBearer *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return !g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error);
+}
+
+static void
+disconnect_after_cancel_ready (MMBearer *self,
+ GAsyncResult *res)
+{
+ GError *error = NULL;
+
+ if (!MM_BEARER_GET_CLASS (self)->disconnect_finish (self, res, &error)) {
+ mm_warn ("Error disconnecting bearer '%s': '%s'. "
+ "Will assume disconnected anyway.",
+ self->priv->path,
+ error->message);
+ g_error_free (error);
+ }
+ else
+ mm_dbg ("Disconnected bearer '%s'", self->priv->path);
+
+ bearer_update_status (self, MM_BEARER_STATUS_DISCONNECTED);
+}
+
+static void
+connect_ready (MMBearer *self,
+ GAsyncResult *res,
+ GSimpleAsyncResult *simple)
+{
+ GError *error = NULL;
+ gboolean launch_disconnect = FALSE;
+ MMBearerConnectResult *result;
+
+ /* NOTE: connect() implementations *MUST* handle cancellations themselves */
+ result = MM_BEARER_GET_CLASS (self)->connect_finish (self, res, &error);
+ if (!result) {
+ mm_dbg ("Couldn't connect bearer '%s': '%s'",
+ self->priv->path,
+ error->message);
+ if (g_error_matches (error,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_CANCELLED)) {
+ /* Will launch disconnection */
+ launch_disconnect = TRUE;
+ } else
+ bearer_update_status (self, MM_BEARER_STATUS_DISCONNECTED);
+
+ g_simple_async_result_take_error (simple, error);
+ }
+ /* Handle cancellations detected after successful connection */
+ else if (g_cancellable_is_cancelled (self->priv->connect_cancellable)) {
+ mm_dbg ("Connected bearer '%s', but need to disconnect", self->priv->path);
+ mm_bearer_connect_result_unref (result);
+ g_simple_async_result_set_error (
+ simple,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_CANCELLED,
+ "Bearer got connected, but had to disconnect after cancellation request");
+ launch_disconnect = TRUE;
+ }
+ else {
+ mm_dbg ("Connected bearer '%s'", self->priv->path);
+
+ /* Update bearer and interface status */
+ bearer_update_status_connected (
+ self,
+ mm_port_get_device (mm_bearer_connect_result_peek_data (result)),
+ mm_bearer_connect_result_peek_ipv4_config (result),
+ mm_bearer_connect_result_peek_ipv6_config (result));
+ mm_bearer_connect_result_unref (result);
+ g_simple_async_result_set_op_res_gboolean (simple, TRUE);
+ }
+
+ if (launch_disconnect) {
+ bearer_update_status (self, MM_BEARER_STATUS_DISCONNECTING);
+ MM_BEARER_GET_CLASS (self)->disconnect (
+ self,
+ (GAsyncReadyCallback)disconnect_after_cancel_ready,
+ NULL);
+ }
+
+ g_clear_object (&self->priv->connect_cancellable);
+ g_simple_async_result_complete (simple);
+ g_object_unref (simple);
+}
+
+void
+mm_bearer_connect (MMBearer *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GSimpleAsyncResult *result;
+
+ g_assert (MM_BEARER_GET_CLASS (self)->connect != NULL);
+ g_assert (MM_BEARER_GET_CLASS (self)->connect_finish != NULL);
+
+ /* If already connecting, return error, don't allow a second request. */
+ if (self->priv->status == MM_BEARER_STATUS_CONNECTING) {
+ g_simple_async_report_error_in_idle (
+ G_OBJECT (self),
+ callback,
+ user_data,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_IN_PROGRESS,
+ "Bearer already being connected");
+ return;
+ }
+
+ /* If currently disconnecting, return error, previous operation should
+ * finish before allowing to connect again. */
+ if (self->priv->status == MM_BEARER_STATUS_DISCONNECTING) {
+ g_simple_async_report_error_in_idle (
+ G_OBJECT (self),
+ callback,
+ user_data,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Bearer currently being disconnected");
+ return;
+ }
+
+ /* Check 3GPP roaming allowance, *only* roaming related here */
+ if (mm_iface_modem_is_3gpp (MM_IFACE_MODEM (self->priv->modem)) &&
+ self->priv->reason_3gpp == CONNECTION_FORBIDDEN_REASON_ROAMING) {
+ g_simple_async_report_error_in_idle (
+ G_OBJECT (self),
+ callback,
+ user_data,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_UNAUTHORIZED,
+ "Not allowed to connect bearer in 3GPP network: '%s'",
+ connection_forbidden_reason_str[self->priv->reason_3gpp]);
+ return;
+ }
+
+ /* Check CDMA roaming allowance, *only* roaming related here */
+ if (mm_iface_modem_is_cdma (MM_IFACE_MODEM (self->priv->modem)) &&
+ self->priv->reason_cdma == CONNECTION_FORBIDDEN_REASON_ROAMING) {
+ g_simple_async_report_error_in_idle (
+ G_OBJECT (self),
+ callback,
+ user_data,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_UNAUTHORIZED,
+ "Not allowed to connect bearer in CDMA network: '%s'",
+ connection_forbidden_reason_str[self->priv->reason_cdma]);
+ return;
+ }
+
+ result = g_simple_async_result_new (G_OBJECT (self),
+ callback,
+ user_data,
+ mm_bearer_connect);
+
+ /* If already connected, done */
+ if (self->priv->status == MM_BEARER_STATUS_CONNECTED) {
+ g_simple_async_result_set_op_res_gboolean (result, TRUE);
+ g_simple_async_result_complete_in_idle (result);
+ g_object_unref (result);
+ return;
+ }
+
+ /* Connecting! */
+ mm_dbg ("Connecting bearer '%s'", self->priv->path);
+ self->priv->connect_cancellable = g_cancellable_new ();
+ bearer_update_status (self, MM_BEARER_STATUS_CONNECTING);
+ MM_BEARER_GET_CLASS (self)->connect (
+ self,
+ self->priv->connect_cancellable,
+ (GAsyncReadyCallback)connect_ready,
+ result);
+}
+
+typedef struct {
+ MMBearer *self;
+ MMBaseModem *modem;
+ GDBusMethodInvocation *invocation;
+} HandleConnectContext;
+
+static void
+handle_connect_context_free (HandleConnectContext *ctx)
+{
+ g_object_unref (ctx->invocation);
+ g_object_unref (ctx->modem);
+ g_object_unref (ctx->self);
+ g_free (ctx);
+}
+
+static void
+handle_connect_ready (MMBearer *self,
+ GAsyncResult *res,
+ HandleConnectContext *ctx)
+{
+ GError *error = NULL;
+
+ if (!mm_bearer_connect_finish (self, res, &error))
+ g_dbus_method_invocation_take_error (ctx->invocation, error);
+ else
+ mm_gdbus_bearer_complete_connect (MM_GDBUS_BEARER (self), ctx->invocation);
+
+ handle_connect_context_free (ctx);
+}
+
+static void
+handle_connect_auth_ready (MMBaseModem *modem,
+ GAsyncResult *res,
+ HandleConnectContext *ctx)
+{
+ GError *error = NULL;
+
+ if (!mm_base_modem_authorize_finish (modem, res, &error)) {
+ g_dbus_method_invocation_take_error (ctx->invocation, error);
+ handle_connect_context_free (ctx);
+ return;
+ }
+
+ mm_bearer_connect (ctx->self,
+ (GAsyncReadyCallback)handle_connect_ready,
+ ctx);
+}
+
+static gboolean
+handle_connect (MMBearer *self,
+ GDBusMethodInvocation *invocation)
+{
+ HandleConnectContext *ctx;
+
+ ctx = g_new0 (HandleConnectContext, 1);
+ ctx->self = g_object_ref (self);
+ ctx->invocation = g_object_ref (invocation);
+ g_object_get (self,
+ MM_BEARER_MODEM, &ctx->modem,
+ NULL);
+
+ mm_base_modem_authorize (ctx->modem,
+ invocation,
+ MM_AUTHORIZATION_DEVICE_CONTROL,
+ (GAsyncReadyCallback)handle_connect_auth_ready,
+ ctx);
+ return TRUE;
+}
+
+/*****************************************************************************/
+/* DISCONNECT */
+
+gboolean
+mm_bearer_disconnect_finish (MMBearer *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return !g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error);
+}
+
+static void
+disconnect_ready (MMBearer *self,
+ GAsyncResult *res,
+ GSimpleAsyncResult *simple)
+{
+ GError *error = NULL;
+
+ if (!MM_BEARER_GET_CLASS (self)->disconnect_finish (self, res, &error)) {
+ mm_dbg ("Couldn't disconnect bearer '%s'", self->priv->path);
+ bearer_update_status (self, MM_BEARER_STATUS_CONNECTED);
+ g_simple_async_result_take_error (simple, error);
+ }
+ else {
+ mm_dbg ("Disconnected bearer '%s'", self->priv->path);
+ bearer_update_status (self, MM_BEARER_STATUS_DISCONNECTED);
+ g_simple_async_result_set_op_res_gboolean (simple, TRUE);
+ }
+
+ g_simple_async_result_complete (simple);
+ g_object_unref (simple);
+}
+
+static void
+status_changed_complete_disconnect (MMBearer *self,
+ GParamSpec *pspec,
+ GSimpleAsyncResult *simple)
+{
+ /* We may get other states here before DISCONNECTED, like DISCONNECTING or
+ * even CONNECTED. */
+ if (self->priv->status != MM_BEARER_STATUS_DISCONNECTED)
+ return;
+
+ mm_dbg ("Disconnected bearer '%s' after cancelling previous connect request",
+ self->priv->path);
+ g_signal_handler_disconnect (self,
+ self->priv->disconnect_signal_handler);
+ self->priv->disconnect_signal_handler = 0;
+
+ /* Note: interface state is updated when the DISCONNECTED state is set */
+
+ g_simple_async_result_set_op_res_gboolean (simple, TRUE);
+ g_simple_async_result_complete (simple);
+ g_object_unref (simple);
+}
+
+void
+mm_bearer_disconnect (MMBearer *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GSimpleAsyncResult *simple;
+
+ g_assert (MM_BEARER_GET_CLASS (self)->disconnect != NULL);
+ g_assert (MM_BEARER_GET_CLASS (self)->disconnect_finish != NULL);
+
+ simple = g_simple_async_result_new (G_OBJECT (self),
+ callback,
+ user_data,
+ mm_bearer_disconnect);
+
+ /* If already disconnected, done */
+ if (self->priv->status == MM_BEARER_STATUS_DISCONNECTED) {
+ g_simple_async_result_set_op_res_gboolean (simple, TRUE);
+ g_simple_async_result_complete_in_idle (simple);
+ g_object_unref (simple);
+ return;
+ }
+
+ /* If already disconnecting, return error, don't allow a second request. */
+ if (self->priv->status == MM_BEARER_STATUS_DISCONNECTING) {
+ g_simple_async_result_set_error (
+ simple,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_IN_PROGRESS,
+ "Bearer already being disconnected");
+ g_simple_async_result_complete_in_idle (simple);
+ g_object_unref (simple);
+ return;
+ }
+
+ mm_dbg ("Disconnecting bearer '%s'", self->priv->path);
+
+ /* If currently connecting, try to cancel that operation, and wait to get
+ * disconnected. */
+ if (self->priv->status == MM_BEARER_STATUS_CONNECTING) {
+ /* Set ourselves as disconnecting */
+ bearer_update_status (self, MM_BEARER_STATUS_DISCONNECTING);
+
+ /* We MUST ensure that we get to DISCONNECTED */
+ g_cancellable_cancel (self->priv->connect_cancellable);
+ /* Note that we only allow to remove disconnected bearers, so should
+ * be safe to assume that we'll get the signal handler called properly
+ */
+ self->priv->disconnect_signal_handler =
+ g_signal_connect (self,
+ "notify::" MM_BEARER_STATUS,
+ (GCallback)status_changed_complete_disconnect,
+ simple); /* takes ownership */
+
+ return;
+ }
+
+ /* Disconnecting! */
+ bearer_update_status (self, MM_BEARER_STATUS_DISCONNECTING);
+ MM_BEARER_GET_CLASS (self)->disconnect (
+ self,
+ (GAsyncReadyCallback)disconnect_ready,
+ simple); /* takes ownership */
+}
+
+typedef struct {
+ MMBearer *self;
+ MMBaseModem *modem;
+ GDBusMethodInvocation *invocation;
+} HandleDisconnectContext;
+
+static void
+handle_disconnect_context_free (HandleDisconnectContext *ctx)
+{
+ g_object_unref (ctx->invocation);
+ g_object_unref (ctx->modem);
+ g_object_unref (ctx->self);
+ g_free (ctx);
+}
+
+static void
+handle_disconnect_ready (MMBearer *self,
+ GAsyncResult *res,
+ HandleDisconnectContext *ctx)
+{
+ GError *error = NULL;
+
+ if (!mm_bearer_disconnect_finish (self, res, &error))
+ g_dbus_method_invocation_take_error (ctx->invocation, error);
+ else
+ mm_gdbus_bearer_complete_disconnect (MM_GDBUS_BEARER (self), ctx->invocation);
+
+ handle_disconnect_context_free (ctx);
+}
+
+static void
+handle_disconnect_auth_ready (MMBaseModem *modem,
+ GAsyncResult *res,
+ HandleDisconnectContext *ctx)
+{
+ GError *error = NULL;
+
+ if (!mm_base_modem_authorize_finish (modem, res, &error)) {
+ g_dbus_method_invocation_take_error (ctx->invocation, error);
+ handle_disconnect_context_free (ctx);
+ return;
+ }
+
+ mm_bearer_disconnect (ctx->self,
+ (GAsyncReadyCallback)handle_disconnect_ready,
+ ctx);
+}
+
+static gboolean
+handle_disconnect (MMBearer *self,
+ GDBusMethodInvocation *invocation)
+{
+ HandleDisconnectContext *ctx;
+
+ ctx = g_new0 (HandleDisconnectContext, 1);
+ ctx->self = g_object_ref (self);
+ ctx->invocation = g_object_ref (invocation);
+ g_object_get (self,
+ MM_BEARER_MODEM, &ctx->modem,
+ NULL);
+
+ mm_base_modem_authorize (ctx->modem,
+ invocation,
+ MM_AUTHORIZATION_DEVICE_CONTROL,
+ (GAsyncReadyCallback)handle_disconnect_auth_ready,
+ ctx);
+ return TRUE;
+}
+
+/*****************************************************************************/
+
+static void
+mm_bearer_dbus_export (MMBearer *self)
+{
+ GError *error = NULL;
+
+ /* Handle method invocations */
+ g_signal_connect (self,
+ "handle-connect",
+ G_CALLBACK (handle_connect),
+ NULL);
+ g_signal_connect (self,
+ "handle-disconnect",
+ G_CALLBACK (handle_disconnect),
+ NULL);
+
+ if (!g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (self),
+ self->priv->connection,
+ self->priv->path,
+ &error)) {
+ mm_warn ("couldn't export BEARER at '%s': '%s'",
+ self->priv->path,
+ error->message);
+ g_error_free (error);
+ }
+}
+
+static void
+mm_bearer_dbus_unexport (MMBearer *self)
+{
+ const gchar *path;
+
+ path = g_dbus_interface_skeleton_get_object_path (G_DBUS_INTERFACE_SKELETON (self));
+ /* Only unexport if currently exported */
+ if (path) {
+ mm_dbg ("Removing from DBus bearer at '%s'", path);
+ g_dbus_interface_skeleton_unexport (G_DBUS_INTERFACE_SKELETON (self));
+ }
+}
+
+/*****************************************************************************/
+
+MMBearerStatus
+mm_bearer_get_status (MMBearer *self)
+{
+ return self->priv->status;
+}
+
+const gchar *
+mm_bearer_get_path (MMBearer *self)
+{
+ return self->priv->path;
+}
+
+MMBearerProperties *
+mm_bearer_peek_config (MMBearer *self)
+{
+ return self->priv->config;
+}
+
+MMBearerProperties *
+mm_bearer_get_config (MMBearer *self)
+{
+ return (self->priv->config ?
+ g_object_ref (self->priv->config) :
+ NULL);
+}
+
+MMBearerIpFamily
+mm_bearer_get_default_ip_family (MMBearer *self)
+{
+ return self->priv->default_ip_family;
+}
+
+/*****************************************************************************/
+
+static void
+disconnect_force_ready (MMBearer *self,
+ GAsyncResult *res)
+{
+ GError *error = NULL;
+
+ if (!MM_BEARER_GET_CLASS (self)->disconnect_finish (self, res, &error)) {
+ mm_warn ("Error disconnecting bearer '%s': '%s'. "
+ "Will assume disconnected anyway.",
+ self->priv->path,
+ error->message);
+ g_error_free (error);
+ }
+ else
+ mm_dbg ("Disconnected bearer '%s'", self->priv->path);
+
+ bearer_update_status (self, MM_BEARER_STATUS_DISCONNECTED);
+}
+
+void
+mm_bearer_disconnect_force (MMBearer *self)
+{
+ if (self->priv->status == MM_BEARER_STATUS_DISCONNECTING ||
+ self->priv->status == MM_BEARER_STATUS_DISCONNECTED)
+ return;
+
+ mm_dbg ("Forcing disconnection of bearer '%s'", self->priv->path);
+
+ /* If currently connecting, try to cancel that operation. */
+ if (self->priv->status == MM_BEARER_STATUS_CONNECTING) {
+ g_cancellable_cancel (self->priv->connect_cancellable);
+ return;
+ }
+
+ /* Disconnecting! */
+ bearer_update_status (self, MM_BEARER_STATUS_DISCONNECTING);
+ MM_BEARER_GET_CLASS (self)->disconnect (
+ self,
+ (GAsyncReadyCallback)disconnect_force_ready,
+ NULL);
+}
+
+/*****************************************************************************/
+
+static void
+report_disconnection (MMBearer *self)
+{
+ /* In the generic bearer implementation we just need to reset the
+ * interface status */
+ bearer_update_status (self, MM_BEARER_STATUS_DISCONNECTED);
+}
+
+void
+mm_bearer_report_disconnection (MMBearer *self)
+{
+ return MM_BEARER_GET_CLASS (self)->report_disconnection (self);
+}
+
+static void
+set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ MMBearer *self = MM_BEARER (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)
+ mm_bearer_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)
+ mm_bearer_dbus_unexport (self);
+ else if (self->priv->path)
+ mm_bearer_dbus_export (self);
+ break;
+ case PROP_MODEM:
+ g_clear_object (&self->priv->modem);
+ self->priv->modem = g_value_dup_object (value);
+ if (self->priv->modem) {
+ /* Bind the modem's connection (which is set when it is exported,
+ * and unset when unexported) to the BEARER's connection */
+ g_object_bind_property (self->priv->modem, MM_BASE_MODEM_CONNECTION,
+ self, MM_BEARER_CONNECTION,
+ G_BINDING_DEFAULT | G_BINDING_SYNC_CREATE);
+ if (self->priv->config) {
+ /* Listen to 3GPP/CDMA registration state changes. We need both
+ * 'config' and 'modem' set. */
+ set_signal_handlers (self);
+ }
+ }
+ break;
+ case PROP_STATUS:
+ /* We don't allow g_object_set()-ing the status property */
+ g_assert_not_reached ();
+ break;
+ case PROP_CONFIG: {
+ GVariant *dictionary;
+
+ g_clear_object (&self->priv->config);
+ self->priv->config = g_value_dup_object (value);
+ if (self->priv->modem) {
+ /* Listen to 3GPP/CDMA registration state changes. We need both
+ * 'config' and 'modem' set. */
+ set_signal_handlers (self);
+ }
+ /* Also expose the properties */
+ dictionary = mm_bearer_properties_get_dictionary (self->priv->config);
+ mm_gdbus_bearer_set_properties (MM_GDBUS_BEARER (self), dictionary);
+ if (dictionary)
+ g_variant_unref (dictionary);
+ break;
+ }
+ case PROP_DEFAULT_IP_FAMILY:
+ self->priv->default_ip_family = g_value_get_enum (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)
+{
+ MMBearer *self = MM_BEARER (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_MODEM:
+ g_value_set_object (value, self->priv->modem);
+ break;
+ case PROP_STATUS:
+ g_value_set_enum (value, self->priv->status);
+ break;
+ case PROP_CONFIG:
+ g_value_set_object (value, self->priv->config);
+ break;
+ case PROP_DEFAULT_IP_FAMILY:
+ g_value_set_enum (value, self->priv->default_ip_family);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+mm_bearer_init (MMBearer *self)
+{
+ /* Initialize private data */
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE ((self),
+ MM_TYPE_BEARER,
+ MMBearerPrivate);
+ self->priv->status = MM_BEARER_STATUS_DISCONNECTED;
+ self->priv->reason_3gpp = CONNECTION_FORBIDDEN_REASON_NONE;
+ self->priv->reason_cdma = CONNECTION_FORBIDDEN_REASON_NONE;
+ self->priv->default_ip_family = MM_BEARER_IP_FAMILY_IPV4;
+
+ /* Set defaults */
+ mm_gdbus_bearer_set_interface (MM_GDBUS_BEARER (self), NULL);
+ mm_gdbus_bearer_set_connected (MM_GDBUS_BEARER (self), FALSE);
+ mm_gdbus_bearer_set_suspended (MM_GDBUS_BEARER (self), FALSE);
+ mm_gdbus_bearer_set_properties (MM_GDBUS_BEARER (self), NULL);
+ mm_gdbus_bearer_set_ip_timeout (MM_GDBUS_BEARER (self), MM_BEARER_IP_TIMEOUT_DEFAULT);
+ mm_gdbus_bearer_set_ip4_config (MM_GDBUS_BEARER (self),
+ mm_bearer_ip_config_get_dictionary (NULL));
+ mm_gdbus_bearer_set_ip6_config (MM_GDBUS_BEARER (self),
+ mm_bearer_ip_config_get_dictionary (NULL));
+}
+
+static void
+finalize (GObject *object)
+{
+ MMBearer *self = MM_BEARER (object);
+
+ g_free (self->priv->path);
+
+ G_OBJECT_CLASS (mm_bearer_parent_class)->finalize (object);
+}
+
+static void
+dispose (GObject *object)
+{
+ MMBearer *self = MM_BEARER (object);
+
+ if (self->priv->connection) {
+ mm_bearer_dbus_unexport (self);
+ g_clear_object (&self->priv->connection);
+ }
+
+ reset_signal_handlers (self);
+ reset_deferred_unregistration (self);
+
+ g_clear_object (&self->priv->modem);
+ g_clear_object (&self->priv->config);
+
+ G_OBJECT_CLASS (mm_bearer_parent_class)->dispose (object);
+}
+
+static void
+mm_bearer_class_init (MMBearerClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ g_type_class_add_private (object_class, sizeof (MMBearerPrivate));
+
+ /* Virtual methods */
+ object_class->get_property = get_property;
+ object_class->set_property = set_property;
+ object_class->finalize = finalize;
+ object_class->dispose = dispose;
+
+ klass->report_disconnection = report_disconnection;
+
+ properties[PROP_CONNECTION] =
+ g_param_spec_object (MM_BEARER_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_BEARER_PATH,
+ "Path",
+ "DBus path of the Bearer",
+ NULL,
+ G_PARAM_READWRITE);
+ g_object_class_install_property (object_class, PROP_PATH, properties[PROP_PATH]);
+
+ properties[PROP_MODEM] =
+ g_param_spec_object (MM_BEARER_MODEM,
+ "Modem",
+ "The Modem which owns this Bearer",
+ MM_TYPE_BASE_MODEM,
+ G_PARAM_READWRITE);
+ g_object_class_install_property (object_class, PROP_MODEM, properties[PROP_MODEM]);
+
+ properties[PROP_STATUS] =
+ g_param_spec_enum (MM_BEARER_STATUS,
+ "Bearer status",
+ "Status of the bearer",
+ MM_TYPE_BEARER_STATUS,
+ MM_BEARER_STATUS_DISCONNECTED,
+ G_PARAM_READABLE);
+ g_object_class_install_property (object_class, PROP_STATUS, properties[PROP_STATUS]);
+
+ properties[PROP_CONFIG] =
+ g_param_spec_object (MM_BEARER_CONFIG,
+ "Bearer configuration",
+ "List of user provided properties",
+ MM_TYPE_BEARER_PROPERTIES,
+ G_PARAM_READWRITE);
+ g_object_class_install_property (object_class, PROP_CONFIG, properties[PROP_CONFIG]);
+
+ properties[PROP_DEFAULT_IP_FAMILY] =
+ g_param_spec_enum (MM_BEARER_DEFAULT_IP_FAMILY,
+ "Bearer default IP family",
+ "IP family to use for this bearer when no IP family is specified",
+ MM_TYPE_BEARER_IP_FAMILY,
+ MM_BEARER_IP_FAMILY_IPV4,
+ G_PARAM_READWRITE);
+ g_object_class_install_property (object_class, PROP_DEFAULT_IP_FAMILY, properties[PROP_DEFAULT_IP_FAMILY]);
+}
+
+/*****************************************************************************/
+/* Helpers to implement connect() */
+
+struct _MMBearerConnectResult {
+ volatile gint ref_count;
+ MMPort *data;
+ MMBearerIpConfig *ipv4_config;
+ MMBearerIpConfig *ipv6_config;
+};
+
+MMBearerConnectResult *
+mm_bearer_connect_result_ref (MMBearerConnectResult *result)
+{
+ g_atomic_int_inc (&result->ref_count);
+ return result;
+}
+
+void
+mm_bearer_connect_result_unref (MMBearerConnectResult *result)
+{
+ if (g_atomic_int_dec_and_test (&result->ref_count)) {
+ if (result->ipv4_config)
+ g_object_unref (result->ipv4_config);
+ if (result->ipv6_config)
+ g_object_unref (result->ipv6_config);
+ if (result->data)
+ g_object_unref (result->data);
+ g_slice_free (MMBearerConnectResult, result);
+ }
+}
+
+MMPort *
+mm_bearer_connect_result_peek_data (MMBearerConnectResult *result)
+{
+ return result->data;
+}
+
+MMBearerIpConfig *
+mm_bearer_connect_result_peek_ipv4_config (MMBearerConnectResult *result)
+{
+ return result->ipv4_config;
+}
+
+MMBearerIpConfig *
+mm_bearer_connect_result_peek_ipv6_config (MMBearerConnectResult *result)
+{
+ return result->ipv6_config;
+}
+
+MMBearerConnectResult *
+mm_bearer_connect_result_new (MMPort *data,
+ MMBearerIpConfig *ipv4_config,
+ MMBearerIpConfig *ipv6_config)
+{
+ MMBearerConnectResult *result;
+
+ /* 'data' must always be given */
+ g_assert (MM_IS_PORT (data));
+
+ result = g_slice_new0 (MMBearerConnectResult);
+ result->ref_count = 1;
+ result->data = g_object_ref (data);
+ if (ipv4_config)
+ result->ipv4_config = g_object_ref (ipv4_config);
+ if (ipv6_config)
+ result->ipv6_config = g_object_ref (ipv6_config);
+ return result;
+}