aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorAleksander Morgado <aleksander@aleksander.es>2021-04-04 23:07:02 +0200
committerAleksander Morgado <aleksander@aleksander.es>2021-04-29 10:13:22 +0000
commit4b2e63aa86fc463fb1622bbc32dcfa90bee060e8 (patch)
tree54dd468fa7bda0634f811a600982f2fd4a46ecb4 /src
parent10aa516f075ef8ea08cd045cc397379b50c8782a (diff)
broadband-modem: implement profile management support
Using AT+CGDCONT for profile settings management (querying, updating, reseting), AT+CGDEL for profile deletion, and AT+CGACT for activation status management (check, deactivate).
Diffstat (limited to 'src')
-rw-r--r--src/mm-broadband-modem.c568
1 files changed, 565 insertions, 3 deletions
diff --git a/src/mm-broadband-modem.c b/src/mm-broadband-modem.c
index 6f5be885..71963b3f 100644
--- a/src/mm-broadband-modem.c
+++ b/src/mm-broadband-modem.c
@@ -12,9 +12,9 @@
*
* Copyright (C) 2008 - 2009 Novell, Inc.
* Copyright (C) 2009 - 2012 Red Hat, Inc.
- * Copyright (C) 2011 - 2012 Google, Inc.
* Copyright (C) 2015 Marco Bascetta <marco.bascetta@sadel.it>
* Copyright (C) 2019 Purism SPC
+ * Copyright (C) 2011 - 2021 Google, Inc.
*/
#include <config.h>
@@ -10098,6 +10098,554 @@ modem_signal_load_values (MMIfaceModemSignal *self,
}
/*****************************************************************************/
+/* Check support (3GPP profile management interface) */
+
+static gboolean
+modem_3gpp_profile_manager_check_support_finish (MMIfaceModem3gppProfileManager *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+profile_manager_cgdcont_test_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+
+ mm_base_modem_at_command_finish (self, res, &error);
+ if (error)
+ g_task_return_error (task, error);
+ else
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+static void
+modem_3gpp_profile_manager_check_support (MMIfaceModem3gppProfileManager *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+
+ task = g_task_new (self, NULL, callback, user_data);
+ if (!mm_iface_modem_is_3gpp (MM_IFACE_MODEM (self))) {
+ g_task_return_boolean (task, FALSE);
+ g_object_unref (task);
+ return;
+ }
+
+ /* Query with CGDCONT=? */
+ mm_base_modem_at_command (
+ MM_BASE_MODEM (self),
+ "+CGDCONT=?",
+ 3,
+ TRUE, /* allow caching, it's a test command */
+ (GAsyncReadyCallback)profile_manager_cgdcont_test_ready,
+ task);
+}
+
+/*****************************************************************************/
+/* List profiles (3GPP profile management interface) */
+
+typedef struct {
+ GList *profiles;
+} ListProfilesContext;
+
+static void
+list_profiles_context_free (ListProfilesContext *ctx)
+{
+ mm_3gpp_profile_list_free (ctx->profiles);
+ g_slice_free (ListProfilesContext, ctx);
+}
+
+static gboolean
+modem_3gpp_profile_manager_list_profiles_finish (MMIfaceModem3gppProfileManager *self,
+ GAsyncResult *res,
+ GList **out_profiles,
+ GError **error)
+{
+ ListProfilesContext *ctx;
+
+ if (!g_task_propagate_boolean (G_TASK (res), error))
+ return FALSE;
+
+ ctx = g_task_get_task_data (G_TASK (res));
+ if (out_profiles)
+ *out_profiles = g_steal_pointer (&ctx->profiles);
+ return TRUE;
+}
+
+static void
+profile_manager_cgdcont_query_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ ListProfilesContext *ctx;
+ const gchar *response;
+ GError *error = NULL;
+ GList *pdp_context_list;
+
+ response = mm_base_modem_at_command_finish (self, res, &error);
+ if (!response) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ /* may return NULL without error if response is empty */
+ pdp_context_list = mm_3gpp_parse_cgdcont_read_response (response, &error);
+ if (error) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ ctx = g_slice_new0 (ListProfilesContext);
+ g_task_set_task_data (task, ctx, (GDestroyNotify) list_profiles_context_free);
+ ctx->profiles = mm_3gpp_profile_list_new_from_pdp_context_list (pdp_context_list);
+
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+static void
+modem_3gpp_profile_manager_list_profiles (MMIfaceModem3gppProfileManager *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ /* Query with CGDCONT? */
+ mm_base_modem_at_command (
+ MM_BASE_MODEM (self),
+ "+CGDCONT?",
+ 3,
+ FALSE,
+ (GAsyncReadyCallback)profile_manager_cgdcont_query_ready,
+ task);
+}
+
+/*****************************************************************************/
+/* Check format (3GPP profile management interface) */
+
+typedef struct {
+ MMBearerIpFamily ip_type;
+ guint min_profile_id;
+ guint max_profile_id;
+} CheckFormatContext;
+
+static void
+check_format_context_free (CheckFormatContext *ctx)
+{
+ g_slice_free (CheckFormatContext, ctx);
+}
+
+static gboolean
+modem_3gpp_profile_manager_check_format_finish (MMIfaceModem3gppProfileManager *self,
+ GAsyncResult *res,
+ gboolean *new_id,
+ gint *min_profile_id,
+ gint *max_profile_id,
+ GEqualFunc *apn_cmp,
+ MM3gppProfileCmpFlags *profile_cmp_flags,
+ GError **error)
+{
+ CheckFormatContext *ctx;
+
+ if (!g_task_propagate_boolean (G_TASK (res), error))
+ return FALSE;
+
+ ctx = g_task_get_task_data (G_TASK (res));
+ if (new_id)
+ *new_id = TRUE;
+ if (min_profile_id)
+ *min_profile_id = (gint) ctx->min_profile_id;
+ if (max_profile_id)
+ *max_profile_id = (gint) ctx->max_profile_id;
+ if (apn_cmp)
+ *apn_cmp = (GEqualFunc) mm_3gpp_cmp_apn_name;
+ if (profile_cmp_flags)
+ *profile_cmp_flags = (MM_3GPP_PROFILE_CMP_FLAGS_NO_AUTH | MM_3GPP_PROFILE_CMP_FLAGS_NO_APN_TYPE);
+ return TRUE;
+}
+
+static void
+check_format_cgdcont_test_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ CheckFormatContext *ctx;
+ const gchar *response;
+ GList *format_list = NULL;
+ g_autofree gchar *ip_family_str = NULL;
+ g_autoptr(GError) error = NULL;
+ gboolean checked = FALSE;
+
+ ctx = g_task_get_task_data (task);
+
+ ip_family_str = mm_bearer_ip_family_build_string_from_mask (ctx->ip_type);
+
+ response = mm_base_modem_at_command_full_finish (self, res, &error);
+ if (!response)
+ mm_obj_dbg (self, "failed checking context definition format: %s", error->message);
+ else {
+ format_list = mm_3gpp_parse_cgdcont_test_response (response, self, &error);
+ if (error)
+ mm_obj_dbg (self, "error parsing +CGDCONT test response: %s", error->message);
+ else if (mm_3gpp_pdp_context_format_list_find_range (format_list, ctx->ip_type,
+ &ctx->min_profile_id, &ctx->max_profile_id))
+ checked = TRUE;
+ }
+
+ if (!checked) {
+ ctx->min_profile_id = 1;
+ ctx->max_profile_id = G_MAXINT-1;
+ mm_obj_dbg (self, "unknown +CGDCONT format details for PDP type '%s', using defaults: minimum %d, maximum %d",
+ ip_family_str, ctx->min_profile_id, ctx->max_profile_id);
+ } else
+ mm_obj_dbg (self, "+CGDCONT format details for PDP type '%s': minimum %d, maximum %d",
+ ip_family_str, ctx->min_profile_id, ctx->max_profile_id);
+
+ mm_3gpp_pdp_context_format_list_free (format_list);
+
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+static void
+modem_3gpp_profile_manager_check_format (MMIfaceModem3gppProfileManager *self,
+ MMBearerIpFamily ip_type,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+ CheckFormatContext *ctx;
+
+ task = g_task_new (self, NULL, callback, user_data);
+ ctx = g_slice_new0 (CheckFormatContext);
+ ctx->ip_type = ip_type;
+ g_task_set_task_data (task, ctx, (GDestroyNotify)check_format_context_free);
+
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "+CGDCONT=?",
+ 3,
+ TRUE, /* cached */
+ (GAsyncReadyCallback)check_format_cgdcont_test_ready,
+ task);
+}
+
+/*****************************************************************************/
+/* Delete profile (3GPP profile management interface) */
+
+static gboolean
+modem_3gpp_profile_manager_delete_profile_finish (MMIfaceModem3gppProfileManager *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+profile_manager_cgdcont_reset_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+ gint profile_id;
+
+ profile_id = GPOINTER_TO_INT (g_task_get_task_data (task));
+
+ if (!mm_base_modem_at_command_finish (self, res, &error)) {
+ mm_obj_dbg (self, "attempting to reset context with id '%d' failed: %s", profile_id, error->message);
+ g_task_return_error (task, error);
+ } else {
+ mm_obj_dbg (self, "reseted context with profile id '%d'", profile_id);
+ g_task_return_boolean (task, TRUE);
+ }
+ g_object_unref (task);
+}
+
+static void
+profile_manager_cgdel_set_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ g_autoptr(GError) error = NULL;
+ g_autofree gchar *cmd = NULL;
+ gint profile_id;
+
+ profile_id = GPOINTER_TO_INT (g_task_get_task_data (task));
+
+ if (mm_base_modem_at_command_finish (self, res, &error)) {
+ mm_obj_dbg (self, "deleted context with profile id '%d'", profile_id);
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+ return;
+ }
+
+ mm_obj_dbg (self, "attempting to delete context with id '%d' failed: %s", profile_id, error->message);
+
+ /* From 3GPP TS 27.007 (v16.3.0):
+ * A special form of the set command, +CGDCONT=<cid> causes the values for
+ * context number <cid> to become undefined.
+ */
+ cmd = g_strdup_printf ("+CGDCONT=%d", profile_id);
+ mm_base_modem_at_command (
+ MM_BASE_MODEM (self),
+ cmd,
+ 3,
+ FALSE,
+ (GAsyncReadyCallback)profile_manager_cgdcont_reset_ready,
+ task);
+}
+
+static void
+modem_3gpp_profile_manager_delete_profile (MMIfaceModem3gppProfileManager *self,
+ MM3gppProfile *profile,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ g_autofree gchar *cmd = NULL;
+ GTask *task;
+ gint profile_id;
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ profile_id = mm_3gpp_profile_get_profile_id (profile);
+ g_assert (profile_id != MM_3GPP_PROFILE_ID_UNKNOWN);
+ g_task_set_task_data (task, GINT_TO_POINTER (profile_id), NULL);
+
+ cmd = g_strdup_printf ("+CGDEL=%d", profile_id);
+
+ mm_base_modem_at_command (
+ MM_BASE_MODEM (self),
+ cmd,
+ 3,
+ FALSE,
+ (GAsyncReadyCallback)profile_manager_cgdel_set_ready,
+ task);
+}
+
+/*****************************************************************************/
+/* Deactivate profile (3GPP profile management interface) */
+
+static gboolean
+modem_3gpp_profile_manager_check_activated_profile_finish (MMIfaceModem3gppProfileManager *self,
+ GAsyncResult *res,
+ gboolean *out_activated,
+ GError **error)
+{
+ GError *inner_error = NULL;
+ gboolean result;
+
+ result = g_task_propagate_boolean (G_TASK (res), &inner_error);
+ if (inner_error) {
+ g_propagate_error (error, inner_error);
+ return FALSE;
+ }
+
+ if (out_activated)
+ *out_activated = result;
+ return TRUE;
+}
+
+static void
+check_activated_profile_cgact_query_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ MM3gppProfile *profile;
+ const gchar *response;
+ GError *error = NULL;
+ GList *pdp_context_active_list = NULL;
+ GList *l;
+ gint profile_id;
+ gboolean activated = FALSE;
+ g_autofree gchar *cmd = NULL;
+
+ response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error);
+ if (response)
+ pdp_context_active_list = mm_3gpp_parse_cgact_read_response (response, &error);
+
+ if (error) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ profile = g_task_get_task_data (task);
+ profile_id = mm_3gpp_profile_get_profile_id (profile);
+
+ for (l = pdp_context_active_list; l; l = g_list_next (l)) {
+ MM3gppPdpContextActive *iter = l->data;
+
+ if ((gint)iter->cid == profile_id) {
+ activated = iter->active;
+ break;
+ }
+ }
+ mm_3gpp_pdp_context_active_list_free (pdp_context_active_list);
+
+ if (!l)
+ g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_NOT_FOUND,
+ "Profile '%d' not found in CGACT? response",
+ profile_id);
+ else
+ g_task_return_boolean (task, activated);
+ g_object_unref (task);
+}
+
+static void
+modem_3gpp_profile_manager_check_activated_profile (MMIfaceModem3gppProfileManager *self,
+ MM3gppProfile *profile,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+ gint profile_id;
+
+ task = g_task_new (self, NULL, callback, user_data);
+ g_task_set_task_data (task, g_object_ref (profile), g_object_unref);
+
+ profile_id = mm_3gpp_profile_get_profile_id (profile);
+
+ mm_obj_dbg (self, "checking if profile with id '%d' is already activated...", profile_id);
+ mm_base_modem_at_command (
+ MM_BASE_MODEM (self),
+ "+CGACT?",
+ 3,
+ FALSE,
+ (GAsyncReadyCallback)check_activated_profile_cgact_query_ready,
+ task);
+}
+
+/*****************************************************************************/
+/* Deactivate profile (3GPP profile management interface) */
+
+static gboolean
+modem_3gpp_profile_manager_deactivate_profile_finish (MMIfaceModem3gppProfileManager *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+deactivate_profile_cgact_set_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+
+ if (!mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error))
+ g_task_return_error (task, error);
+ else
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+static void
+modem_3gpp_profile_manager_deactivate_profile (MMIfaceModem3gppProfileManager *self,
+ MM3gppProfile *profile,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+ gint profile_id;
+ g_autofree gchar *cmd = NULL;
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ profile_id = mm_3gpp_profile_get_profile_id (profile);
+ mm_obj_dbg (self, "deactivating profile with id '%d'...", profile_id);
+
+ cmd = g_strdup_printf ("+CGACT=0,%d", profile_id);
+ mm_base_modem_at_command (
+ MM_BASE_MODEM (self),
+ cmd,
+ MM_BASE_BEARER_DEFAULT_DISCONNECTION_TIMEOUT,
+ FALSE,
+ (GAsyncReadyCallback)deactivate_profile_cgact_set_ready,
+ task);
+}
+
+/*****************************************************************************/
+/* Store profile (3GPP profile management interface) */
+
+static gint
+modem_3gpp_profile_manager_store_profile_finish (MMIfaceModem3gppProfileManager *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ if (!g_task_propagate_boolean (G_TASK (res), error))
+ return MM_3GPP_PROFILE_ID_UNKNOWN;
+
+ return GPOINTER_TO_INT (g_task_get_task_data (G_TASK (res)));
+}
+
+static void
+store_profile_cgdcont_set_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+
+ if (!mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error))
+ g_task_return_error (task, error);
+ else
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+static void
+modem_3gpp_profile_manager_store_profile (MMIfaceModem3gppProfileManager *self,
+ MM3gppProfile *profile,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+ gint profile_id;
+ MMBearerIpFamily ip_type;
+ const gchar *apn;
+ const gchar *pdp_type;
+ g_autofree gchar *ip_type_str = NULL;
+ g_autofree gchar *quoted_apn = NULL;
+ g_autofree gchar *cmd = NULL;
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ profile_id = mm_3gpp_profile_get_profile_id (profile);
+ g_assert (profile_id != MM_3GPP_PROFILE_ID_UNKNOWN);
+ g_task_set_task_data (task, GINT_TO_POINTER (profile_id), NULL);
+
+ ip_type = mm_3gpp_profile_get_ip_type (profile);
+ g_assert (ip_type != MM_BEARER_IP_FAMILY_NONE);
+ g_assert (ip_type != MM_BEARER_IP_FAMILY_ANY);
+ ip_type_str = mm_bearer_ip_family_build_string_from_mask (ip_type);
+ pdp_type = mm_3gpp_get_pdp_type_from_ip_family (ip_type);
+ g_assert (pdp_type);
+
+ apn = mm_3gpp_profile_get_apn (profile);
+ quoted_apn = mm_port_serial_at_quote_string (apn);
+
+ mm_obj_dbg (self, "storing profile '%d': apn '%s', ip type '%s'",
+ profile_id, apn, ip_type_str);
+
+ cmd = g_strdup_printf ("+CGDCONT=%d,\"%s\",%s", profile_id, pdp_type, quoted_apn);
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ cmd,
+ 3,
+ FALSE,
+ (GAsyncReadyCallback) store_profile_cgdcont_set_ready,
+ task);
+}
+
+/*****************************************************************************/
static const gchar *primary_init_sequence[] = {
/* Ensure echo is off */
@@ -12579,8 +13127,22 @@ static void
iface_modem_3gpp_profile_manager_init (MMIfaceModem3gppProfileManager *iface)
{
/* Initialization steps */
- iface->check_support = NULL;
- iface->check_support_finish = NULL;
+ iface->check_support = modem_3gpp_profile_manager_check_support;
+ iface->check_support_finish = modem_3gpp_profile_manager_check_support_finish;
+
+ /* User actions */
+ iface->list_profiles = modem_3gpp_profile_manager_list_profiles;
+ iface->list_profiles_finish = modem_3gpp_profile_manager_list_profiles_finish;
+ iface->delete_profile = modem_3gpp_profile_manager_delete_profile;
+ iface->delete_profile_finish = modem_3gpp_profile_manager_delete_profile_finish;
+ iface->check_format = modem_3gpp_profile_manager_check_format;
+ iface->check_format_finish = modem_3gpp_profile_manager_check_format_finish;
+ iface->check_activated_profile = modem_3gpp_profile_manager_check_activated_profile;
+ iface->check_activated_profile_finish = modem_3gpp_profile_manager_check_activated_profile_finish;
+ iface->deactivate_profile = modem_3gpp_profile_manager_deactivate_profile;
+ iface->deactivate_profile_finish = modem_3gpp_profile_manager_deactivate_profile_finish;
+ iface->store_profile = modem_3gpp_profile_manager_store_profile;
+ iface->store_profile_finish = modem_3gpp_profile_manager_store_profile_finish;
}
static void