huawei: improve support for network time on Huawei modules

Third revision of Huawei nwtime support.  Takes on feedback from the
mailing list including helpers,  some basic tests and use of the ^NTCT
command to determine network time support (^NWTIME).  Expanded test cases,
more use of g_assert and more logical helper return values/errors.

Signed-off-by: David McCullough <david.mccullough@accelecon.com>
This commit is contained in:
David McCullough
2014-08-06 17:30:22 +10:00
committed by Aleksander Morgado
parent 8c4318d87a
commit bd0ffd24f1
4 changed files with 404 additions and 65 deletions

View File

@@ -108,6 +108,8 @@ struct _MMBroadbandModemHuaweiPrivate {
FeatureSupport syscfg_support; FeatureSupport syscfg_support;
FeatureSupport syscfgex_support; FeatureSupport syscfgex_support;
FeatureSupport prefmode_support; FeatureSupport prefmode_support;
FeatureSupport time_support;
FeatureSupport nwtime_support;
MMModemLocationSource enabled_sources; MMModemLocationSource enabled_sources;
@@ -2840,70 +2842,70 @@ get_detailed_registration_state (MMIfaceModemCdma *self,
/*****************************************************************************/ /*****************************************************************************/
/* Load network time (Time interface) */ /* Load network time (Time interface) */
static gchar * static MMNetworkTimezone *
modem_time_load_network_time_finish (MMIfaceModemTime *self, modem_time_load_network_timezone_finish (MMIfaceModemTime *_self,
GAsyncResult *res, GAsyncResult *res,
GError **error) GError **error)
{ {
MMBroadbandModemHuawei *self = MM_BROADBAND_MODEM_HUAWEI (_self);
MMNetworkTimezone *tz = NULL;
const gchar *response; const gchar *response;
GRegex *r;
GMatchInfo *match_info = NULL;
GError *match_error = NULL;
guint year, month, day, hour, minute, second;
gchar *result = NULL;
response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error); g_assert (self->priv->nwtime_support == FEATURE_SUPPORTED ||
self->priv->time_support == FEATURE_SUPPORTED);
response = mm_base_modem_at_command_finish (MM_BASE_MODEM (_self), res, error);
if (!response) if (!response)
return NULL; return NULL;
/* Already in ISO-8601 format, but verify just to be sure */ if (self->priv->nwtime_support == FEATURE_SUPPORTED)
r = g_regex_new ("\\^TIME:\\s*(\\d+)/(\\d+)/(\\d+)\\s*(\\d+):(\\d+):(\\d*)$", 0, 0, NULL); mm_huawei_parse_nwtime_response (response, NULL, &tz, error);
g_assert (r != NULL); else if (self->priv->time_support == FEATURE_SUPPORTED)
mm_huawei_parse_time_response (response, NULL, &tz, error);
return tz;
}
if (!g_regex_match_full (r, response, -1, 0, 0, &match_info, &match_error)) {
if (match_error) {
g_propagate_error (error, match_error);
g_prefix_error (error, "Could not parse ^TIME results: ");
} else {
g_set_error_literal (error,
MM_CORE_ERROR,
MM_CORE_ERROR_FAILED,
"Couldn't match ^TIME reply");
}
} else {
/* Remember that g_match_info_get_match_count() includes match #0 */
g_assert (g_match_info_get_match_count (match_info) >= 7);
if (mm_get_uint_from_match_info (match_info, 1, &year) && static gchar *
mm_get_uint_from_match_info (match_info, 2, &month) && modem_time_load_network_time_finish (MMIfaceModemTime *_self,
mm_get_uint_from_match_info (match_info, 3, &day) && GAsyncResult *res,
mm_get_uint_from_match_info (match_info, 4, &hour) && GError **error)
mm_get_uint_from_match_info (match_info, 5, &minute) && {
mm_get_uint_from_match_info (match_info, 6, &second)) { MMBroadbandModemHuawei *self = MM_BROADBAND_MODEM_HUAWEI (_self);
/* Return ISO-8601 format date/time string */ const gchar *response;
result = g_strdup_printf ("%04d/%02d/%02d %02d:%02d:%02d", gchar *iso8601 = NULL;
year, month, day, hour, minute, second);
} else {
g_set_error_literal (error,
MM_CORE_ERROR,
MM_CORE_ERROR_FAILED,
"Failed to parse ^TIME reply");
}
}
if (match_info) g_assert (self->priv->nwtime_support == FEATURE_SUPPORTED ||
g_match_info_free (match_info); self->priv->time_support == FEATURE_SUPPORTED);
g_regex_unref (r);
return result; response = mm_base_modem_at_command_finish (MM_BASE_MODEM (_self), res, error);
if (!response)
return NULL;
if (self->priv->nwtime_support == FEATURE_SUPPORTED)
mm_huawei_parse_nwtime_response (response, &iso8601, NULL, error);
else if (self->priv->time_support == FEATURE_SUPPORTED)
mm_huawei_parse_time_response (response, &iso8601, NULL, error);
return iso8601;
} }
static void static void
modem_time_load_network_time (MMIfaceModemTime *self, modem_time_load_network_time_or_zone (MMIfaceModemTime *_self,
GAsyncReadyCallback callback, GAsyncReadyCallback callback,
gpointer user_data) gpointer user_data)
{ {
const char *command = NULL;
MMBroadbandModemHuawei *self = MM_BROADBAND_MODEM_HUAWEI (_self);
if (self->priv->nwtime_support == FEATURE_SUPPORTED)
command = "^NWTIME?";
else if (self->priv->time_support == FEATURE_SUPPORTED)
command = "^TIME";
g_assert (command != NULL);
mm_base_modem_at_command (MM_BASE_MODEM (self), mm_base_modem_at_command (MM_BASE_MODEM (self),
"^TIME", command,
3, 3,
FALSE, FALSE,
callback, callback,
@@ -3487,28 +3489,61 @@ enable_location_gathering (MMIfaceModemLocation *self,
/* Check support (Time interface) */ /* Check support (Time interface) */
static gboolean static gboolean
modem_time_check_support_finish (MMIfaceModemTime *self, modem_time_check_support_finish (MMIfaceModemTime *_self,
GAsyncResult *res, GAsyncResult *res,
GError **error) GError **error)
{ {
return !g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error); MMBroadbandModemHuawei *self = MM_BROADBAND_MODEM_HUAWEI (_self);
if (self->priv->nwtime_support == FEATURE_SUPPORTED)
return TRUE;
if (self->priv->time_support == FEATURE_SUPPORTED)
return TRUE;
return FALSE;
} }
static void static void
modem_time_check_ready (MMBroadbandModem *self, modem_time_check_ready (MMBaseModem *self,
GAsyncResult *res, GAsyncResult *res,
GSimpleAsyncResult *simple) GSimpleAsyncResult *simple)
{ {
GError *error = NULL; (void)mm_base_modem_at_sequence_finish (self, res, NULL, NULL);
mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error);
if (error)
g_simple_async_result_take_error (simple, error);
g_simple_async_result_complete (simple); g_simple_async_result_complete (simple);
g_object_unref (simple); g_object_unref (simple);
} }
static gboolean
modem_check_time_reply (MMBaseModem *_self,
gpointer none,
const gchar *command,
const gchar *response,
gboolean last_command,
const GError *error,
GVariant **result,
GError **result_error)
{
MMBroadbandModemHuawei *self = MM_BROADBAND_MODEM_HUAWEI (_self);
if (!error) {
if (strstr (response, "^NTCT"))
self->priv->nwtime_support = FEATURE_SUPPORTED;
else if (strstr (response, "^TIME"))
self->priv->time_support = FEATURE_SUPPORTED;
} else {
if (strstr (command, "^NTCT"))
self->priv->nwtime_support = FEATURE_NOT_SUPPORTED;
else if (strstr (command, "^TIME"))
self->priv->time_support = FEATURE_NOT_SUPPORTED;
}
return FALSE;
}
static const MMBaseModemAtCommand time_cmd_sequence[] = {
{ "^NTCT?", 3, FALSE, modem_check_time_reply }, /* 3GPP/LTE */
{ "^TIME", 3, FALSE, modem_check_time_reply }, /* CDMA */
{ NULL }
};
static void static void
modem_time_check_support (MMIfaceModemTime *self, modem_time_check_support (MMIfaceModemTime *self,
GAsyncReadyCallback callback, GAsyncReadyCallback callback,
@@ -3521,13 +3556,12 @@ modem_time_check_support (MMIfaceModemTime *self,
user_data, user_data,
modem_time_check_support); modem_time_check_support);
/* Only CDMA devices support this at the moment */ mm_base_modem_at_sequence (MM_BASE_MODEM (self),
mm_base_modem_at_command (MM_BASE_MODEM (self), time_cmd_sequence,
"^TIME", NULL, /* response_processor_context */
3, NULL, /* response_processor_context_free */
TRUE, (GAsyncReadyCallback)modem_time_check_ready,
(GAsyncReadyCallback)modem_time_check_ready, result);
result);
} }
/*****************************************************************************/ /*****************************************************************************/
@@ -3714,6 +3748,8 @@ mm_broadband_modem_huawei_init (MMBroadbandModemHuawei *self)
self->priv->syscfg_support = FEATURE_SUPPORT_UNKNOWN; self->priv->syscfg_support = FEATURE_SUPPORT_UNKNOWN;
self->priv->syscfgex_support = FEATURE_SUPPORT_UNKNOWN; self->priv->syscfgex_support = FEATURE_SUPPORT_UNKNOWN;
self->priv->prefmode_support = FEATURE_SUPPORT_UNKNOWN; self->priv->prefmode_support = FEATURE_SUPPORT_UNKNOWN;
self->priv->nwtime_support = FEATURE_SUPPORT_UNKNOWN;
self->priv->time_support = FEATURE_SUPPORT_UNKNOWN;
} }
static void static void
@@ -3845,8 +3881,10 @@ iface_modem_time_init (MMIfaceModemTime *iface)
{ {
iface->check_support = modem_time_check_support; iface->check_support = modem_time_check_support;
iface->check_support_finish = modem_time_check_support_finish; iface->check_support_finish = modem_time_check_support_finish;
iface->load_network_time = modem_time_load_network_time; iface->load_network_time = modem_time_load_network_time_or_zone;
iface->load_network_time_finish = modem_time_load_network_time_finish; iface->load_network_time_finish = modem_time_load_network_time_finish;
iface->load_network_timezone = modem_time_load_network_time_or_zone;
iface->load_network_timezone_finish = modem_time_load_network_timezone_finish;
} }
static void static void

View File

@@ -1033,3 +1033,154 @@ mm_huawei_parse_syscfgex_response (const gchar *response,
g_strfreev (split); g_strfreev (split);
return NULL; return NULL;
} }
/*****************************************************************************/
/* ^NWTIME response parser */
gboolean mm_huawei_parse_nwtime_response (const gchar *response,
gchar **iso8601p,
MMNetworkTimezone **tzp,
GError **error)
{
GRegex *r;
GMatchInfo *match_info = NULL;
GError *match_error = NULL;
guint year = 0, month = 0, day = 0, hour = 0, minute = 0, second = 0, dt = 0;
gint tz = 0;
gboolean ret = FALSE;
g_assert (iso8601p || tzp); /* at least one */
r = g_regex_new ("\\^NWTIME:\\s*(\\d+)/(\\d+)/(\\d+),(\\d+):(\\d+):(\\d*)([\\-\\+\\d]+),(\\d+)$", 0, 0, NULL);
g_assert (r != NULL);
if (!g_regex_match_full (r, response, -1, 0, 0, &match_info, &match_error)) {
if (match_error) {
g_propagate_error (error, match_error);
g_prefix_error (error, "Could not parse ^NWTIME results: ");
} else {
g_set_error_literal (error,
MM_CORE_ERROR,
MM_CORE_ERROR_FAILED,
"Couldn't match ^NWTIME reply");
}
} else {
/* Remember that g_match_info_get_match_count() includes match #0 */
g_assert (g_match_info_get_match_count (match_info) >= 9);
if (mm_get_uint_from_match_info (match_info, 1, &year) &&
mm_get_uint_from_match_info (match_info, 2, &month) &&
mm_get_uint_from_match_info (match_info, 3, &day) &&
mm_get_uint_from_match_info (match_info, 4, &hour) &&
mm_get_uint_from_match_info (match_info, 5, &minute) &&
mm_get_uint_from_match_info (match_info, 6, &second) &&
mm_get_int_from_match_info (match_info, 7, &tz) &&
mm_get_uint_from_match_info (match_info, 8, &dt)) {
/* adjust year */
if (year < 100)
year += 2000;
/*
* tz = timezone offset in 15 minute intervals
* dt = daylight adjustment, 0 = none, 1 = 1 hour, 2 = 2 hours
* other values are marked reserved.
*/
if (iso8601p) {
/* Return ISO-8601 format date/time string */
*iso8601p = mm_new_iso8601_time (year, month, day, hour,
minute, second,
TRUE, (tz * 15) + (dt * 60));
}
if (tzp) {
*tzp = mm_network_timezone_new ();
mm_network_timezone_set_offset (*tzp, tz * 15);
mm_network_timezone_set_dst_offset (*tzp, dt * 60);
}
ret = TRUE;
} else {
g_set_error_literal (error,
MM_CORE_ERROR,
MM_CORE_ERROR_FAILED,
"Failed to parse ^NWTIME reply");
}
}
if (match_info)
g_match_info_free (match_info);
g_regex_unref (r);
return ret;
}
/*****************************************************************************/
/* ^TIME response parser */
gboolean mm_huawei_parse_time_response (const gchar *response,
gchar **iso8601p,
MMNetworkTimezone **tzp,
GError **error)
{
GRegex *r;
GMatchInfo *match_info = NULL;
GError *match_error = NULL;
guint year, month, day, hour, minute, second;
gboolean ret = FALSE;
g_assert (iso8601p || tzp); /* at least one */
/* TIME response cannot ever provide TZ info */
if (tzp) {
g_set_error_literal (error,
MM_CORE_ERROR,
MM_CORE_ERROR_UNSUPPORTED,
"^TIME does not provide timezone information");
return FALSE;
}
/* Already in ISO-8601 format, but verify just to be sure */
r = g_regex_new ("\\^TIME:\\s*(\\d+)/(\\d+)/(\\d+)\\s*(\\d+):(\\d+):(\\d*)$", 0, 0, NULL);
g_assert (r != NULL);
if (!g_regex_match_full (r, response, -1, 0, 0, &match_info, &match_error)) {
if (match_error) {
g_propagate_error (error, match_error);
g_prefix_error (error, "Could not parse ^TIME results: ");
} else {
g_set_error_literal (error,
MM_CORE_ERROR,
MM_CORE_ERROR_FAILED,
"Couldn't match ^TIME reply");
}
} else {
/* Remember that g_match_info_get_match_count() includes match #0 */
g_assert (g_match_info_get_match_count (match_info) >= 7);
if (mm_get_uint_from_match_info (match_info, 1, &year) &&
mm_get_uint_from_match_info (match_info, 2, &month) &&
mm_get_uint_from_match_info (match_info, 3, &day) &&
mm_get_uint_from_match_info (match_info, 4, &hour) &&
mm_get_uint_from_match_info (match_info, 5, &minute) &&
mm_get_uint_from_match_info (match_info, 6, &second)) {
/* adjust year */
if (year < 100)
year += 2000;
/* Return ISO-8601 format date/time string */
if (iso8601p)
*iso8601p = mm_new_iso8601_time (year, month, day, hour,
minute, second, FALSE, 0);
ret = TRUE;
} else {
g_set_error_literal (error,
MM_CORE_ERROR,
MM_CORE_ERROR_FAILED,
"Failed to parse ^TIME reply");
}
}
if (match_info)
g_match_info_free (match_info);
g_regex_unref (r);
return ret;
}

