modem-helpers: new COPS? response parser
Split into two different actions the actual COPS? response parsing and the operator name normalization process. Also, allow parsing not only the operator string, but also the format, mode and the optional access technology value.
This commit is contained in:
@@ -1065,16 +1065,21 @@ modem_3gpp_load_operator_code_finish (MMIfaceModem3gpp *self,
|
||||
GError **error)
|
||||
{
|
||||
const gchar *result;
|
||||
gchar *operator_code;
|
||||
gchar *operator_code = NULL;
|
||||
|
||||
result = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error);
|
||||
if (!result)
|
||||
return NULL;
|
||||
|
||||
operator_code = mm_3gpp_parse_operator (result, MM_MODEM_CHARSET_UNKNOWN);
|
||||
if (operator_code)
|
||||
mm_dbg ("loaded Operator Code: %s", operator_code);
|
||||
if (!mm_3gpp_parse_cops_read_response (result,
|
||||
NULL, /* mode */
|
||||
NULL, /* format */
|
||||
&operator_code,
|
||||
NULL, /* act */
|
||||
error))
|
||||
return NULL;
|
||||
|
||||
mm_dbg ("loaded Operator Code: %s", operator_code);
|
||||
return operator_code;
|
||||
}
|
||||
|
||||
@@ -1109,16 +1114,23 @@ modem_3gpp_load_operator_name_finish (MMIfaceModem3gpp *self,
|
||||
GError **error)
|
||||
{
|
||||
const gchar *result;
|
||||
gchar *operator_name;
|
||||
gchar *operator_name = NULL;
|
||||
|
||||
result = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error);
|
||||
if (!result)
|
||||
return NULL;
|
||||
|
||||
operator_name = mm_3gpp_parse_operator (result, MM_MODEM_CHARSET_UNKNOWN);
|
||||
if (!mm_3gpp_parse_cops_read_response (result,
|
||||
NULL, /* mode */
|
||||
NULL, /* format */
|
||||
&operator_name,
|
||||
NULL, /* act */
|
||||
error))
|
||||
return NULL;
|
||||
|
||||
mm_3gpp_normalize_operator_name (&operator_name, MM_MODEM_CHARSET_UNKNOWN);
|
||||
if (operator_name)
|
||||
mm_dbg ("loaded Operator Name: %s", operator_name);
|
||||
|
||||
return operator_name;
|
||||
}
|
||||
|
||||
|
@@ -2179,18 +2179,26 @@ modem_3gpp_load_operator_name_finish (MMIfaceModem3gpp *self,
|
||||
GError **error)
|
||||
{
|
||||
const gchar *result;
|
||||
gchar *operator_name;
|
||||
gchar *operator_name = NULL;
|
||||
|
||||
result = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error);
|
||||
if (!result)
|
||||
return NULL;
|
||||
|
||||
if (!mm_3gpp_parse_cops_read_response (result,
|
||||
NULL, /* mode */
|
||||
NULL, /* format */
|
||||
&operator_name,
|
||||
NULL, /* act */
|
||||
error))
|
||||
return NULL;
|
||||
|
||||
/* Despite +CSCS? may claim supporting UCS2, Huawei modems always report the
|
||||
* operator name in ASCII in a +COPS response. Thus, we ignore the current
|
||||
* charset claimed by the modem and assume the charset is IRA when parsing
|
||||
* the operator name.
|
||||
*/
|
||||
operator_name = mm_3gpp_parse_operator (result, MM_MODEM_CHARSET_IRA);
|
||||
mm_3gpp_normalize_operator_name (&operator_name, MM_MODEM_CHARSET_IRA);
|
||||
if (operator_name)
|
||||
mm_dbg ("loaded Operator Name: %s", operator_name);
|
||||
|
||||
|
@@ -3495,16 +3495,21 @@ modem_3gpp_load_operator_code_finish (MMIfaceModem3gpp *self,
|
||||
GError **error)
|
||||
{
|
||||
const gchar *result;
|
||||
gchar *operator_code;
|
||||
gchar *operator_code = NULL;
|
||||
|
||||
result = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error);
|
||||
if (!result)
|
||||
return NULL;
|
||||
|
||||
operator_code = mm_3gpp_parse_operator (result, MM_MODEM_CHARSET_UNKNOWN);
|
||||
if (operator_code)
|
||||
mm_dbg ("loaded Operator Code: %s", operator_code);
|
||||
if (!mm_3gpp_parse_cops_read_response (result,
|
||||
NULL, /* mode */
|
||||
NULL, /* format */
|
||||
&operator_code,
|
||||
NULL, /* act */
|
||||
error))
|
||||
return NULL;
|
||||
|
||||
mm_dbg ("loaded Operator Code: %s", operator_code);
|
||||
return operator_code;
|
||||
}
|
||||
|
||||
@@ -3531,16 +3536,23 @@ modem_3gpp_load_operator_name_finish (MMIfaceModem3gpp *self,
|
||||
GError **error)
|
||||
{
|
||||
const gchar *result;
|
||||
gchar *operator_name;
|
||||
gchar *operator_name = NULL;
|
||||
|
||||
result = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error);
|
||||
if (!result)
|
||||
return NULL;
|
||||
|
||||
operator_name = mm_3gpp_parse_operator (result, MM_BROADBAND_MODEM (self)->priv->modem_current_charset);
|
||||
if (!mm_3gpp_parse_cops_read_response (result,
|
||||
NULL, /* mode */
|
||||
NULL, /* format */
|
||||
&operator_name,
|
||||
NULL, /* act */
|
||||
error))
|
||||
return NULL;
|
||||
|
||||
mm_3gpp_normalize_operator_name (&operator_name, MM_BROADBAND_MODEM (self)->priv->modem_current_charset);
|
||||
if (operator_name)
|
||||
mm_dbg ("loaded Operator Name: %s", operator_name);
|
||||
|
||||
return operator_name;
|
||||
}
|
||||
|
||||
|
@@ -849,6 +849,89 @@ mm_3gpp_parse_cops_test_response (const gchar *reply,
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
gboolean
|
||||
mm_3gpp_parse_cops_read_response (const gchar *response,
|
||||
guint *out_mode,
|
||||
guint *out_format,
|
||||
gchar **out_operator,
|
||||
MMModemAccessTechnology *out_act,
|
||||
GError **error)
|
||||
{
|
||||
GRegex *r;
|
||||
GMatchInfo *match_info;
|
||||
GError *inner_error = NULL;
|
||||
guint mode = 0;
|
||||
guint format = 0;
|
||||
gchar *operator = NULL;
|
||||
guint actval = 0;
|
||||
MMModemAccessTechnology act = MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN;
|
||||
|
||||
g_assert (out_mode || out_format || out_operator || out_act);
|
||||
|
||||
/* We assume the response to be either:
|
||||
* +COPS: <mode>,<format>,<oper>
|
||||
* or:
|
||||
* +COPS: <mode>,<format>,<oper>,<AcT>
|
||||
*/
|
||||
r = g_regex_new ("\\+COPS:\\s*(\\d+),(\\d+),([^,]*)(?:,(\\d+))?(?:\\r\\n)?", 0, 0, NULL);
|
||||
g_assert (r != NULL);
|
||||
|
||||
g_regex_match_full (r, response, strlen (response), 0, 0, &match_info, &inner_error);
|
||||
if (inner_error)
|
||||
goto out;
|
||||
|
||||
if (!g_match_info_matches (match_info)) {
|
||||
inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Couldn't match response");
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (out_mode && !mm_get_uint_from_match_info (match_info, 1, &mode)) {
|
||||
inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Error parsing mode");
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (out_format && !mm_get_uint_from_match_info (match_info, 2, &format)) {
|
||||
inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Error parsing format");
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (out_operator && !(operator = mm_get_string_unquoted_from_match_info (match_info, 3))) {
|
||||
inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Error parsing operator");
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* AcT is optional */
|
||||
if (out_act && g_match_info_get_match_count (match_info) >= 5) {
|
||||
if (!mm_get_uint_from_match_info (match_info, 4, &actval)) {
|
||||
inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Error parsing AcT");
|
||||
goto out;
|
||||
}
|
||||
act = get_mm_access_tech_from_etsi_access_tech (actval);
|
||||
}
|
||||
|
||||
out:
|
||||
if (match_info)
|
||||
g_match_info_free (match_info);
|
||||
g_regex_unref (r);
|
||||
|
||||
if (inner_error) {
|
||||
g_free (operator);
|
||||
g_propagate_error (error, inner_error);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (out_mode)
|
||||
*out_mode = mode;
|
||||
if (out_format)
|
||||
*out_format = format;
|
||||
if (out_operator)
|
||||
*out_operator = operator;
|
||||
if (out_act)
|
||||
*out_act = act;
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
static void
|
||||
mm_3gpp_pdp_context_format_free (MM3gppPdpContextFormat *format)
|
||||
@@ -2349,57 +2432,34 @@ mm_string_to_access_tech (const gchar *string)
|
||||
|
||||
/*************************************************************************/
|
||||
|
||||
gchar *
|
||||
mm_3gpp_parse_operator (const gchar *reply,
|
||||
MMModemCharset cur_charset)
|
||||
void
|
||||
mm_3gpp_normalize_operator_name (gchar **operator,
|
||||
MMModemCharset cur_charset)
|
||||
{
|
||||
gchar *operator = NULL;
|
||||
g_assert (operator);
|
||||
|
||||
if (reply && !strncmp (reply, "+COPS: ", 7)) {
|
||||
/* Got valid reply */
|
||||
GRegex *r;
|
||||
GMatchInfo *match_info;
|
||||
if (*operator == NULL)
|
||||
return;
|
||||
|
||||
reply += 7;
|
||||
r = g_regex_new ("(\\d),(\\d),\"(.+)\"", G_REGEX_UNGREEDY, 0, NULL);
|
||||
if (!r)
|
||||
return NULL;
|
||||
|
||||
g_regex_match (r, reply, 0, &match_info);
|
||||
if (g_match_info_matches (match_info))
|
||||
operator = g_match_info_fetch (match_info, 3);
|
||||
|
||||
g_match_info_free (match_info);
|
||||
g_regex_unref (r);
|
||||
/* Some modems (Option & HSO) return the operator name as a hexadecimal
|
||||
* string of the bytes of the operator name as encoded by the current
|
||||
* character set.
|
||||
*/
|
||||
if (cur_charset == MM_MODEM_CHARSET_UCS2) {
|
||||
/* In this case we're already checking UTF-8 validity */
|
||||
*operator = mm_charset_take_and_convert_to_utf8 (*operator, MM_MODEM_CHARSET_UCS2);
|
||||
}
|
||||
/* Ensure the operator name is valid UTF-8 so that we can send it
|
||||
* through D-Bus and such.
|
||||
*/
|
||||
else if (!g_utf8_validate (*operator, -1, NULL))
|
||||
g_clear_pointer (operator, g_free);
|
||||
|
||||
if (operator) {
|
||||
/* Some modems (Option & HSO) return the operator name as a hexadecimal
|
||||
* string of the bytes of the operator name as encoded by the current
|
||||
* character set.
|
||||
*/
|
||||
if (cur_charset == MM_MODEM_CHARSET_UCS2) {
|
||||
/* In this case we're already checking UTF-8 validity */
|
||||
operator = mm_charset_take_and_convert_to_utf8 (operator, MM_MODEM_CHARSET_UCS2);
|
||||
}
|
||||
/* Ensure the operator name is valid UTF-8 so that we can send it
|
||||
* through D-Bus and such.
|
||||
*/
|
||||
else if (!g_utf8_validate (operator, -1, NULL)) {
|
||||
g_free (operator);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Some modems (Novatel LTE) return the operator name as "Unknown" when
|
||||
* it fails to obtain the operator name. Return NULL in such case.
|
||||
*/
|
||||
if (operator && g_ascii_strcasecmp (operator, "unknown") == 0) {
|
||||
g_free (operator);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
return operator;
|
||||
/* Some modems (Novatel LTE) return the operator name as "Unknown" when
|
||||
* it fails to obtain the operator name. Return NULL in such case.
|
||||
*/
|
||||
if (*operator && g_ascii_strcasecmp (*operator, "unknown") == 0)
|
||||
g_clear_pointer (operator, g_free);
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
|
@@ -116,6 +116,14 @@ void mm_3gpp_network_info_list_free (GList *info_list);
|
||||
GList *mm_3gpp_parse_cops_test_response (const gchar *reply,
|
||||
GError **error);
|
||||
|
||||
/* AT+COPS? (current operator) response parser */
|
||||
gboolean mm_3gpp_parse_cops_read_response (const gchar *response,
|
||||
guint *out_mode,
|
||||
guint *out_format,
|
||||
gchar **out_operator,
|
||||
MMModemAccessTechnology *out_act,
|
||||
GError **error);
|
||||
|
||||
/* AT+CGDCONT=? (PDP context format) test parser */
|
||||
typedef struct {
|
||||
guint min_cid;
|
||||
@@ -240,8 +248,8 @@ gchar *mm_3gpp_facility_to_acronym (MMModem3gppFacility facility);
|
||||
|
||||
MMModemAccessTechnology mm_string_to_access_tech (const gchar *string);
|
||||
|
||||
gchar *mm_3gpp_parse_operator (const gchar *reply,
|
||||
MMModemCharset cur_charset);
|
||||
void mm_3gpp_normalize_operator_name (gchar **operator,
|
||||
MMModemCharset cur_charset);
|
||||
|
||||
gboolean mm_3gpp_parse_operator_id (const gchar *operator_id,
|
||||
guint16 *mcc,
|
||||
|
@@ -688,6 +688,83 @@ test_cops_response_umts_invalid (void *f, gpointer d)
|
||||
g_assert_no_error (error);
|
||||
}
|
||||
|
||||
/*****************************************************************************/
|
||||
/* Test COPS? responses */
|
||||
|
||||
typedef struct {
|
||||
const gchar *str;
|
||||
guint mode;
|
||||
guint format;
|
||||
const gchar *operator;
|
||||
MMModemAccessTechnology act;
|
||||
} CopsQueryData;
|
||||
|
||||
static void
|
||||
test_cops_query_data (const CopsQueryData *item)
|
||||
{
|
||||
gboolean result;
|
||||
GError *error = NULL;
|
||||
guint mode = G_MAXUINT;
|
||||
guint format = G_MAXUINT;
|
||||
gchar *operator = NULL;
|
||||
MMModemAccessTechnology act = MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN;
|
||||
|
||||
result = mm_3gpp_parse_cops_read_response (item->str,
|
||||
&mode,
|
||||
&format,
|
||||
&operator,
|
||||
&act,
|
||||
&error);
|
||||
g_assert_no_error (error);
|
||||
g_assert (result);
|
||||
g_assert_cmpuint (mode, ==, item->mode);
|
||||
g_assert_cmpuint (format, ==, item->format);
|
||||
g_assert_cmpstr (operator, ==, item->operator);
|
||||
g_assert_cmpuint (act, ==, item->act);
|
||||
|
||||
g_free (operator);
|
||||
}
|
||||
|
||||
static const CopsQueryData cops_query_data[] = {
|
||||
{
|
||||
.str = "+COPS: 1,0,\"CHINA MOBILE\"",
|
||||
.mode = 1,
|
||||
.format = 0,
|
||||
.operator = "CHINA MOBILE",
|
||||
.act = MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN
|
||||
},
|
||||
{
|
||||
.str = "+COPS: 1,0,\"CHINA MOBILE\",7",
|
||||
.mode = 1,
|
||||
.format = 0,
|
||||
.operator = "CHINA MOBILE",
|
||||
.act = MM_MODEM_ACCESS_TECHNOLOGY_LTE
|
||||
},
|
||||
{
|
||||
.str = "+COPS: 1,2,\"46000\"",
|
||||
.mode = 1,
|
||||
.format = 2,
|
||||
.operator = "46000",
|
||||
.act = MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN
|
||||
},
|
||||
{
|
||||
.str = "+COPS: 1,2,\"46000\",7",
|
||||
.mode = 1,
|
||||
.format = 2,
|
||||
.operator = "46000",
|
||||
.act = MM_MODEM_ACCESS_TECHNOLOGY_LTE
|
||||
},
|
||||
};
|
||||
|
||||
static void
|
||||
test_cops_query (void)
|
||||
{
|
||||
guint i;
|
||||
|
||||
for (i = 0; i < G_N_ELEMENTS (cops_query_data); i++)
|
||||
test_cops_query_data (&cops_query_data[i]);
|
||||
}
|
||||
|
||||
/*****************************************************************************/
|
||||
/* Test CREG/CGREG responses and unsolicited messages */
|
||||
|
||||
@@ -2962,6 +3039,8 @@ int main (int argc, char **argv)
|
||||
g_test_suite_add (suite, TESTCASE (test_cops_response_gsm_invalid, NULL));
|
||||
g_test_suite_add (suite, TESTCASE (test_cops_response_umts_invalid, NULL));
|
||||
|
||||
g_test_suite_add (suite, TESTCASE (test_cops_query, NULL));
|
||||
|
||||
g_test_suite_add (suite, TESTCASE (test_creg1_solicited, reg_data));
|
||||
g_test_suite_add (suite, TESTCASE (test_creg1_unsolicited, reg_data));
|
||||
g_test_suite_add (suite, TESTCASE (test_creg2_mercury_solicited, reg_data));
|
||||
|
Reference in New Issue
Block a user