broadband-bearer: setup cid selection as a testable method
Rework how the broadband bearer runs CGDCONT? and CGDCONT=? and setup a helper method to perform the CID selection logic. Also, implement unit tests for the CID selection logic.
This commit is contained in:
committed by
Dan Williams
parent
80703b667b
commit
137ece0f2f
+238
-285
@@ -600,22 +600,34 @@ dial_3gpp (MMBroadbandBearer *self,
|
||||
/*****************************************************************************/
|
||||
/* 3GPP cid selection (sub-step of the 3GPP Connection sequence) */
|
||||
|
||||
typedef enum {
|
||||
CID_SELECTION_3GPP_STEP_FIRST,
|
||||
CID_SELECTION_3GPP_STEP_FORMAT,
|
||||
CID_SELECTION_3GPP_STEP_CURRENT,
|
||||
CID_SELECTION_3GPP_STEP_SELECT_CONTEXT,
|
||||
CID_SELECTION_3GPP_STEP_INITIALIZE_CONTEXT,
|
||||
CID_SELECTION_3GPP_STEP_LAST,
|
||||
} CidSelection3gppStep;
|
||||
|
||||
typedef struct {
|
||||
MMBroadbandBearer *self;
|
||||
MMBaseModem *modem;
|
||||
MMPortSerialAt *primary;
|
||||
GCancellable *cancellable;
|
||||
guint cid;
|
||||
guint max_cid;
|
||||
guint blank_cid;
|
||||
gboolean use_existing_cid;
|
||||
MMBearerIpFamily ip_family;
|
||||
CidSelection3gppStep step;
|
||||
MMBaseModem *modem;
|
||||
MMPortSerialAt *primary;
|
||||
GCancellable *cancellable;
|
||||
GList *context_list;
|
||||
GList *context_format_list;
|
||||
guint cid;
|
||||
gboolean cid_reused;
|
||||
gboolean cid_overwritten;
|
||||
MMBearerIpFamily ip_family;
|
||||
const gchar *pdp_type;
|
||||
} CidSelection3gppContext;
|
||||
|
||||
static void
|
||||
cid_selection_3gpp_context_free (CidSelection3gppContext *ctx)
|
||||
{
|
||||
g_object_unref (ctx->self);
|
||||
mm_3gpp_pdp_context_format_list_free (ctx->context_format_list);
|
||||
mm_3gpp_pdp_context_list_free (ctx->context_list);
|
||||
g_object_unref (ctx->modem);
|
||||
g_object_unref (ctx->primary);
|
||||
g_object_unref (ctx->cancellable);
|
||||
@@ -634,228 +646,129 @@ cid_selection_3gpp_finish (MMBroadbandBearer *self,
|
||||
return (guint) (cid < 0 ? 0 : cid);
|
||||
}
|
||||
|
||||
static void cid_selection_3gpp_context_step (GTask *task);
|
||||
|
||||
static void
|
||||
initialize_pdp_context_ready (MMBaseModem *modem,
|
||||
GAsyncResult *res,
|
||||
GTask *task)
|
||||
cgdcont_set_ready (MMBaseModem *modem,
|
||||
GAsyncResult *res,
|
||||
GTask *task)
|
||||
{
|
||||
CidSelection3gppContext *ctx;
|
||||
GError *error = NULL;
|
||||
|
||||
ctx = (CidSelection3gppContext *) g_task_get_task_data (task);
|
||||
ctx = g_task_get_task_data (task);
|
||||
|
||||
mm_base_modem_at_command_full_finish (modem, res, &error);
|
||||
if (error) {
|
||||
mm_warn ("Couldn't initialize PDP context with our APN: '%s'", error->message);
|
||||
mm_warn ("Couldn't initialize context: '%s'", error->message);
|
||||
g_task_return_error (task, error);
|
||||
} else
|
||||
g_task_return_int (task, (gssize) ctx->cid);
|
||||
g_object_unref (task);
|
||||
g_object_unref (task);
|
||||
return;
|
||||
}
|
||||
|
||||
ctx->step++;
|
||||
cid_selection_3gpp_context_step (task);
|
||||
}
|
||||
|
||||
static void
|
||||
find_cid_ready (MMBaseModem *modem,
|
||||
GAsyncResult *res,
|
||||
GTask *task)
|
||||
cid_selection_3gpp_initialize_context (GTask *task)
|
||||
{
|
||||
gchar *apn;
|
||||
gchar *command;
|
||||
GError *error = NULL;
|
||||
const gchar *pdp_type;
|
||||
MMBroadbandBearer *self;
|
||||
CidSelection3gppContext *ctx;
|
||||
const gchar *apn;
|
||||
gchar *quoted_apn;
|
||||
gchar *cmd;
|
||||
|
||||
ctx = (CidSelection3gppContext *) g_task_get_task_data (task);
|
||||
|
||||
mm_base_modem_at_sequence_full_finish (modem, res, NULL, &error);
|
||||
if (error) {
|
||||
mm_warn ("Couldn't find best CID to use: '%s'", error->message);
|
||||
g_task_return_error (task, error);
|
||||
g_object_unref (task);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Validate requested PDP type */
|
||||
pdp_type = mm_3gpp_get_pdp_type_from_ip_family (ctx->ip_family);
|
||||
if (!pdp_type) {
|
||||
gchar * str;
|
||||
|
||||
str = mm_bearer_ip_family_build_string_from_mask (ctx->ip_family);
|
||||
g_task_return_new_error (task,
|
||||
MM_CORE_ERROR, MM_CORE_ERROR_INVALID_ARGS,
|
||||
"Unsupported IP type requested: '%s'", str);
|
||||
g_object_unref (task);
|
||||
g_free (str);
|
||||
return;
|
||||
}
|
||||
|
||||
/* If no error reported, we must have a valid CID to be used */
|
||||
self = g_task_get_source_object (task);
|
||||
ctx = g_task_get_task_data (task);
|
||||
g_assert (ctx->cid != 0);
|
||||
g_assert (!ctx->cid_reused);
|
||||
|
||||
/* If there's already a PDP context defined, just use it */
|
||||
if (ctx->use_existing_cid) {
|
||||
g_task_return_int (task, (gssize) ctx->cid);
|
||||
g_object_unref (task);
|
||||
return;
|
||||
}
|
||||
/* Initialize a PDP context with our APN and PDP type */
|
||||
apn = mm_bearer_properties_get_apn (mm_base_bearer_peek_config (MM_BASE_BEARER (self)));
|
||||
mm_dbg ("%s context with APN '%s' and PDP type '%s'",
|
||||
ctx->cid_overwritten ? "Overwriting" : "Initializing",
|
||||
apn, ctx->pdp_type);
|
||||
quoted_apn = mm_port_serial_at_quote_string (apn);
|
||||
cmd = g_strdup_printf ("+CGDCONT=%u,\"%s\",%s", ctx->cid, ctx->pdp_type, quoted_apn);
|
||||
g_free (quoted_apn);
|
||||
|
||||
/* Otherwise, initialize a new PDP context with our APN */
|
||||
apn = mm_port_serial_at_quote_string (mm_bearer_properties_get_apn (mm_base_bearer_peek_config (MM_BASE_BEARER (ctx->self))));
|
||||
command = g_strdup_printf ("+CGDCONT=%u,\"%s\",%s", ctx->cid, pdp_type, apn);
|
||||
g_free (apn);
|
||||
mm_base_modem_at_command_full (ctx->modem,
|
||||
ctx->primary,
|
||||
command,
|
||||
cmd,
|
||||
3,
|
||||
FALSE,
|
||||
FALSE, /* raw */
|
||||
NULL, /* cancellable */
|
||||
(GAsyncReadyCallback) initialize_pdp_context_ready,
|
||||
(GAsyncReadyCallback) cgdcont_set_ready,
|
||||
task);
|
||||
g_free (command);
|
||||
g_free (cmd);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
parse_cid_range (MMBaseModem *modem,
|
||||
CidSelection3gppContext *ctx,
|
||||
const gchar *command,
|
||||
const gchar *response,
|
||||
gboolean last_command,
|
||||
const GError *error,
|
||||
GVariant **result,
|
||||
GError **result_error)
|
||||
static void
|
||||
cid_selection_3gpp_select_context (GTask *task)
|
||||
{
|
||||
GError *inner_error = NULL;
|
||||
GList *formats, *l;
|
||||
guint unused_cid;
|
||||
MMBroadbandBearer *self;
|
||||
CidSelection3gppContext *ctx;
|
||||
|
||||
/* If cancelled, set result error */
|
||||
if (g_cancellable_is_cancelled (ctx->cancellable)) {
|
||||
g_set_error (result_error, MM_CORE_ERROR, MM_CORE_ERROR_CANCELLED,
|
||||
"Connection setup operation has been cancelled");
|
||||
return FALSE;
|
||||
}
|
||||
self = g_task_get_source_object (task);
|
||||
ctx = g_task_get_task_data (task);
|
||||
|
||||
if (error) {
|
||||
mm_dbg ("Unexpected +CGDCONT error: '%s'", error->message);
|
||||
ctx->cid = mm_3gpp_select_best_cid (mm_bearer_properties_get_apn (mm_base_bearer_peek_config (MM_BASE_BEARER (self))),
|
||||
ctx->ip_family,
|
||||
ctx->context_list,
|
||||
ctx->context_format_list,
|
||||
&ctx->cid_reused,
|
||||
&ctx->cid_overwritten);
|
||||
|
||||
/* At this point, CID must ALWAYS be set */
|
||||
g_assert (ctx->cid);
|
||||
|
||||
/* If we're reusing an existing one, no need to initialize it, so we're done */
|
||||
if (ctx->cid_reused) {
|
||||
g_assert (!ctx->cid_overwritten);
|
||||
ctx->step = CID_SELECTION_3GPP_STEP_LAST;
|
||||
} else
|
||||
ctx->step++;
|
||||
cid_selection_3gpp_context_step (task);
|
||||
}
|
||||
|
||||
static void
|
||||
cgdcont_query_ready (MMBaseModem *modem,
|
||||
GAsyncResult *res,
|
||||
GTask *task)
|
||||
{
|
||||
CidSelection3gppContext *ctx;
|
||||
GError *error = NULL;
|
||||
const gchar *response;
|
||||
GList *l;
|
||||
|
||||
ctx = g_task_get_task_data (task);
|
||||
|
||||
response = mm_base_modem_at_command_full_finish (modem, res, &error);
|
||||
if (!response) {
|
||||
/* Ignore errors */
|
||||
mm_dbg ("Failed checking currently defined contexts: %s", error->message);
|
||||
g_clear_error (&error);
|
||||
goto out;
|
||||
}
|
||||
|
||||
formats = mm_3gpp_parse_cgdcont_test_response (response, &inner_error);
|
||||
if (inner_error) {
|
||||
mm_dbg ("Error parsing +CGDCONT test response: '%s'", inner_error->message);
|
||||
g_error_free (inner_error);
|
||||
/* Build context list */
|
||||
ctx->context_list = mm_3gpp_parse_cgdcont_read_response (response, &error);
|
||||
if (!ctx->context_list) {
|
||||
if (error) {
|
||||
mm_dbg ("Failed parsing currently defined contexts: %s", error->message);
|
||||
g_clear_error (&error);
|
||||
} else
|
||||
mm_dbg ("No contexts currently defined");
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* We need to look for the max allowed cid in the IP family we need, to see if
|
||||
* we can use the next available CID */
|
||||
unused_cid = 0;
|
||||
|
||||
for (l = formats; l; l = g_list_next (l)) {
|
||||
MM3gppPdpContextFormat *format = l->data;
|
||||
|
||||
/* Found exact PDP type? */
|
||||
if (format->pdp_type == ctx->ip_family) {
|
||||
gchar *ip_family_str;
|
||||
|
||||
ip_family_str = mm_bearer_ip_family_build_string_from_mask (format->pdp_type);
|
||||
/* If the max existing CID found during CGDCONT? is below the max allowed
|
||||
* CID, then we can use the next available CID because it's an unused one. */
|
||||
if (ctx->max_cid && (ctx->max_cid < format->max_cid)) {
|
||||
unused_cid = ctx->max_cid + 1;
|
||||
mm_dbg ("Found an unused PDP context at CID %u", unused_cid);
|
||||
}
|
||||
g_free (ip_family_str);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
mm_3gpp_pdp_context_format_list_free (formats);
|
||||
|
||||
/* If an unused CID is found available after the last existing one, use it */
|
||||
if (unused_cid) {
|
||||
ctx->cid = unused_cid;
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
out:
|
||||
|
||||
/* Otherwise, rewrite a context defined with no APN, if any */
|
||||
if (ctx->blank_cid) {
|
||||
mm_dbg ("Rewriting first found context without APN set at CID %u", ctx->max_cid);
|
||||
ctx->cid = ctx->blank_cid;
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/* Otherwise, rewrite the last existing one found */
|
||||
if (ctx->max_cid) {
|
||||
mm_dbg ("Rewriting last found context at CID %u", ctx->max_cid);
|
||||
ctx->cid = ctx->max_cid;
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/* Otherwise, just fallback to CID=1 */
|
||||
mm_dbg ("Defaulting to CID=1");
|
||||
ctx->cid = 1;
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
parse_pdp_list (MMBaseModem *modem,
|
||||
CidSelection3gppContext *ctx,
|
||||
const gchar *command,
|
||||
const gchar *response,
|
||||
gboolean last_command,
|
||||
const GError *error,
|
||||
GVariant **result,
|
||||
GError **result_error)
|
||||
{
|
||||
GError *inner_error = NULL;
|
||||
GList *pdp_list;
|
||||
GList *l;
|
||||
guint exact_cid;
|
||||
guint unused_cid;
|
||||
guint prev_cid;
|
||||
|
||||
/* If cancelled, set result error */
|
||||
if (g_cancellable_is_cancelled (ctx->cancellable)) {
|
||||
g_set_error (result_error, MM_CORE_ERROR, MM_CORE_ERROR_CANCELLED,
|
||||
"Connection setup operation has been cancelled");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* Some Android phones don't support querying existing PDP contexts,
|
||||
* but will accept setting the APN. So if CGDCONT? isn't supported,
|
||||
* just ignore that error and hope for the best. (bgo #637327)
|
||||
*/
|
||||
if (g_error_matches (error,
|
||||
MM_MOBILE_EQUIPMENT_ERROR,
|
||||
MM_MOBILE_EQUIPMENT_ERROR_NOT_SUPPORTED)) {
|
||||
mm_dbg ("Querying PDP context list is unsupported");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (error) {
|
||||
mm_dbg ("Unexpected +CGDCONT? error: '%s'", error->message);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
pdp_list = mm_3gpp_parse_cgdcont_read_response (response, &inner_error);
|
||||
if (!pdp_list) {
|
||||
if (inner_error) {
|
||||
mm_dbg ("%s", inner_error->message);
|
||||
g_error_free (inner_error);
|
||||
} else {
|
||||
/* No predefined PDP contexts found */
|
||||
mm_dbg ("No PDP contexts found");
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* Show all found PDP contexts in debug log */
|
||||
mm_dbg ("Found '%u' PDP contexts", g_list_length (pdp_list));
|
||||
for (l = pdp_list; l; l = g_list_next (l)) {
|
||||
mm_dbg ("Found '%u' PDP contexts", g_list_length (ctx->context_list));
|
||||
for (l = ctx->context_list; l; l = g_list_next (l)) {
|
||||
MM3gppPdpContext *pdp = l->data;
|
||||
gchar *ip_family_str;
|
||||
gchar *ip_family_str;
|
||||
|
||||
ip_family_str = mm_bearer_ip_family_build_string_from_mask (pdp->pdp_type);
|
||||
mm_dbg (" PDP context [cid=%u] [type='%s'] [apn='%s']",
|
||||
@@ -865,86 +778,121 @@ parse_pdp_list (MMBaseModem *modem,
|
||||
g_free (ip_family_str);
|
||||
}
|
||||
|
||||
/* Look for the exact PDP context we want */
|
||||
exact_cid = 0;
|
||||
unused_cid = 0;
|
||||
prev_cid = 0;
|
||||
|
||||
for (l = pdp_list; l; l = g_list_next (l)) {
|
||||
MM3gppPdpContext *pdp = l->data;
|
||||
|
||||
if (pdp->pdp_type == ctx->ip_family) {
|
||||
const gchar *apn;
|
||||
|
||||
apn = mm_bearer_properties_get_apn (mm_base_bearer_peek_config (MM_BASE_BEARER (ctx->self)));
|
||||
|
||||
/* First requested, then existing */
|
||||
if (mm_3gpp_cmp_apn_name (apn, pdp->apn)) {
|
||||
gchar *ip_family_str;
|
||||
|
||||
/* Found a PDP context with the same APN and PDP type, we'll use it. */
|
||||
ip_family_str = mm_bearer_ip_family_build_string_from_mask (pdp->pdp_type);
|
||||
mm_dbg ("Found PDP context with CID %u and PDP type %s for APN '%s'",
|
||||
pdp->cid, ip_family_str, apn ? apn : "");
|
||||
exact_cid = pdp->cid;
|
||||
g_free (ip_family_str);
|
||||
/* In this case, stop searching */
|
||||
break;
|
||||
}
|
||||
|
||||
/* PDP with no APN set? we may use that one if no exact match found */
|
||||
if ((!pdp->apn || !pdp->apn[0]) && !ctx->blank_cid) {
|
||||
ctx->blank_cid = pdp->cid;
|
||||
mm_dbg ("Found the first PDP context with no APN with CID %u", ctx->blank_cid);
|
||||
}
|
||||
}
|
||||
|
||||
/* If an unused CID was not found yet and the previous CID is not (CID - 1),
|
||||
* this means that (previous CID + 1) is an unused CID that can be used.
|
||||
* This logic will allow us using unused CIDs that are available in the gaps
|
||||
* between already defined contexts.
|
||||
*/
|
||||
if (!unused_cid && prev_cid && ((prev_cid + 1) < pdp->cid)) {
|
||||
unused_cid = prev_cid + 1;
|
||||
mm_dbg ("Found the first unused PDP context with CID %u", unused_cid);
|
||||
}
|
||||
|
||||
/* Update previous CID value to the current CID for use in the next loop,
|
||||
* unless an unused CID was already found.
|
||||
*/
|
||||
if (!unused_cid)
|
||||
prev_cid = pdp->cid;
|
||||
|
||||
if (ctx->max_cid < pdp->cid)
|
||||
ctx->max_cid = pdp->cid;
|
||||
}
|
||||
mm_3gpp_pdp_context_list_free (pdp_list);
|
||||
|
||||
/* Always prefer an exact match */
|
||||
if (exact_cid) {
|
||||
ctx->use_existing_cid = TRUE;
|
||||
ctx->cid = exact_cid;
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/* Otherwise, try to use an empty CID detected in between the already
|
||||
* defined contexts */
|
||||
if (unused_cid) {
|
||||
ctx->cid = unused_cid;
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/* And otherwise, fallback to running CGDCONT=? to look for the max
|
||||
* range of CIDs and try to use the next one available after the last
|
||||
* existing one. */
|
||||
return FALSE;
|
||||
out:
|
||||
ctx->step++;
|
||||
cid_selection_3gpp_context_step (task);
|
||||
}
|
||||
|
||||
static const MMBaseModemAtCommand find_cid_sequence[] = {
|
||||
{ "+CGDCONT?", 3, FALSE, (MMBaseModemAtResponseProcessor) parse_pdp_list },
|
||||
{ "+CGDCONT=?", 3, TRUE, (MMBaseModemAtResponseProcessor) parse_cid_range },
|
||||
{ NULL }
|
||||
};
|
||||
static void
|
||||
cid_selection_3gpp_query_current (GTask *task)
|
||||
{
|
||||
CidSelection3gppContext *ctx;
|
||||
|
||||
ctx = g_task_get_task_data (task);
|
||||
|
||||
mm_dbg ("Checking currently defined contexts...");
|
||||
mm_base_modem_at_command_full (ctx->modem,
|
||||
ctx->primary,
|
||||
"+CGDCONT?",
|
||||
3,
|
||||
FALSE, /* cached */
|
||||
FALSE, /* raw */
|
||||
ctx->cancellable,
|
||||
(GAsyncReadyCallback)cgdcont_query_ready,
|
||||
task);
|
||||
}
|
||||
|
||||
static void
|
||||
cgdcont_test_ready (MMBaseModem *modem,
|
||||
GAsyncResult *res,
|
||||
GTask *task)
|
||||
{
|
||||
CidSelection3gppContext *ctx;
|
||||
GError *error = NULL;
|
||||
const gchar *response;
|
||||
|
||||
ctx = g_task_get_task_data (task);
|
||||
|
||||
response = mm_base_modem_at_command_full_finish (modem, res, &error);
|
||||
if (!response) {
|
||||
/* Ignore errors */
|
||||
mm_dbg ("Failed checking context definition format: %s", error->message);
|
||||
g_clear_error (&error);
|
||||
goto out;
|
||||
}
|
||||
|
||||
ctx->context_format_list = mm_3gpp_parse_cgdcont_test_response (response, &error);
|
||||
if (error) {
|
||||
mm_dbg ("Error parsing +CGDCONT test response: '%s'", error->message);
|
||||
g_clear_error (&error);
|
||||
goto out;
|
||||
}
|
||||
|
||||
out:
|
||||
ctx->step++;
|
||||
cid_selection_3gpp_context_step (task);
|
||||
}
|
||||
|
||||
static void
|
||||
cid_selection_3gpp_query_format (GTask *task)
|
||||
{
|
||||
CidSelection3gppContext *ctx;
|
||||
|
||||
ctx = g_task_get_task_data (task);
|
||||
|
||||
mm_dbg ("Checking context definition format...");
|
||||
mm_base_modem_at_command_full (ctx->modem,
|
||||
ctx->primary,
|
||||
"+CGDCONT=?",
|
||||
3,
|
||||
TRUE, /* cached */
|
||||
FALSE, /* raw */
|
||||
ctx->cancellable,
|
||||
(GAsyncReadyCallback)cgdcont_test_ready,
|
||||
task);
|
||||
}
|
||||
|
||||
static void
|
||||
cid_selection_3gpp_context_step (GTask *task)
|
||||
{
|
||||
CidSelection3gppContext *ctx;
|
||||
|
||||
ctx = g_task_get_task_data (task);
|
||||
|
||||
/* Abort if we've been cancelled */
|
||||
if (g_task_return_error_if_cancelled (task)) {
|
||||
g_object_unref (task);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (ctx->step) {
|
||||
case CID_SELECTION_3GPP_STEP_FIRST:
|
||||
/* Fall down to next step */
|
||||
ctx->step++;
|
||||
|
||||
case CID_SELECTION_3GPP_STEP_FORMAT:
|
||||
cid_selection_3gpp_query_format (task);
|
||||
return;
|
||||
|
||||
case CID_SELECTION_3GPP_STEP_CURRENT:
|
||||
cid_selection_3gpp_query_current (task);
|
||||
return;
|
||||
|
||||
case CID_SELECTION_3GPP_STEP_SELECT_CONTEXT:
|
||||
cid_selection_3gpp_select_context (task);
|
||||
return;
|
||||
|
||||
case CID_SELECTION_3GPP_STEP_INITIALIZE_CONTEXT:
|
||||
cid_selection_3gpp_initialize_context (task);
|
||||
return;
|
||||
|
||||
case CID_SELECTION_3GPP_STEP_LAST:
|
||||
g_assert (ctx->cid != 0);
|
||||
g_task_return_int (task, (gssize) ctx->cid);
|
||||
g_object_unref (task);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
cid_selection_3gpp (MMBroadbandBearer *self,
|
||||
@@ -957,25 +905,30 @@ cid_selection_3gpp (MMBroadbandBearer *self,
|
||||
GTask *task;
|
||||
CidSelection3gppContext *ctx;
|
||||
|
||||
task = g_task_new (self, cancellable, callback, user_data);
|
||||
|
||||
ctx = g_slice_new0 (CidSelection3gppContext);
|
||||
ctx->self = g_object_ref (self);
|
||||
ctx->step = CID_SELECTION_3GPP_STEP_FIRST;
|
||||
ctx->modem = g_object_ref (modem);
|
||||
ctx->primary = g_object_ref (primary);
|
||||
ctx->cancellable = g_object_ref (cancellable);
|
||||
ctx->ip_family = select_bearer_ip_family (self);
|
||||
|
||||
task = g_task_new (self, cancellable, callback, user_data);
|
||||
g_task_set_task_data (task, ctx, (GDestroyNotify) cid_selection_3gpp_context_free);
|
||||
|
||||
mm_dbg ("Looking for best CID...");
|
||||
mm_base_modem_at_sequence_full (ctx->modem,
|
||||
ctx->primary,
|
||||
find_cid_sequence,
|
||||
ctx, /* also passed as response processor context */
|
||||
NULL, /* response_processor_context_free */
|
||||
NULL, /* cancellable */
|
||||
(GAsyncReadyCallback) find_cid_ready,
|
||||
task);
|
||||
/* Validate PDP type */
|
||||
ctx->pdp_type = mm_3gpp_get_pdp_type_from_ip_family (ctx->ip_family);
|
||||
if (!ctx->pdp_type) {
|
||||
gchar * str;
|
||||
|
||||
str = mm_bearer_ip_family_build_string_from_mask (ctx->ip_family);
|
||||
g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_INVALID_ARGS,
|
||||
"Unsupported IP type requested: '%s'", str);
|
||||
g_object_unref (task);
|
||||
g_free (str);
|
||||
return;
|
||||
}
|
||||
|
||||
cid_selection_3gpp_context_step (task);
|
||||
}
|
||||
|
||||
/*****************************************************************************/
|
||||
|
||||
@@ -1540,6 +1540,131 @@ mm_3gpp_cmp_apn_name (const gchar *requested,
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
static guint
|
||||
find_max_allowed_cid (GList *context_format_list,
|
||||
MMBearerIpFamily ip_family)
|
||||
{
|
||||
GList *l;
|
||||
|
||||
for (l = context_format_list; l; l = g_list_next (l)) {
|
||||
MM3gppPdpContextFormat *format = l->data;
|
||||
|
||||
/* Found exact PDP type? */
|
||||
if (format->pdp_type == ip_family)
|
||||
return format->max_cid;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
guint
|
||||
mm_3gpp_select_best_cid (const gchar *apn,
|
||||
MMBearerIpFamily ip_family,
|
||||
GList *context_list,
|
||||
GList *context_format_list,
|
||||
gboolean *cid_reused,
|
||||
gboolean *cid_overwritten)
|
||||
{
|
||||
GList *l;
|
||||
guint prev_cid = 0;
|
||||
guint exact_cid = 0;
|
||||
guint unused_cid = 0;
|
||||
guint max_cid = 0;
|
||||
guint max_allowed_cid = 0;
|
||||
guint blank_cid = 0;
|
||||
gchar *ip_family_str;
|
||||
|
||||
ip_family_str = mm_bearer_ip_family_build_string_from_mask (ip_family);
|
||||
mm_dbg ("Looking for best CID matching APN '%s' and PDP type '%s'...",
|
||||
apn, ip_family_str);
|
||||
g_free (ip_family_str);
|
||||
|
||||
/* Look for the exact PDP context we want */
|
||||
for (l = context_list; l; l = g_list_next (l)) {
|
||||
MM3gppPdpContext *pdp = l->data;
|
||||
|
||||
/* Match PDP type */
|
||||
if (pdp->pdp_type == ip_family) {
|
||||
/* Try to match exact APN and PDP type */
|
||||
if (mm_3gpp_cmp_apn_name (apn, pdp->apn)) {
|
||||
exact_cid = pdp->cid;
|
||||
break;
|
||||
}
|
||||
|
||||
/* Same PDP type but with no APN set? we may use that one if no exact match found */
|
||||
if ((!pdp->apn || !pdp->apn[0]) && !blank_cid)
|
||||
blank_cid = pdp->cid;
|
||||
}
|
||||
|
||||
/* If an unused CID was not found yet and the previous CID is not (CID - 1),
|
||||
* this means that (previous CID + 1) is an unused CID that can be used.
|
||||
* This logic will allow us using unused CIDs that are available in the gaps
|
||||
* between already defined contexts.
|
||||
*/
|
||||
if (!unused_cid && prev_cid && ((prev_cid + 1) < pdp->cid))
|
||||
unused_cid = prev_cid + 1;
|
||||
|
||||
/* Update previous CID value to the current CID for use in the next loop,
|
||||
* unless an unused CID was already found.
|
||||
*/
|
||||
if (!unused_cid)
|
||||
prev_cid = pdp->cid;
|
||||
|
||||
/* Update max CID if we found a bigger one */
|
||||
if (max_cid < pdp->cid)
|
||||
max_cid = pdp->cid;
|
||||
}
|
||||
|
||||
/* Always prefer an exact match */
|
||||
if (exact_cid) {
|
||||
mm_dbg ("Found exact context at CID %u", exact_cid);
|
||||
*cid_reused = TRUE;
|
||||
*cid_overwritten = FALSE;
|
||||
return exact_cid;
|
||||
}
|
||||
|
||||
/* Try to use an unused CID detected in between the already defined contexts */
|
||||
if (unused_cid) {
|
||||
mm_dbg ("Found unused context at CID %u", unused_cid);
|
||||
*cid_reused = FALSE;
|
||||
*cid_overwritten = FALSE;
|
||||
return unused_cid;
|
||||
}
|
||||
|
||||
/* If the max existing CID found during CGDCONT? is below the max allowed
|
||||
* CID, then we can use the next available CID because it's an unused one. */
|
||||
max_allowed_cid = find_max_allowed_cid (context_format_list, ip_family);
|
||||
if (max_cid && (max_cid < max_allowed_cid)) {
|
||||
mm_dbg ("Found unused context at CID %u (<%u)", max_cid + 1, max_allowed_cid);
|
||||
*cid_reused = FALSE;
|
||||
*cid_overwritten = FALSE;
|
||||
return (max_cid + 1);
|
||||
}
|
||||
|
||||
/* Rewrite a context defined with no APN, if any */
|
||||
if (blank_cid) {
|
||||
mm_dbg ("Rewriting context with empty APN at CID %u", blank_cid);
|
||||
*cid_reused = FALSE;
|
||||
*cid_overwritten = TRUE;
|
||||
return blank_cid;
|
||||
}
|
||||
|
||||
/* Rewrite the last existing one found */
|
||||
if (max_cid) {
|
||||
mm_dbg ("Rewriting last context detected at CID %u", max_cid);
|
||||
*cid_reused = FALSE;
|
||||
*cid_overwritten = TRUE;
|
||||
return max_cid;
|
||||
}
|
||||
|
||||
/* Otherwise, just fallback to CID=1 */
|
||||
mm_dbg ("Falling back to CID 1");
|
||||
*cid_reused = FALSE;
|
||||
*cid_overwritten = TRUE;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
static void
|
||||
mm_3gpp_pdp_context_format_free (MM3gppPdpContextFormat *format)
|
||||
{
|
||||
|
||||
@@ -192,6 +192,14 @@ void mm_3gpp_pdp_context_list_free (GList *pdp_list);
|
||||
GList *mm_3gpp_parse_cgdcont_read_response (const gchar *reply,
|
||||
GError **error);
|
||||
|
||||
/* Select best CID to use during connection */
|
||||
guint mm_3gpp_select_best_cid (const gchar *apn,
|
||||
MMBearerIpFamily ip_family,
|
||||
GList *context_list,
|
||||
GList *context_format_list,
|
||||
gboolean *cid_reused,
|
||||
gboolean *cid_overwritten);
|
||||
|
||||
/* AT+CGACT? (active PDP context query) response parser */
|
||||
typedef struct {
|
||||
guint cid;
|
||||
|
||||
@@ -2681,6 +2681,171 @@ test_cgact_read_response_multiple (void)
|
||||
test_cgact_read_results ("multiple", reply, &expected[0], G_N_ELEMENTS (expected));
|
||||
}
|
||||
|
||||
/*****************************************************************************/
|
||||
/* CID selection logic */
|
||||
|
||||
typedef struct {
|
||||
const gchar *apn;
|
||||
MMBearerIpFamily ip_family;
|
||||
const gchar *cgdcont_test;
|
||||
const gchar *cgdcont_query;
|
||||
guint expected_cid;
|
||||
gboolean expected_cid_reused;
|
||||
gboolean expected_cid_overwritten;
|
||||
} CidSelectionTest;
|
||||
|
||||
static const CidSelectionTest cid_selection_tests[] = {
|
||||
/* Test: exact APN match */
|
||||
{
|
||||
.apn = "ac.vodafone.es",
|
||||
.ip_family = MM_BEARER_IP_FAMILY_IPV4,
|
||||
.cgdcont_test = "+CGDCONT: (1-10),\"IP\",,,(0,1),(0,1)\r\n"
|
||||
"+CGDCONT: (1-10),\"IPV6\",,,(0,1),(0,1)\r\n"
|
||||
"+CGDCONT: (1-10),\"IPV4V6\",,,(0,1),(0,1)\r\n",
|
||||
.cgdcont_query = "+CGDCONT: 1,\"IP\",\"telefonica.es\",\"\",0,0\r\n"
|
||||
"+CGDCONT: 2,\"IP\",\"ac.vodafone.es\",\"\",0,0\r\n"
|
||||
"+CGDCONT: 3,\"IP\",\"inet.es\",\"\",0,0\r\n",
|
||||
.expected_cid = 2,
|
||||
.expected_cid_reused = TRUE,
|
||||
.expected_cid_overwritten = FALSE
|
||||
},
|
||||
/* Test: exact APN match reported as activated */
|
||||
{
|
||||
.apn = "ac.vodafone.es",
|
||||
.ip_family = MM_BEARER_IP_FAMILY_IPV4,
|
||||
.cgdcont_test = "+CGDCONT: (1-10),\"IP\",,,(0,1),(0,1)\r\n"
|
||||
"+CGDCONT: (1-10),\"IPV6\",,,(0,1),(0,1)\r\n"
|
||||
"+CGDCONT: (1-10),\"IPV4V6\",,,(0,1),(0,1)\r\n",
|
||||
.cgdcont_query = "+CGDCONT: 1,\"IP\",\"telefonica.es\",\"\",0,0\r\n"
|
||||
"+CGDCONT: 2,\"IP\",\"ac.vodafone.es.MNC001.MCC214.GPRS\",\"\",0,0\r\n"
|
||||
"+CGDCONT: 3,\"IP\",\"inet.es\",\"\",0,0\r\n",
|
||||
.expected_cid = 2,
|
||||
.expected_cid_reused = TRUE,
|
||||
.expected_cid_overwritten = FALSE
|
||||
},
|
||||
/* Test: first empty slot in between defined contexts */
|
||||
{
|
||||
.apn = "ac.vodafone.es",
|
||||
.ip_family = MM_BEARER_IP_FAMILY_IPV4,
|
||||
.cgdcont_test = "+CGDCONT: (1-10),\"IP\",,,(0,1),(0,1)\r\n"
|
||||
"+CGDCONT: (1-10),\"IPV6\",,,(0,1),(0,1)\r\n"
|
||||
"+CGDCONT: (1-10),\"IPV4V6\",,,(0,1),(0,1)\r\n",
|
||||
.cgdcont_query = "+CGDCONT: 1,\"IP\",\"telefonica.es\",\"\",0,0\r\n"
|
||||
"+CGDCONT: 10,\"IP\",\"inet.es\",\"\",0,0\r\n",
|
||||
.expected_cid = 2,
|
||||
.expected_cid_reused = FALSE,
|
||||
.expected_cid_overwritten = FALSE
|
||||
},
|
||||
/* Test: first empty slot in between defined contexts, different PDP types */
|
||||
{
|
||||
.apn = "ac.vodafone.es",
|
||||
.ip_family = MM_BEARER_IP_FAMILY_IPV4,
|
||||
.cgdcont_test = "+CGDCONT: (1-10),\"IP\",,,(0,1),(0,1)\r\n"
|
||||
"+CGDCONT: (1-10),\"IPV6\",,,(0,1),(0,1)\r\n"
|
||||
"+CGDCONT: (1-10),\"IPV4V6\",,,(0,1),(0,1)\r\n",
|
||||
.cgdcont_query = "+CGDCONT: 1,\"IPV6\",\"telefonica.es\",\"\",0,0\r\n"
|
||||
"+CGDCONT: 10,\"IP\",\"inet.es\",\"\",0,0\r\n",
|
||||
.expected_cid = 2,
|
||||
.expected_cid_reused = FALSE,
|
||||
.expected_cid_overwritten = FALSE
|
||||
},
|
||||
/* Test: first empty slot after last context found */
|
||||
{
|
||||
.apn = "ac.vodafone.es",
|
||||
.ip_family = MM_BEARER_IP_FAMILY_IPV4,
|
||||
.cgdcont_test = "+CGDCONT: (1-10),\"IP\",,,(0,1),(0,1)\r\n"
|
||||
"+CGDCONT: (1-10),\"IPV6\",,,(0,1),(0,1)\r\n"
|
||||
"+CGDCONT: (1-10),\"IPV4V6\",,,(0,1),(0,1)\r\n",
|
||||
.cgdcont_query = "+CGDCONT: 1,\"IP\",\"telefonica.es\",\"\",0,0\r\n"
|
||||
"+CGDCONT: 2,\"IP\",\"inet.es\",\"\",0,0\r\n",
|
||||
.expected_cid = 3,
|
||||
.expected_cid_reused = FALSE,
|
||||
.expected_cid_overwritten = FALSE
|
||||
},
|
||||
/* Test: first empty slot after last context found, different PDP types */
|
||||
{
|
||||
.apn = "ac.vodafone.es",
|
||||
.ip_family = MM_BEARER_IP_FAMILY_IPV4,
|
||||
.cgdcont_test = "+CGDCONT: (1-10),\"IP\",,,(0,1),(0,1)\r\n"
|
||||
"+CGDCONT: (1-10),\"IPV6\",,,(0,1),(0,1)\r\n"
|
||||
"+CGDCONT: (1-10),\"IPV4V6\",,,(0,1),(0,1)\r\n",
|
||||
.cgdcont_query = "+CGDCONT: 1,\"IP\",\"telefonica.es\",\"\",0,0\r\n"
|
||||
"+CGDCONT: 2,\"IPV6\",\"inet.es\",\"\",0,0\r\n",
|
||||
.expected_cid = 3,
|
||||
.expected_cid_reused = FALSE,
|
||||
.expected_cid_overwritten = FALSE
|
||||
},
|
||||
/* Test: no empty slot, rewrite context with empty APN */
|
||||
{
|
||||
.apn = "ac.vodafone.es",
|
||||
.ip_family = MM_BEARER_IP_FAMILY_IPV4,
|
||||
.cgdcont_test = "+CGDCONT: (1-3),\"IP\",,,(0,1),(0,1)\r\n"
|
||||
"+CGDCONT: (1-3),\"IPV6\",,,(0,1),(0,1)\r\n"
|
||||
"+CGDCONT: (1-3),\"IPV4V6\",,,(0,1),(0,1)\r\n",
|
||||
.cgdcont_query = "+CGDCONT: 1,\"IP\",\"telefonica.es\",\"\",0,0\r\n"
|
||||
"+CGDCONT: 2,\"IP\",\"\",\"\",0,0\r\n"
|
||||
"+CGDCONT: 3,\"IP\",\"inet.es\",\"\",0,0\r\n",
|
||||
.expected_cid = 2,
|
||||
.expected_cid_reused = FALSE,
|
||||
.expected_cid_overwritten = TRUE
|
||||
},
|
||||
/* Test: no empty slot, rewrite last context found */
|
||||
{
|
||||
.apn = "ac.vodafone.es",
|
||||
.ip_family = MM_BEARER_IP_FAMILY_IPV4,
|
||||
.cgdcont_test = "+CGDCONT: (1-3),\"IP\",,,(0,1),(0,1)\r\n"
|
||||
"+CGDCONT: (1-3),\"IPV6\",,,(0,1),(0,1)\r\n"
|
||||
"+CGDCONT: (1-3),\"IPV4V6\",,,(0,1),(0,1)\r\n",
|
||||
.cgdcont_query = "+CGDCONT: 1,\"IP\",\"telefonica.es\",\"\",0,0\r\n"
|
||||
"+CGDCONT: 2,\"IP\",\"vzwinternet\",\"\",0,0\r\n"
|
||||
"+CGDCONT: 3,\"IP\",\"inet.es\",\"\",0,0\r\n",
|
||||
.expected_cid = 3,
|
||||
.expected_cid_reused = FALSE,
|
||||
.expected_cid_overwritten = TRUE
|
||||
},
|
||||
/* Test: CGDCONT? and CGDCONT=? failures, fallback to CID=1 (a.g. some Android phones) */
|
||||
{
|
||||
.apn = "ac.vodafone.es",
|
||||
.ip_family = MM_BEARER_IP_FAMILY_IPV4,
|
||||
.cgdcont_test = NULL,
|
||||
.cgdcont_query = NULL,
|
||||
.expected_cid = 1,
|
||||
.expected_cid_reused = FALSE,
|
||||
.expected_cid_overwritten = TRUE
|
||||
},
|
||||
};
|
||||
|
||||
static void
|
||||
test_cid_selection (void)
|
||||
{
|
||||
guint i;
|
||||
|
||||
for (i = 0; i < G_N_ELEMENTS (cid_selection_tests); i++) {
|
||||
const CidSelectionTest *test;
|
||||
GList *context_list;
|
||||
GList *context_format_list;
|
||||
guint cid;
|
||||
gboolean cid_reused;
|
||||
gboolean cid_overwritten;
|
||||
|
||||
test = &cid_selection_tests[i];
|
||||
|
||||
context_format_list = test->cgdcont_test ? mm_3gpp_parse_cgdcont_test_response (test->cgdcont_test, NULL) : NULL;
|
||||
context_list = test->cgdcont_query ? mm_3gpp_parse_cgdcont_read_response (test->cgdcont_query, NULL) : NULL;
|
||||
|
||||
cid = mm_3gpp_select_best_cid (test->apn, test->ip_family,
|
||||
context_list, context_format_list,
|
||||
&cid_reused, &cid_overwritten);
|
||||
|
||||
g_assert_cmpuint (cid, ==, test->expected_cid);
|
||||
g_assert_cmpuint (cid_reused, ==, test->expected_cid_reused);
|
||||
g_assert_cmpuint (cid_overwritten, ==, test->expected_cid_overwritten);
|
||||
|
||||
mm_3gpp_pdp_context_format_list_free (context_format_list);
|
||||
mm_3gpp_pdp_context_list_free (context_list);
|
||||
}
|
||||
}
|
||||
|
||||
/*****************************************************************************/
|
||||
/* Test CPMS responses */
|
||||
|
||||
@@ -4447,6 +4612,8 @@ int main (int argc, char **argv)
|
||||
g_test_suite_add (suite, TESTCASE (test_cgdcont_read_response_nokia, NULL));
|
||||
g_test_suite_add (suite, TESTCASE (test_cgdcont_read_response_samsung, NULL));
|
||||
|
||||
g_test_suite_add (suite, TESTCASE (test_cid_selection, NULL));
|
||||
|
||||
g_test_suite_add (suite, TESTCASE (test_cgact_read_response_none, NULL));
|
||||
g_test_suite_add (suite, TESTCASE (test_cgact_read_response_single_inactive, NULL));
|
||||
g_test_suite_add (suite, TESTCASE (test_cgact_read_response_single_active, NULL));
|
||||
|
||||
Reference in New Issue
Block a user