View File

@@ -113,4 +113,20 @@ const MMHuaweiSyscfgexCombination *mm_huawei_parse_syscfgex_response (const gcha
const GArray *supported_mode_combinations, const GArray *supported_mode_combinations,
GError **error); GError **error);
/*****************************************************************************/
/* ^NWTIME response parser */
gboolean mm_huawei_parse_nwtime_response (const gchar *response,
gchar **iso8601p,
MMNetworkTimezone **tzp,
GError **error);
/*****************************************************************************/
/* ^TIME response parser */
gboolean mm_huawei_parse_time_response (const gchar *response,
gchar **iso8601p,
MMNetworkTimezone **tzp,
GError **error);
#endif /* MM_MODEM_HELPERS_HUAWEI_H */ #endif /* MM_MODEM_HELPERS_HUAWEI_H */

View File

@@ -1001,6 +1001,138 @@ test_syscfgex_response (void)
} }
} }
/*****************************************************************************/
/* Test ^NWTIME responses */
typedef struct {
const gchar *str;
gboolean ret;
gboolean test_iso8601;
gboolean test_tz;
gchar *iso8601;
gint32 offset;
gint32 dst_offset;
gint32 leap_seconds;
} NwtimeTest;
#define NWT_UNKNOWN MM_NETWORK_TIMEZONE_LEAP_SECONDS_UNKNOWN
static const NwtimeTest nwtime_tests[] = {
{ "^NWTIME: 14/08/05,04:00:21+40,00", TRUE, TRUE, FALSE,
"2014-08-05T04:00:21+10:00", 600, 0, NWT_UNKNOWN },
{ "^NWTIME: 14/08/05,04:00:21+40,00", TRUE, FALSE, TRUE,
"2014-08-05T04:00:21+10:00", 600, 0, NWT_UNKNOWN },
{ "^NWTIME: 14/08/05,04:00:21+40,00", TRUE, TRUE, TRUE,
"2014-08-05T04:00:21+10:00", 600, 0, NWT_UNKNOWN },
{ "^NWTIME: 14/08/05,04:00:21+20,00", TRUE, TRUE, FALSE,
"2014-08-05T04:00:21+05:00", 300, 0, NWT_UNKNOWN },
{ "^NWTIME: 14/08/05,04:00:21+20,00", TRUE, FALSE, TRUE,
"2014-08-05T04:00:21+05:00", 300, 0, NWT_UNKNOWN },
{ "^NWTIME: 14/08/05,04:00:21+20,00", TRUE, TRUE, TRUE,
"2014-08-05T04:00:21+05:00", 300, 0, NWT_UNKNOWN },
{ "^NWTIME: 14/08/05,04:00:21+40,01", TRUE, TRUE, FALSE,
"2014-08-05T04:00:21+11:00", 600, 60, NWT_UNKNOWN },
{ "^NWTIME: 14/08/05,04:00:21+40,01", TRUE, FALSE, TRUE,
"2014-08-05T04:00:21+11:00", 600, 60, NWT_UNKNOWN },
{ "^NWTIME: 14/08/05,04:00:21+40,01", TRUE, TRUE, TRUE,
"2014-08-05T04:00:21+11:00", 600, 60, NWT_UNKNOWN },
{ "^NWTIME: 14/08/05,04:00:21+40,02", TRUE, TRUE, FALSE,
"2014-08-05T04:00:21+12:00", 600, 120, NWT_UNKNOWN },
{ "^NWTIME: 14/08/05,04:00:21+40,02", TRUE, FALSE, TRUE,
"2014-08-05T04:00:21+12:00", 600, 120, NWT_UNKNOWN },
{ "^NWTIME: 14/08/05,04:00:21+40,02", TRUE, TRUE, TRUE,
"2014-08-05T04:00:21+12:00", 600, 120, NWT_UNKNOWN },
{ "^TIME: XX/XX/XX,XX:XX:XX+XX,XX", FALSE, TRUE, FALSE,
NULL, NWT_UNKNOWN, NWT_UNKNOWN, NWT_UNKNOWN },
{ "^TIME: 14/08/05,04:00:21+40,00", FALSE, TRUE, FALSE,
NULL, NWT_UNKNOWN, NWT_UNKNOWN, NWT_UNKNOWN },
{ NULL, FALSE, FALSE, FALSE, NULL, NWT_UNKNOWN, NWT_UNKNOWN, NWT_UNKNOWN }
};
static void
test_nwtime (void)
{
guint i;
for (i = 0; nwtime_tests[i].str; i++) {
GError *error = NULL;
gchar *iso8601 = NULL;
MMNetworkTimezone *tz = NULL;
gboolean ret;
ret = mm_huawei_parse_nwtime_response (nwtime_tests[i].str,
nwtime_tests[i].test_iso8601 ? &iso8601 : NULL,
nwtime_tests[i].test_tz ? &tz : NULL,
&error);
g_assert (ret == nwtime_tests[i].ret);
g_assert (ret == (error ? FALSE : TRUE));
g_clear_error (&error);
if (nwtime_tests[i].test_iso8601)
g_assert_cmpstr (nwtime_tests[i].iso8601, ==, iso8601);
if (nwtime_tests[i].test_tz) {
g_assert (nwtime_tests[i].offset == mm_network_timezone_get_offset (tz));
g_assert (nwtime_tests[i].dst_offset == mm_network_timezone_get_dst_offset (tz));
g_assert (nwtime_tests[i].leap_seconds == mm_network_timezone_get_leap_seconds (tz));
}
if (iso8601)
g_free (iso8601);
}
}
/*****************************************************************************/
/* Test ^TIME responses */
typedef struct {
const gchar *str;
gboolean ret;
gchar *iso8601;
} TimeTest;
static const TimeTest time_tests[] = {
{ "^TIME: 14/08/05 04:00:21", TRUE, "2014-08-05T04:00:21" },
{ "^TIME: 2014/08/05 04:00:21", TRUE, "2014-08-05T04:00:21" },
{ "^TIME: 14-08-05 04:00:21", FALSE, NULL },
{ "^TIME: 14-08-05,04:00:21", FALSE, NULL },
{ "^TIME: 14/08/05 04:00:21 AEST", FALSE, NULL },
{ NULL, FALSE, NULL }
};
static void
test_time (void)
{
guint i;
for (i = 0; time_tests[i].str; i++) {
GError *error = NULL;
gchar *iso8601 = NULL;
gboolean ret;
ret = mm_huawei_parse_time_response (time_tests[i].str,
&iso8601,
NULL,
&error);
g_assert (ret == time_tests[i].ret);
g_assert (ret == (error ? FALSE : TRUE));
g_assert_cmpstr (time_tests[i].iso8601, ==, iso8601);
if (iso8601)
g_free (iso8601);
}
}
/*****************************************************************************/ /*****************************************************************************/
void void
@@ -1039,6 +1171,8 @@ int main (int argc, char **argv)
g_test_add_func ("/MM/huawei/syscfg/response", test_syscfg_response); g_test_add_func ("/MM/huawei/syscfg/response", test_syscfg_response);
g_test_add_func ("/MM/huawei/syscfgex", test_syscfgex); g_test_add_func ("/MM/huawei/syscfgex", test_syscfgex);
g_test_add_func ("/MM/huawei/syscfgex/response", test_syscfgex_response); g_test_add_func ("/MM/huawei/syscfgex/response", test_syscfgex_response);
g_test_add_func ("/MM/huawei/nwtime", test_nwtime);
g_test_add_func ("/MM/huawei/time", test_time);
return g_test_run (); return g_test_run ();
} }