static gboolean
check_append_or_replace (MMLocationGpsNmea *self,
                         const gchar *trace)
{
    /* By default, replace */
    gboolean append_or_replace = FALSE;
    GMatchInfo *match_info = NULL;

    if (G_UNLIKELY (!self->priv->sequence_regex))
        self->priv->sequence_regex = g_regex_new ("\\$GPGSV,(\\d),(\\d).*",
                                                  G_REGEX_RAW | G_REGEX_OPTIMIZE,
                                                  0,
                                                  NULL);

    if (g_regex_match (self->priv->sequence_regex, trace, 0, &match_info)) {
        guint index;

        /* If we don't have the first element of a sequence, append */
        if (mm_get_uint_from_match_info (match_info, 2, &index) && index != 1)
            append_or_replace = TRUE;
    }
    g_match_info_free (match_info);

    return append_or_replace;
}
static void
erinfo_received (MMPortSerialAt *port,
                 GMatchInfo *info,
                 MMBroadbandModemMbm *self)
{
    MMModemAccessTechnology act = MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN;
    guint mode;

    if (mm_get_uint_from_match_info (info, 2, &mode)) {
        switch (mode) {
        case 1:
            act = MM_MODEM_ACCESS_TECHNOLOGY_GPRS;
            break;
        case 2:
            act = MM_MODEM_ACCESS_TECHNOLOGY_EDGE;
            break;
        default:
            break;
        }
    }

    /* 3G modes take precedence */
    if (mm_get_uint_from_match_info (info, 3, &mode)) {
        switch (mode) {
        case 1:
            act = MM_MODEM_ACCESS_TECHNOLOGY_UMTS;
            break;
        case 2:
            act = MM_MODEM_ACCESS_TECHNOLOGY_HSDPA;
            break;
        case 3:
            act = MM_MODEM_ACCESS_TECHNOLOGY_HSPA;
            break;
        default:
            break;
        }
    }

    mm_iface_modem_update_access_technologies (MM_IFACE_MODEM (self),
                                               act,
                                               MM_IFACE_MODEM_3GPP_ALL_ACCESS_TECHNOLOGIES_MASK);
}
static void
e2nap_received (MMPortSerialAt *port,
                GMatchInfo *info,
                MMBroadbandModemMbm *self)
{
    MMBearerList *list = NULL;
    guint state;
    BearerListReportStatusForeachContext ctx;

    if (!mm_get_uint_from_match_info (info, 1, &state))
        return;

    ctx.status = MM_BEARER_CONNECTION_STATUS_UNKNOWN;

    switch (state) {
    case MBM_E2NAP_DISCONNECTED:
        mm_dbg ("disconnected");
        ctx.status = MM_BEARER_CONNECTION_STATUS_DISCONNECTED;
        break;
    case MBM_E2NAP_CONNECTED:
        mm_dbg ("connected");
        ctx.status = MM_BEARER_CONNECTION_STATUS_CONNECTED;
        break;
    case MBM_E2NAP_CONNECTING:
        mm_dbg ("connecting");
        break;
    default:
        /* Should not happen */
        mm_dbg ("unhandled E2NAP state %d", state);
    }

    /* If unknown status, don't try to report anything */
    if (ctx.status == MM_BEARER_CONNECTION_STATUS_UNKNOWN)
        return;

    /* If empty bearer list, nothing else to do */
    g_object_get (self,
                  MM_IFACE_MODEM_BEARER_LIST, &list,
                  NULL);
    if (!list)
        return;

    mm_bearer_list_foreach (list,
                            (MMBearerListForeachFunc)bearer_list_report_status_foreach,
                            &ctx);
    g_object_unref (list);
}
static gchar *
parse_time (const gchar *response,
            const gchar *regex,
            const gchar *tag,
            GError **error)
{
    GRegex *r;
    GMatchInfo *match_info = NULL;
    GError *match_error = NULL;
    guint year, month, day, hour, minute, second;
    gchar *result = NULL;

    r = g_regex_new (regex, 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 %s results: ", tag);
        } else {
            g_set_error (error,
                         MM_CORE_ERROR,
                         MM_CORE_ERROR_FAILED,
                         "Couldn't match %s reply", tag);
        }
    } else {
        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)) {
            /* Return ISO-8601 format date/time string */
            result = g_strdup_printf ("%04d/%02d/%02d %02d:%02d:%02d",
                                      year, month, day, hour, minute, second);
        } else {
            g_set_error (error,
                         MM_CORE_ERROR,
                         MM_CORE_ERROR_FAILED,
                         "Failed to parse %s reply", tag);
        }
    }

    if (match_info)
        g_match_info_free (match_info);
    g_regex_unref (r);
    return result;
}
static gboolean
parse_nwltime_reply (const char *response,
                     gchar **out_iso_8601,
                     MMNetworkTimezone **out_tz,
                     GError **error)
{
    GRegex *r;
    GMatchInfo *match_info = NULL;
    GError *match_error = NULL;
    guint year, month, day, hour, minute, second;
    gchar *result = NULL;
    gint utc_offset = 0;
    gboolean success = FALSE;

    /* Sample reply: 2013.3.27.15.47.19.2.-5 */
    r = g_regex_new ("(\\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 $NWLTIME results: ");
        } else {
            g_set_error_literal (error,
                                 MM_CORE_ERROR,
                                 MM_CORE_ERROR_FAILED,
                                 "Couldn't match $NWLTIME 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, 8, &utc_offset)) {

            result = mm_new_iso8601_time (year, month, day, hour, minute, second,
                                          TRUE, utc_offset * 60);
            if (out_tz) {
                *out_tz = mm_network_timezone_new ();
                mm_network_timezone_set_offset (*out_tz, utc_offset * 60);
            }

            success = TRUE;
        } else {
            g_set_error_literal (error,
                                 MM_CORE_ERROR,
                                 MM_CORE_ERROR_FAILED,
                                 "Failed to parse $NWLTIME reply");
        }
    }

    if (out_iso_8601)
        *out_iso_8601 = result;
    else
        g_free (result);

    if (match_info)
        g_match_info_free (match_info);
    g_regex_unref (r);
    return success;
}
static gboolean
cache_port_mode (MMDevice *device,
                 const gchar *reply)
{
    GRegex *r = NULL;
    GRegexCompileFlags flags = G_REGEX_DOLLAR_ENDONLY | G_REGEX_RAW;
    GMatchInfo *match_info = NULL;
    GError *error = NULL;
    gboolean ret = FALSE;
    guint portcfg_current;

    /* #PORTCFG: <requested>,<active> */
    r = g_regex_new ("#PORTCFG:\\s*(\\d+),(\\d+)", flags, 0, NULL);
    g_assert (r != NULL);

    if (!g_regex_match_full (r, reply, strlen (reply), 0, 0, &match_info, &error))
        goto out;

    if (!mm_get_uint_from_match_info (match_info, 2, &portcfg_current)) {
        mm_dbg ("telit: unrecognized #PORTCFG <active> value");
        goto out;
    }

    /* Reference for port configurations:
     * HE910/UE910/UL865 Families Ports Arrangements User Guide
     */
    switch (portcfg_current) {
    case 0:
    case 1:
    case 4:
    case 5:
    case 7:
    case 9:
    case 10:
    case 11:
        g_object_set_data (G_OBJECT (device), TAG_TELIT_MODEM_PORT, "00");
        g_object_set_data (G_OBJECT (device), TAG_TELIT_AUX_PORT, "06");
        break;
    case 2:
    case 3:
    case 6:
        g_object_set_data (G_OBJECT (device), TAG_TELIT_MODEM_PORT, "00");
        break;
    case 8:
    case 12:
        g_object_set_data (G_OBJECT (device), TAG_TELIT_MODEM_PORT, "00");
        g_object_set_data (G_OBJECT (device), TAG_TELIT_AUX_PORT, "06");
        g_object_set_data (G_OBJECT (device), TAG_TELIT_NMEA_PORT, "0a");
        break;
    default:
        /* portcfg value not supported */
        goto out;
    }
    ret = TRUE;

out:
    g_match_info_free (match_info);
    g_regex_unref (r);
    if (error != NULL) {
      mm_dbg ("telit: error while matching: %s", error->message);
      g_error_free (error);
    }
    return ret;
}
static void
wwsm_read_ready (MMBaseModem *self,
                 GAsyncResult *res,
                 GSimpleAsyncResult *simple)
{
    GRegex *r;
    GMatchInfo *match_info = NULL;
    LoadAllowedModesResult result;
    const gchar *response;
    GError *error = NULL;

    response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error);
    if (!response) {
        g_simple_async_result_take_error (simple, error);
        g_simple_async_result_complete (simple);
        g_object_unref (simple);
        return;
    }

    result.allowed = MM_MODEM_MODE_NONE;
    result.preferred = MM_MODEM_MODE_NONE;

    /* Possible responses:
     *   +WWSM: 0    (2G only)
     *   +WWSM: 1    (3G only)
     *   +WWSM: 2,0  (Any)
     *   +WWSM: 2,1  (2G preferred)
     *   +WWSM: 2,2  (3G preferred)
     */
    r = g_regex_new ("\\r\\n\\+WWSM: ([0-2])(,([0-2]))?.*$", 0, 0, NULL);
    g_assert (r != NULL);

    if (g_regex_match_full (r, response, strlen (response), 0, 0, &match_info, NULL)) {
        guint allowed = 0;

        if (mm_get_uint_from_match_info (match_info, 1, &allowed)) {
            switch (allowed) {
            case 0:
                result.allowed = MM_MODEM_MODE_2G;
                result.preferred = MM_MODEM_MODE_NONE;
                break;
            case 1:
                result.allowed = MM_MODEM_MODE_3G;
                result.preferred = MM_MODEM_MODE_NONE;
                break;
            case 2: {
                guint preferred = 0;

                result.allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G);

                /* 3, to avoid the comma */
                if (mm_get_uint_from_match_info (match_info, 3, &preferred)) {
                    switch (preferred) {
                    case 0:
                        result.preferred = MM_MODEM_MODE_NONE;
                        break;
                    case 1:
                        result.preferred = MM_MODEM_MODE_2G;
                        break;
                    case 2:
                        result.preferred = MM_MODEM_MODE_3G;
                        break;
                    default:
                        g_warn_if_reached ();
                        break;
                    }
                }
                break;
            }
            default:
                g_warn_if_reached ();
                break;
            }
        }
    }

    if (result.allowed == MM_MODEM_MODE_NONE)
        g_simple_async_result_set_error (simple,
                                         MM_CORE_ERROR,
                                         MM_CORE_ERROR_FAILED,
                                         "Unknown wireless data service reply: '%s'",
                                         response);
    else
        g_simple_async_result_set_op_res_gpointer (simple, &result, NULL);
    g_simple_async_result_complete (simple);
    g_object_unref (simple);

    g_regex_unref (r);
    if (match_info)
        g_match_info_free (match_info);
}
gboolean
mm_huawei_parse_sysinfoex_response (const char *reply,
                                    guint *out_srv_status,
                                    guint *out_srv_domain,
                                    guint *out_roam_status,
                                    guint *out_sim_state,
                                    guint *out_sys_mode,
                                    guint *out_sys_submode,
                                    GError **error)
{
    gboolean matched;
    GRegex *r;
    GMatchInfo *match_info = NULL;
    GError *match_error = NULL;

    g_assert (out_srv_status != NULL);
    g_assert (out_srv_domain != NULL);
    g_assert (out_roam_status != NULL);
    g_assert (out_sim_state != NULL);
    g_assert (out_sys_mode != NULL);
    g_assert (out_sys_submode != NULL);

    /* Format:
     *
     * ^SYSINFOEX: <srv_status>,<srv_domain>,<roam_status>,<sim_state>,<reserved>,<sysmode>,<sysmode_name>,<submode>,<submode_name>
     *
     * <sysmode_name> and <submode_name> may not be quoted on some Huawei modems (e.g. E303).
     */

    /* ^SYSINFOEX:2,3,0,1,,3,"WCDMA",41,"HSPA+" */

    r = g_regex_new ("\\^SYSINFOEX:\\s*(\\d+),(\\d+),(\\d+),(\\d+),?(\\d*),(\\d+),\"?([^\"]*)\"?,(\\d+),\"?([^\"]*)\"?$", 0, 0, NULL);
    g_assert (r != NULL);

    matched = g_regex_match_full (r, reply, -1, 0, 0, &match_info, &match_error);
    if (!matched) {
        if (match_error) {
            g_propagate_error (error, match_error);
            g_prefix_error (error, "Could not parse ^SYSINFOEX results: ");
        } else {
            g_set_error_literal (error,
                                 MM_CORE_ERROR,
                                 MM_CORE_ERROR_FAILED,
                                 "Couldn't match ^SYSINFOEX reply");
        }
    } else {
        mm_get_uint_from_match_info (match_info, 1, out_srv_status);
        mm_get_uint_from_match_info (match_info, 2, out_srv_domain);
        mm_get_uint_from_match_info (match_info, 3, out_roam_status);
        mm_get_uint_from_match_info (match_info, 4, out_sim_state);

        /* We just ignore the sysmode and submode name strings */
        mm_get_uint_from_match_info (match_info, 6, out_sys_mode);
        mm_get_uint_from_match_info (match_info, 8, out_sys_submode);
    }

    if (match_info)
        g_match_info_free (match_info);
    g_regex_unref (r);
    return matched;
}
gboolean
mm_huawei_parse_ndisstatqry_response (const gchar *response,
                                      gboolean *ipv4_available,
                                      gboolean *ipv4_connected,
                                      gboolean *ipv6_available,
                                      gboolean *ipv6_connected,
                                      GError **error)
{
    GRegex *r;
    GMatchInfo *match_info;
    GError *inner_error = NULL;

    if (!response ||
        !(g_ascii_strncasecmp (response, "^NDISSTAT:", strlen ("^NDISSTAT:")) == 0 ||
          g_ascii_strncasecmp (response, "^NDISSTATQRY:", strlen ("^NDISSTATQRY:")) == 0)) {
        g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Missing ^NDISSTAT / ^NDISSTATQRY prefix");
        return FALSE;
    }

    *ipv4_available = FALSE;
    *ipv6_available = FALSE;

    /* The response maybe as:
     *     ^NDISSTAT: 1,,,IPV4
     *     ^NDISSTAT: 0,33,,IPV6
     *     ^NDISSTATQRY: 1,,,IPV4
     *     ^NDISSTATQRY: 0,33,,IPV6
     *     OK
     *
     * Or, in newer firmwares:
     *     ^NDISSTATQRY:0,,,"IPV4",0,,,"IPV6"
     *     OK
     *
     * Or, even (handled separately):
     *     ^NDISSTATQry:1
     *     OK
     */

    /* If multiple fields available, try first parsing method */
    if (strchr (response, ',')) {
        r = g_regex_new ("\\^NDISSTAT(?:QRY)?(?:Qry)?:\\s*(\\d),([^,]*),([^,]*),([^,\\r\\n]*)(?:\\r\\n)?"
                         "(?:\\^NDISSTAT:|\\^NDISSTATQRY:)?\\s*,?(\\d)?,?([^,]*)?,?([^,]*)?,?([^,\\r\\n]*)?(?:\\r\\n)?",
                         G_REGEX_DOLLAR_ENDONLY | G_REGEX_RAW,
                         0, NULL);
        g_assert (r != NULL);

        g_regex_match_full (r, response, strlen (response), 0, 0, &match_info, &inner_error);
        if (!inner_error && g_match_info_matches (match_info)) {
            guint ip_type_field = 4;

            /* IPv4 and IPv6 are fields 4 and (if available) 8 */

            while (!inner_error && ip_type_field <= 8) {
                gchar *ip_type_str;
                guint connected;

                ip_type_str = mm_get_string_unquoted_from_match_info (match_info, ip_type_field);
                if (!ip_type_str)
                    break;

                if (!mm_get_uint_from_match_info (match_info, (ip_type_field - 3), &connected) ||
                    (connected != 0 && connected != 1)) {
                    inner_error = g_error_new (MM_CORE_ERROR,
                                               MM_CORE_ERROR_FAILED,
                                               "Couldn't parse ^NDISSTAT / ^NDISSTATQRY fields");
                } else if (g_ascii_strcasecmp (ip_type_str, "IPV4") == 0) {
                    *ipv4_available = TRUE;
                    *ipv4_connected = (gboolean)connected;
                } else if (g_ascii_strcasecmp (ip_type_str, "IPV6") == 0) {
                    *ipv6_available = TRUE;
                    *ipv6_connected = (gboolean)connected;
                }

                g_free (ip_type_str);
                ip_type_field += 4;
            }
        }

        g_match_info_free (match_info);
        g_regex_unref (r);
    }
    /* No separate IPv4/IPv6 info given just connected/not connected */
    else {
        r = g_regex_new ("\\^NDISSTAT(?:QRY)?(?:Qry)?:\\s*(\\d)(?:\\r\\n)?",
                         G_REGEX_DOLLAR_ENDONLY | G_REGEX_RAW,
                         0, NULL);
        g_assert (r != NULL);

        g_regex_match_full (r, response, strlen (response), 0, 0, &match_info, &inner_error);
        if (!inner_error && g_match_info_matches (match_info)) {
            guint connected;

            if (!mm_get_uint_from_match_info (match_info, 1, &connected) ||
                (connected != 0 && connected != 1)) {
                inner_error = g_error_new (MM_CORE_ERROR,
                                           MM_CORE_ERROR_FAILED,
                                           "Couldn't parse ^NDISSTAT / ^NDISSTATQRY fields");
            } else {
                /* We'll assume IPv4 */
                *ipv4_available = TRUE;
                *ipv4_connected = (gboolean)connected;
            }
        }

        g_match_info_free (match_info);
        g_regex_unref (r);
    }

    if (!ipv4_available && !ipv6_available) {
        inner_error = g_error_new (MM_CORE_ERROR,
                                   MM_CORE_ERROR_FAILED,
                                   "Couldn't find IPv4 or IPv6 info in ^NDISSTAT / ^NDISSTATQRY response");
    }

    if (inner_error) {
        g_propagate_error (error, inner_error);
        return FALSE;
    }

    return TRUE;
}
gboolean
mm_huawei_parse_sysinfo_response (const char *reply,
                                  guint *out_srv_status,
                                  guint *out_srv_domain,
                                  guint *out_roam_status,
                                  guint *out_sys_mode,
                                  guint *out_sim_state,
                                  gboolean *out_sys_submode_valid,
                                  guint *out_sys_submode,
                                  GError **error)
{
    gboolean matched;
    GRegex *r;
    GMatchInfo *match_info = NULL;
    GError *match_error = NULL;

    g_assert (out_srv_status != NULL);
    g_assert (out_srv_domain != NULL);
    g_assert (out_roam_status != NULL);
    g_assert (out_sys_mode != NULL);
    g_assert (out_sim_state != NULL);
    g_assert (out_sys_submode_valid != NULL);
    g_assert (out_sys_submode != NULL);

    /* Format:
     *
     * ^SYSINFO: <srv_status>,<srv_domain>,<roam_status>,<sys_mode>,<sim_state>[,<reserved>,<sys_submode>]
     */

    /* Can't just use \d here since sometimes you get "^SYSINFO:2,1,0,3,1,,3" */
    r = g_regex_new ("\\^SYSINFO:\\s*(\\d+),(\\d+),(\\d+),(\\d+),(\\d+),?(\\d+)?,?(\\d+)?$", 0, 0, NULL);
    g_assert (r != NULL);

    matched = g_regex_match_full (r, reply, -1, 0, 0, &match_info, &match_error);
    if (!matched) {
        if (match_error) {
            g_propagate_error (error, match_error);
            g_prefix_error (error, "Could not parse ^SYSINFO results: ");
        } else {
            g_set_error_literal (error,
                                 MM_CORE_ERROR,
                                 MM_CORE_ERROR_FAILED,
                                 "Couldn't match ^SYSINFO reply");
        }
    } else {
        mm_get_uint_from_match_info (match_info, 1, out_srv_status);
        mm_get_uint_from_match_info (match_info, 2, out_srv_domain);
        mm_get_uint_from_match_info (match_info, 3, out_roam_status);
        mm_get_uint_from_match_info (match_info, 4, out_sys_mode);
        mm_get_uint_from_match_info (match_info, 5, out_sim_state);

        /* Remember that g_match_info_get_match_count() includes match #0 */
        if (g_match_info_get_match_count (match_info) >= 8) {
            *out_sys_submode_valid = TRUE;
            mm_get_uint_from_match_info (match_info, 7, out_sys_submode);
        }
    }

    if (match_info)
        g_match_info_free (match_info);
    g_regex_unref (r);
    return matched;
}
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;
}
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;
}
static void
selrat_query_ready (MMBaseModem *self,
                    GAsyncResult *res,
                    GSimpleAsyncResult *simple)
{
    LoadAllowedModesResult result;
    const gchar *response;
    GError *error = NULL;
    GRegex *r = NULL;
    GMatchInfo *match_info = NULL;

    response = mm_base_modem_at_command_full_finish (MM_BASE_MODEM (self), res, &error);
    if (!response) {
        g_simple_async_result_take_error (simple, error);
        g_simple_async_result_complete (simple);
        g_object_unref (simple);
        return;
    }

    /* Example response: !SELRAT: 03, UMTS 3G Preferred */
    r = g_regex_new ("!SELRAT:\\s*(\\d+).*$", 0, 0, NULL);
    g_assert (r != NULL);

    if (g_regex_match_full (r, response, strlen (response), 0, 0, &match_info, &error)) {
        guint mode;

        if (mm_get_uint_from_match_info (match_info, 1, &mode) &&
            mode >= 0 &&
            mode <= 7) {
            switch (mode) {
            case 0:
                result.allowed = MM_MODEM_MODE_ANY;
                result.preferred = MM_MODEM_MODE_NONE;
                break;
            case 1:
                result.allowed = MM_MODEM_MODE_3G;
                result.preferred = MM_MODEM_MODE_NONE;
                break;
            case 2:
                result.allowed = MM_MODEM_MODE_2G;
                result.preferred = MM_MODEM_MODE_NONE;
                break;
            case 3:
                /* in Sierra LTE devices, mode 3 is automatic, including LTE, no preference */
                if (mm_iface_modem_is_3gpp_lte (MM_IFACE_MODEM (self))) {
                    result.allowed = MM_MODEM_MODE_ANY;
                    result.preferred = MM_MODEM_MODE_NONE;
                } else {
                    result.allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G);
                    result.preferred = MM_MODEM_MODE_3G;
                }
                break;
            case 4:
                /* in Sierra LTE devices, mode 4 is automatic, including LTE, no preference */
                if (mm_iface_modem_is_3gpp_lte (MM_IFACE_MODEM (self))) {
                    result.allowed = MM_MODEM_MODE_ANY;
                    result.preferred = MM_MODEM_MODE_NONE;
                } else {
                    result.allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G);
                    result.preferred = MM_MODEM_MODE_2G;
                }
                break;
            case 5:
                result.allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G);
                result.preferred = MM_MODEM_MODE_NONE;
                break;
            case 6:
                result.allowed = MM_MODEM_MODE_4G;
                result.preferred = MM_MODEM_MODE_NONE;
                break;
            case 7:
                result.allowed = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G);
                result.preferred = MM_MODEM_MODE_NONE;
                break;
            default:
                g_assert_not_reached ();
                break;
            }
        } else
            error = g_error_new (MM_CORE_ERROR,
                                 MM_CORE_ERROR_FAILED,
                                 "Failed to parse the allowed mode response: '%s'",
                                 response);
    }

    if (error)
        g_simple_async_result_take_error (simple, error);
    else
        g_simple_async_result_set_op_res_gpointer (simple, &result, NULL);
    g_simple_async_result_complete (simple);
    g_object_unref (simple);
}