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).
This commit is contained in:
Aleksander Morgado
2021-04-04 23:07:02 +02:00
parent 10aa516f07
commit 4b2e63aa86

View File

@@ -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>
@@ -10097,6 +10097,554 @@ modem_signal_load_values (MMIfaceModemSignal *self,
user_data);
}
/*****************************************************************************/
/* 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[] = {
@@ -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