/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details: * * Copyright (C) 2008 - 2009 Novell, Inc. * Copyright (C) 2009 - 2012 Red Hat, Inc. * Copyright (C) 2012 Google, Inc. * Copyright (c) 2021 Qualcomm Innovation Center, Inc. */ #include #include #include #include #include #include #include #include #include #define _LIBMM_INSIDE_MM #include #include "mm-sms-part.h" #include "mm-common-helpers.h" #include "mm-modem-helpers.h" #include "mm-helper-enums-types.h" #include "mm-log-object.h" /*****************************************************************************/ gchar * mm_strip_quotes (gchar *str) { gsize len; if (!str) return NULL; len = strlen (str); if ((len >= 2) && (str[0] == '"') && (str[len - 1] == '"')) { str[0] = ' '; str[len - 1] = ' '; } return g_strstrip (str); } const gchar * mm_strip_tag (const gchar *str, const gchar *cmd) { const gchar *p = str; if (p) { if (!strncmp (p, cmd, strlen (cmd))) p += strlen (cmd); while (isspace (*p)) p++; } return p; } /*****************************************************************************/ gchar ** mm_split_string_groups (const gchar *str) { GPtrArray *array; const gchar *start; const gchar *next; array = g_ptr_array_new (); /* * Manually parse splitting groups. Groups may be single elements, or otherwise * lists given between parenthesis, e.g.: * * ("SM","ME"),("SM","ME"),("SM","ME") * "SM","SM","SM" * "SM",("SM","ME"),("SM","ME") */ /* Iterate string splitting groups */ for (start = str; start; start = next) { gchar *item; gssize len = -1; /* skip leading whitespaces */ while (*start == ' ') start++; if (*start == '(') { start++; next = strchr (start, ')'); if (next) { len = next - start; next = strchr (next, ','); if (next) next++; } } else { next = strchr (start, ','); if (next) { len = next - start; next++; } } if (len < 0) item = g_strdup (start); else item = g_strndup (start, len); g_ptr_array_add (array, item); } if (array->len > 0) { g_ptr_array_add (array, NULL); return (gchar **) g_ptr_array_free (array, FALSE); } g_ptr_array_unref (array); return NULL; } /*****************************************************************************/ static int uint_compare_func (gconstpointer a, gconstpointer b) { return (*(guint *)a - *(guint *)b); } GArray * mm_parse_uint_list (const gchar *str, GError **error) { GArray *array; gchar *dupstr; gchar *aux; GError *inner_error = NULL; if (!str || !str[0]) return NULL; /* Parses into a GArray of guints, the list of numbers given in the string, * also supporting number intervals. * E.g.: * 1-6 --> 1,2,3,4,5,6 * 1,2,4-6 --> 1,2,4,5,6 */ array = g_array_new (FALSE, FALSE, sizeof (guint)); aux = dupstr = g_strdup (str); while (aux) { gchar *next; gchar *interval; next = strchr (aux, ','); if (next) { *next = '\0'; next++; } interval = strchr (aux, '-'); if (interval) { guint start = 0; guint stop = 0; *interval = '\0'; interval++; if (!mm_get_uint_from_str (aux, &start)) { inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "couldn't parse interval start integer: '%s'", aux); goto out; } if (!mm_get_uint_from_str (interval, &stop)) { inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "couldn't parse interval stop integer: '%s'", interval); goto out; } if (start > stop) { inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "interval start (%u) cannot be bigger than interval stop (%u)", start, stop); goto out; } for (; start <= stop; start++) g_array_append_val (array, start); } else { guint num; if (!mm_get_uint_from_str (aux, &num)) { inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "couldn't parse integer: '%s'", aux); goto out; } g_array_append_val (array, num); } aux = next; } if (!array->len) inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "couldn't parse list of integers: '%s'", str); else g_array_sort (array, uint_compare_func); out: g_free (dupstr); if (inner_error) { g_propagate_error (error, inner_error); g_array_unref (array); return NULL; } return array; } /*****************************************************************************/ guint mm_count_bits_set (gulong number) { guint c; for (c = 0; number; c++) number &= number - 1; return c; } guint mm_find_bit_set (gulong number) { guint c = 0; for (c = 0; !(number & 0x1); c++) number >>= 1; return c; } /*****************************************************************************/ gchar * mm_create_device_identifier (guint vid, guint pid, gpointer log_object, const gchar *ati, const gchar *ati1, const gchar *gsn, const gchar *revision, const gchar *model, const gchar *manf) { g_autoptr(GString) devid = NULL; g_autoptr(GString) msg = NULL; g_autoptr(GChecksum) sum = NULL; const gchar *ret; gchar *p = NULL; gchar str_vid[10], str_pid[10]; /* Build up the device identifier */ devid = g_string_sized_new (50); if (ati) g_string_append (devid, ati); if (ati1) { /* Only append "ATI1" if it's differnet than "ATI" */ if (!ati || (strcmp (ati, ati1) != 0)) g_string_append (devid, ati1); } if (gsn) g_string_append (devid, gsn); if (revision) g_string_append (devid, revision); if (model) g_string_append (devid, model); if (manf) g_string_append (devid, manf); if (!strlen (devid->str)) return NULL; p = devid->str; msg = g_string_sized_new (strlen (devid->str) + 17); sum = g_checksum_new (G_CHECKSUM_SHA1); if (vid) { snprintf (str_vid, sizeof (str_vid) - 1, "%08x", vid); g_checksum_update (sum, (const guchar *) &str_vid[0], strlen (str_vid)); g_string_append_printf (msg, "%08x", vid); } if (pid) { snprintf (str_pid, sizeof (str_pid) - 1, "%08x", pid); g_checksum_update (sum, (const guchar *) &str_pid[0], strlen (str_pid)); g_string_append_printf (msg, "%08x", pid); } while (*p) { /* Strip spaces and linebreaks */ if (!isblank (*p) && !isspace (*p) && isascii (*p)) { g_checksum_update (sum, (const guchar *) p, 1); g_string_append_c (msg, *p); } p++; } ret = g_checksum_get_string (sum); mm_obj_dbg (log_object, "device identifier built: %s -> %s", msg->str, ret); return g_strdup (ret); } /*****************************************************************************/ guint mm_netmask_to_cidr (const gchar *netmask) { guint32 num = 0; inet_pton (AF_INET, netmask, &num); return mm_count_bits_set (num); } /*****************************************************************************/ GArray * mm_filter_current_bands (const GArray *supported_bands, const GArray *current_bands) { /* We will assure that the list given in 'current' bands maps the list * given in 'supported' bands, unless 'UNKNOWN' or 'ANY' is given, of * course */ guint i; GArray *filtered; if (!supported_bands || supported_bands->len == 0 || !current_bands || current_bands->len == 0) return NULL; if (supported_bands->len == 1 && (g_array_index (supported_bands, MMModemBand, 0) == MM_MODEM_BAND_UNKNOWN || g_array_index (supported_bands, MMModemBand, 0) == MM_MODEM_BAND_ANY)) return NULL; if (current_bands->len == 1 && (g_array_index (current_bands, MMModemBand, 0) == MM_MODEM_BAND_UNKNOWN || g_array_index (current_bands, MMModemBand, 0) == MM_MODEM_BAND_ANY)) return NULL; filtered = g_array_sized_new (FALSE, FALSE, sizeof (MMModemBand), current_bands->len); for (i = 0; i < current_bands->len; i++) { guint j; for (j = 0; j < supported_bands->len; j++) { if (g_array_index (supported_bands, MMModemBand, j) == g_array_index (current_bands, MMModemBand, i)) { g_array_append_val (filtered, g_array_index (current_bands, MMModemBand, i)); /* Found */ break; } } } if (filtered->len == 0) { g_array_unref (filtered); return NULL; } return filtered; } /*****************************************************************************/ GArray * mm_filter_supported_modes (const GArray *all, const GArray *supported_combinations, gpointer log_object) { MMModemModeCombination all_item; guint i; GArray *filtered_combinations; g_return_val_if_fail (all != NULL, NULL); g_return_val_if_fail (all->len == 1, NULL); g_return_val_if_fail (supported_combinations != NULL, NULL); mm_obj_dbg (log_object, "filtering %u supported mode combinations with %u modes", supported_combinations->len, all->len); all_item = g_array_index (all, MMModemModeCombination, 0); g_return_val_if_fail (all_item.allowed != MM_MODEM_MODE_NONE, NULL); /* We will filter out all combinations which have modes not listed in 'all' */ filtered_combinations = g_array_sized_new (FALSE, FALSE, sizeof (MMModemModeCombination), supported_combinations->len); for (i = 0; i < supported_combinations->len; i++) { MMModemModeCombination *mode; mode = &g_array_index (supported_combinations, MMModemModeCombination, i); if (!(mode->allowed & ~all_item.allowed)) { /* Compare only 'allowed', *not* preferred. If there is at least one item with allowed * containing all supported modes, we're already good to go. This allows us to have a * default with preferred != NONE (e.g. Wavecom 2G modem with allowed=CS+2G and * preferred=2G */ g_array_append_val (filtered_combinations, *mode); } } if (filtered_combinations->len == 0) mm_obj_warn (log_object, "all supported mode combinations were filtered out"); mm_obj_dbg (log_object, "device supports %u different mode combinations", filtered_combinations->len); return filtered_combinations; } /*****************************************************************************/ static const gchar bcd_chars[] = "0123456789\0\0\0\0\0\0"; gchar * mm_bcd_to_string (const guint8 *bcd, gsize bcd_len, gboolean low_nybble_first) { GString *str; gsize i; g_return_val_if_fail (bcd != NULL, NULL); str = g_string_sized_new (bcd_len * 2 + 1); for (i = 0 ; i < bcd_len; i++) { if (low_nybble_first) str = g_string_append_c (str, bcd_chars[bcd[i] & 0xF]); str = g_string_append_c (str, bcd_chars[(bcd[i] >> 4) & 0xF]); if (!low_nybble_first) str = g_string_append_c (str, bcd_chars[bcd[i] & 0xF]); } return g_string_free (str, FALSE); } /*****************************************************************************/ GRegex * mm_voice_ring_regex_get (void) { /* Example: * RING */ return g_regex_new ("\\r\\nRING(?:\\r)?\\r\\n", G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); } GRegex * mm_voice_cring_regex_get (void) { /* Example: * +CRING: VOICE * +CRING: DATA */ return g_regex_new ("\\r\\n\\+CRING:\\s*(\\S+)\\r\\n", G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); } GRegex * mm_voice_clip_regex_get (void) { /* * Only first 2 fields are mandatory: * +CLIP: ,[,,[,[][,]]] * * Example: * +CLIP: "+393351391306",145,,,,0 * \_ Number \_ Type */ return g_regex_new ("\\r\\n\\+CLIP:\\s*([^,\\s]*)\\s*,\\s*(\\d+)\\s*,?(.*)\\r\\n", G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); } GRegex * mm_voice_ccwa_regex_get (void) { /* * Only first 3 fields are mandatory, but we read only the first one * +CCWA: ,,,[][,[,,[,]]] * * Example: * +CCWA: "+393351391306",145,1 * \_ Number \_ Type */ return g_regex_new ("\\r\\n\\+CCWA:\\s*([^,\\s]*)\\s*,\\s*(\\d+)\\s*,\\s*(\\d+)\\s*,?(.*)\\r\\n", G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); } static void call_info_free (MMCallInfo *info) { if (!info) return; g_free (info->number); g_slice_free (MMCallInfo, info); } gboolean mm_3gpp_parse_clcc_response (const gchar *str, gpointer log_object, GList **out_list, GError **error) { g_autoptr(GRegex) r = NULL; g_autoptr(GMatchInfo) match_info = NULL; GList *list = NULL; GError *inner_error = NULL; static const MMCallDirection call_direction[] = { [0] = MM_CALL_DIRECTION_OUTGOING, [1] = MM_CALL_DIRECTION_INCOMING, }; static const MMCallState call_state[] = { [0] = MM_CALL_STATE_ACTIVE, [1] = MM_CALL_STATE_HELD, [2] = MM_CALL_STATE_DIALING, /* Dialing (MOC) */ [3] = MM_CALL_STATE_RINGING_OUT, /* Alerting (MOC) */ [4] = MM_CALL_STATE_RINGING_IN, /* Incoming (MTC) */ [5] = MM_CALL_STATE_WAITING, /* Waiting (MTC) */ /* This next call state number isn't defined by 3GPP, because it * doesn't make sense to have it when reporting a full list of calls * via +CLCC (i.e. the absence of the call would mean it's terminated). * But, this value may be used by other implementations (e.g. SimTech * plugin) to report that a call is terminated even when the full * call list isn't being reported. So, let's support it in the generic, * parser, even if not strictly standard. */ [6] = MM_CALL_STATE_TERMINATED, }; g_assert (out_list); /* * 1 2 3 4 5 6 7 8 9 10 * +CLCC: ,,,,[,,[,[,[,]]]] * +CLCC: ,,,,[,,[,[,[,]]]] * ... */ r = g_regex_new ("\\+CLCC:\\s*(\\d+),\\s*(\\d+),\\s*(\\d+),\\s*(\\d+),\\s*(\\d+)" /* mandatory fields */ "(?:,\\s*([^,]*),\\s*(\\d+)" /* number and type */ "(?:,\\s*([^,]*)" /* alpha */ "(?:,\\s*(\\d*)" /* priority */ "(?:,\\s*(\\d*)" /* CLI validity */ ")?)?)?)?$", G_REGEX_RAW | G_REGEX_MULTILINE | G_REGEX_NEWLINE_CRLF, G_REGEX_MATCH_NEWLINE_CRLF, NULL); g_assert (r != NULL); g_regex_match_full (r, str, strlen (str), 0, 0, &match_info, &inner_error); if (inner_error) goto out; /* Parse the results */ while (g_match_info_matches (match_info)) { MMCallInfo *call_info; guint aux; call_info = g_slice_new0 (MMCallInfo); if (!mm_get_uint_from_match_info (match_info, 1, &call_info->index)) { mm_obj_warn (log_object, "couldn't parse call index from +CLCC line"); goto next; } if (!mm_get_uint_from_match_info (match_info, 2, &aux) || (aux >= G_N_ELEMENTS (call_direction))) { mm_obj_warn (log_object, "couldn't parse call direction from +CLCC line"); goto next; } call_info->direction = call_direction[aux]; if (!mm_get_uint_from_match_info (match_info, 3, &aux) || (aux >= G_N_ELEMENTS (call_state))) { mm_obj_warn (log_object, "couldn't parse call state from +CLCC line"); goto next; } call_info->state = call_state[aux]; if (!mm_get_uint_from_match_info (match_info, 4, &aux)) { mm_obj_warn (log_object, "couldn't parse mode from +CLCC line"); goto next; } /* * Skip calls in Fax-only and DATA-only mode (3GPP TS 27.007): * 0: Voice * 1: Data * 2: Fax * 3: Voice followed by data, voice mode * 4: Alternating voice/data, voice mode * 5: Alternating voice/fax, voice mode * 6: Voice followed by data, data mode * 7: Alternating voice/data, data mode * 8: Alternating voice/fax, fax mode * 9: unknown */ if (aux != 0 && aux != 3 && aux != 4 && aux != 5) { mm_obj_dbg (log_object, "+CLCC line is not a voice call, skipping."); goto next; } if (g_match_info_get_match_count (match_info) >= 7) call_info->number = mm_get_string_unquoted_from_match_info (match_info, 6); list = g_list_append (list, call_info); call_info = NULL; next: call_info_free (call_info); g_match_info_next (match_info, NULL); } out: if (inner_error) { mm_3gpp_call_info_list_free (list); g_propagate_error (error, inner_error); return FALSE; } *out_list = list; return TRUE; } void mm_3gpp_call_info_list_free (GList *call_info_list) { g_list_free_full (call_info_list, (GDestroyNotify) call_info_free); } /*************************************************************************/ static MMFlowControl flow_control_array_to_mask (GArray *array, const gchar *item, gpointer log_object) { MMFlowControl mask = MM_FLOW_CONTROL_UNKNOWN; guint i; for (i = 0; i < array->len; i++) { guint mode; mode = g_array_index (array, guint, i); switch (mode) { case 0: mm_obj_dbg (log_object, "%s supports no flow control", item); mask |= MM_FLOW_CONTROL_NONE; break; case 1: mm_obj_dbg (log_object, "%s supports XON/XOFF flow control", item); mask |= MM_FLOW_CONTROL_XON_XOFF; break; case 2: mm_obj_dbg (log_object, "%s supports RTS/CTS flow control", item); mask |= MM_FLOW_CONTROL_RTS_CTS; break; default: break; } } return mask; } static MMFlowControl flow_control_match_info_to_mask (GMatchInfo *match_info, guint index, const gchar *item, gpointer log_object, GError **error) { MMFlowControl mask = MM_FLOW_CONTROL_UNKNOWN; gchar *aux = NULL; GArray *array = NULL; if (!(aux = mm_get_string_unquoted_from_match_info (match_info, index))) { g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Error retrieving list of supported %s flow control methods", item); goto out; } if (!(array = mm_parse_uint_list (aux, error))) { g_prefix_error (error, "Error parsing list of supported %s flow control methods: ", item); goto out; } if ((mask = flow_control_array_to_mask (array, item, log_object)) == MM_FLOW_CONTROL_UNKNOWN) { g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "No known %s flow control method given", item); goto out; } out: g_clear_pointer (&aux, g_free); g_clear_pointer (&array, g_array_unref); return mask; } MMFlowControl mm_parse_ifc_test_response (const gchar *response, gpointer log_object, GError **error) { g_autoptr(GRegex) r = NULL; g_autoptr(GMatchInfo) match_info = NULL; GError *inner_error = NULL; MMFlowControl te_mask = MM_FLOW_CONTROL_UNKNOWN; MMFlowControl ta_mask = MM_FLOW_CONTROL_UNKNOWN; MMFlowControl mask = MM_FLOW_CONTROL_UNKNOWN; r = g_regex_new ("(?:\\+IFC:)?\\s*\\((.*)\\),\\((.*)\\)(?:\\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; } /* Parse TE flow control methods */ if ((te_mask = flow_control_match_info_to_mask (match_info, 1, "TE", log_object, &inner_error)) == MM_FLOW_CONTROL_UNKNOWN) goto out; /* Parse TA flow control methods */ if ((ta_mask = flow_control_match_info_to_mask (match_info, 2, "TA", log_object, &inner_error)) == MM_FLOW_CONTROL_UNKNOWN) goto out; /* Only those methods in both TA and TE will be the ones we report */ mask = te_mask & ta_mask; out: if (inner_error) g_propagate_error (error, inner_error); return mask; } MMFlowControl mm_flow_control_from_string (const gchar *str, GError **error) { GFlagsClass *flags_class; guint value; guint i; flags_class = G_FLAGS_CLASS (g_type_class_ref (MM_TYPE_FLOW_CONTROL)); for (i = 0; flags_class->values[i].value_nick; i++) { if (!g_ascii_strcasecmp (str, flags_class->values[i].value_nick)) { value = flags_class->values[i].value; g_type_class_unref (flags_class); return value; } } g_type_class_unref (flags_class); g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_INVALID_ARGS, "Couldn't match '%s' with a valid MMFlowControl value", str); return MM_FLOW_CONTROL_UNKNOWN; } /*************************************************************************/ gboolean mm_modem_3gpp_registration_state_is_registered (MMModem3gppRegistrationState state) { switch (state) { case MM_MODEM_3GPP_REGISTRATION_STATE_HOME: case MM_MODEM_3GPP_REGISTRATION_STATE_ROAMING: case MM_MODEM_3GPP_REGISTRATION_STATE_HOME_SMS_ONLY: case MM_MODEM_3GPP_REGISTRATION_STATE_ROAMING_SMS_ONLY: case MM_MODEM_3GPP_REGISTRATION_STATE_EMERGENCY_ONLY: case MM_MODEM_3GPP_REGISTRATION_STATE_HOME_CSFB_NOT_PREFERRED: case MM_MODEM_3GPP_REGISTRATION_STATE_ROAMING_CSFB_NOT_PREFERRED: case MM_MODEM_3GPP_REGISTRATION_STATE_ATTACHED_RLOS: return TRUE; case MM_MODEM_3GPP_REGISTRATION_STATE_IDLE: case MM_MODEM_3GPP_REGISTRATION_STATE_SEARCHING: case MM_MODEM_3GPP_REGISTRATION_STATE_DENIED: case MM_MODEM_3GPP_REGISTRATION_STATE_UNKNOWN: default: return FALSE; } } /*************************************************************************/ static const gchar *creg_regex[] = { /* +CREG: (GSM 07.07 CREG=1 unsolicited) */ [0] = "\\+(CREG|CGREG|CEREG|C5GREG):\\s*0*([0-9])", /* +CREG: , (GSM 07.07 CREG=1 solicited) */ [1] = "\\+(CREG|CGREG|CEREG|C5GREG):\\s*0*([0-9]),\\s*0*([0-9])", /* +CREG: ,, (GSM 07.07 CREG=2 unsolicited) */ [2] = "\\+(CREG|CGREG|CEREG):\\s*0*([0-9]),\\s*([^,\\s]*)\\s*,\\s*([^,\\s]*)", /* +CREG: ,,, (GSM 07.07 solicited and some CREG=2 unsolicited) */ [3] = "\\+(CREG|CGREG|CEREG):\\s*([0-9]),\\s*([0-9])\\s*,\\s*([^,]*)\\s*,\\s*([^,\\s]*)", [4] = "\\+(CREG|CGREG|CEREG):\\s*0*([0-9]),\\s*0*([0-9])\\s*,\\s*(\"[^,]*\")\\s*,\\s*(\"[^,\\s]*\")", /* +CREG: ,,, (ETSI 27.007 CREG=2 unsolicited) */ [5] = "\\+(CREG|CGREG|CEREG):\\s*([0-9])\\s*,\\s*([^,\\s]*)\\s*,\\s*([^,\\s]*)\\s*,\\s*([0-9])", [6] = "\\+(CREG|CGREG|CEREG):\\s*0*([0-9])\\s*,\\s*(\"[^,\\s]*\")\\s*,\\s*(\"[^,\\s]*\")\\s*,\\s*0*([0-9])", /* +CREG: ,,,, (ETSI 27.007 solicited and some CREG=2 unsolicited) */ [7] = "\\+(CREG|CGREG|CEREG):\\s*0*([0-9]),\\s*0*([0-9])\\s*,\\s*([^,\\s]*)\\s*,\\s*([^,\\s]*)\\s*,\\s*0*([0-9])", /* +CREG: ,,,,, (Samsung Wave S8500) */ /* '+CREG: 2,1,000B,2816, B, C2816OK' */ [8] = "\\+(CREG|CGREG):\\s*0*([0-9]),\\s*0*([0-9])\\s*,\\s*([^,\\s]*)\\s*,\\s*([^,\\s]*)\\s*,\\s*([^,\\s]*)\\s*,\\s*[^,\\s]*", /* +CREG: ,,,, (ETSI 27.007 v9.20 CREG=2 unsolicited with RAC) */ [9] = "\\+(CREG|CGREG):\\s*0*([0-9])\\s*,\\s*([^,\\s]*)\\s*,\\s*([^,\\s]*)\\s*,\\s*0*([0-9])\\s*,\\s*([^,\\s]*)", /* +CEREG: ,,,, (ETSI 27.007 v8.6 CREG=2 unsolicited with RAC) */ [10] = "\\+(CEREG):\\s*0*([0-9])\\s*,\\s*([^,\\s]*)\\s*,\\s*([^,\\s]*)\\s*,\\s*([^,\\s]*)\\s*,\\s*0*([0-9])", /* +CEREG: ,,,,, (ETSI 27.007 v8.6 CREG=2 solicited with RAC) */ [11] = "\\+(CEREG):\\s*0*([0-9]),\\s*0*([0-9])\\s*,\\s*([^,\\s]*)\\s*,\\s*([^,\\s]*)\\s*,\\s*([^,\\s]*)\\s*,\\s*0*([0-9])", /* +C5GREG: ,,,,, (ETSI 27.007 CREG=2 unsolicited) */ [12] = "\\+(C5GREG):\\s*([0-9]+)\\s*,\\s*([^,\\s]*)\\s*,\\s*([^,\\s]*)\\s*,\\s*([0-9]+)\\s*,\\s*([^,\\s]*)\\s*,\\s*([^,\\s]*)", /* +C5GREG: ,,,,,, (ETSI 27.007 solicited) */ [13] = "\\+(C5GREG):\\s*([0-9]+)\\s*,\\s*([0-9+])\\s*,\\s*([^,\\s]*)\\s*,\\s*([^,\\s]*)\\s*,\\s*([0-9]+)\\s*,\\s*([^,\\s]*)\\s*,\\s*([^,\\s]*)", }; GPtrArray * mm_3gpp_creg_regex_get (gboolean solicited) { GPtrArray *array; guint i; array = g_ptr_array_sized_new (G_N_ELEMENTS (creg_regex)); for (i = 0; i < G_N_ELEMENTS (creg_regex); i++) { GRegex *regex; g_autofree gchar *pattern = NULL; if (solicited) { pattern = g_strdup_printf ("%s$", creg_regex[i]); regex = g_regex_new (pattern, G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); } else { pattern = g_strdup_printf ("\\r\\n%s\\r\\n", creg_regex[i]); regex = g_regex_new (pattern, G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); } g_assert (regex); g_ptr_array_add (array, regex); } return array; } void mm_3gpp_creg_regex_destroy (GPtrArray *array) { g_ptr_array_foreach (array, (GFunc) g_regex_unref, NULL); g_ptr_array_free (array, TRUE); } /*************************************************************************/ GRegex * mm_3gpp_ciev_regex_get (void) { return g_regex_new ("\\r\\n\\+CIEV: (.*),(\\d)\\r\\n", G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); } /*************************************************************************/ GRegex * mm_3gpp_cgev_regex_get (void) { return g_regex_new ("\\r\\n\\+CGEV:\\s*(.*)\\r\\n", G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); } /*************************************************************************/ GRegex * mm_3gpp_cusd_regex_get (void) { return g_regex_new ("\\r\\n\\+CUSD:\\s*(.*)\\r\\n", G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); } /*************************************************************************/ GRegex * mm_3gpp_cmti_regex_get (void) { return g_regex_new ("\\r\\n\\+CMTI:\\s*\"(\\S+)\",\\s*(\\d+)\\r\\n", G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); } GRegex * mm_3gpp_cds_regex_get (void) { /* Example: * +CDS: 2407914356060013F10659098136395339F6219011707193802190117071938030 */ return g_regex_new ("\\r\\n\\+CDS:\\s*(\\d+)\\r\\n(.*)\\r\\n", G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); } /*************************************************************************/ /* AT+WS46=? response parser * * More than one numeric ID may appear in the list, that's why * they are checked separately. * * NOTE: ignore WS46 prefix or it will break Cinterion handling. * * For the specific case of '25', we will check if any other mode supports * 4G, and if there is none, we'll remove 4G caps from it. This is needed * because pre-LTE modems used '25' to report GERAN+URAN instead of the * new '29' value since LTE modems are around. */ typedef struct { guint ws46; MMModemMode mode; } Ws46Mode; /* 3GPP TS 27.007 v16.3.0, section 5.9: select wireless network +WS46 */ static const Ws46Mode ws46_modes[] = { /* GSM Digital Cellular Systems (GERAN only) */ { 12, MM_MODEM_MODE_2G }, /* UTRAN only */ { 22, MM_MODEM_MODE_3G }, /* 3GPP Systems (GERAN, UTRAN and E-UTRAN) */ { 25, MM_MODEM_MODE_ANY }, /* E-UTRAN only */ { 28, MM_MODEM_MODE_4G }, /* GERAN and UTRAN */ { 29, MM_MODEM_MODE_2G | MM_MODEM_MODE_3G }, /* GERAN and E-UTRAN */ { 30, MM_MODEM_MODE_2G | MM_MODEM_MODE_4G }, /* UTRAN and E-UTRAN */ { 31, MM_MODEM_MODE_3G | MM_MODEM_MODE_4G }, /* GERAN, UTRAN, E-UTRAN and NG-RAN */ { 35, MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G | MM_MODEM_MODE_5G }, /* NG-RAN only */ { 36, MM_MODEM_MODE_5G }, /* E-UTRAN and NG-RAN */ { 37, MM_MODEM_MODE_4G | MM_MODEM_MODE_5G }, /* UTRAN, E-UTRAN and NG-RAN */ { 38, MM_MODEM_MODE_3G | MM_MODEM_MODE_4G | MM_MODEM_MODE_5G }, /* GERAN, E-UTRAN and NG-RAN */ { 39, MM_MODEM_MODE_2G | MM_MODEM_MODE_4G | MM_MODEM_MODE_5G }, /* UTRAN and NG-RAN */ { 40, MM_MODEM_MODE_3G | MM_MODEM_MODE_5G }, /* GERAN, UTRAN and NG-RAN */ { 41, MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_5G }, /* GERAN and NG-RAN */ { 42, MM_MODEM_MODE_2G | MM_MODEM_MODE_5G }, }; GArray * mm_3gpp_parse_ws46_test_response (const gchar *response, gpointer log_object, GError **error) { g_autoptr(GRegex) r = NULL; g_autoptr(GMatchInfo) match_info = NULL; GArray *modes = NULL; GArray *tech_values = NULL; GError *inner_error = NULL; gchar *full_list = NULL; guint val; guint i; guint j; gboolean supported_5g = FALSE; gboolean supported_4g = FALSE; gboolean supported_3g = FALSE; gboolean supported_2g = FALSE; gboolean supported_mode_25 = FALSE; gboolean supported_mode_29 = FALSE; r = g_regex_new ("(?:\\+WS46:)?\\s*\\((.*)\\)(?:\\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 (!(full_list = mm_get_string_unquoted_from_match_info (match_info, 1))) { inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Error parsing full list string"); goto out; } if (!(tech_values = mm_parse_uint_list (full_list, &inner_error))) goto out; modes = g_array_new (FALSE, FALSE, sizeof (MMModemMode)); for (i = 0; i < tech_values->len; i++) { val = g_array_index (tech_values, guint, i); for (j = 0; j < G_N_ELEMENTS (ws46_modes); j++) { if (ws46_modes[j].ws46 == val) { if (val == 25) supported_mode_25 = TRUE; else { if (val == 29) supported_mode_29 = TRUE; if (ws46_modes[j].mode & MM_MODEM_MODE_5G) supported_5g = TRUE; if (ws46_modes[j].mode & MM_MODEM_MODE_4G) supported_4g = TRUE; if (ws46_modes[j].mode & MM_MODEM_MODE_3G) supported_3g = TRUE; if (ws46_modes[j].mode & MM_MODEM_MODE_2G) supported_2g = TRUE; g_array_append_val (modes, ws46_modes[j].mode); } break; } } if (j == G_N_ELEMENTS (ws46_modes)) mm_obj_warn (log_object, "Unknown +WS46 mode reported: %u", val); } if (supported_mode_25) { MMModemMode mode_25; mode_25 = MM_MODEM_MODE_2G | MM_MODEM_MODE_3G; if (supported_4g) { mode_25 |= MM_MODEM_MODE_4G; g_array_append_val (modes, mode_25); } else if (!supported_mode_29) g_array_append_val (modes, mode_25); } if (modes->len == 0) { inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "No valid modes reported"); g_clear_pointer (&modes, g_array_unref); goto out; } /* Fixup the ANY value, based on which are the supported modes */ for (i = 0; i < modes->len; i++) { MMModemMode *mode; mode = &g_array_index (modes, MMModemMode, i); if (*mode == MM_MODEM_MODE_ANY) { *mode = 0; if (supported_2g) *mode |= MM_MODEM_MODE_2G; if (supported_3g) *mode |= MM_MODEM_MODE_3G; if (supported_4g) *mode |= MM_MODEM_MODE_4G; if (supported_5g) *mode |= MM_MODEM_MODE_5G; if (*mode == 0) { inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "No way to fixup the ANY value"); g_clear_pointer (&modes, g_array_unref); goto out; } } } out: if (tech_values) g_array_unref (tech_values); g_free (full_list); if (inner_error) { g_propagate_error (error, inner_error); return NULL; } g_assert (modes && modes->len); return modes; } /*************************************************************************/ static void mm_3gpp_network_info_free (MM3gppNetworkInfo *info) { g_free (info->operator_long); g_free (info->operator_short); g_free (info->operator_code); g_free (info); } void mm_3gpp_network_info_list_free (GList *info_list) { g_list_free_full (info_list, (GDestroyNotify) mm_3gpp_network_info_free); } static MMModem3gppNetworkAvailability get_mm_network_availability_from_3gpp_network_availability (guint val, gpointer log_object) { /* The network availability status in the MM API and in the 3GPP specs take the * same numeric values, but we map them with an enum as it's much safer if new * values are defined by 3GPP in the future. */ switch (val) { case 1: return MM_MODEM_3GPP_NETWORK_AVAILABILITY_AVAILABLE; case 2: return MM_MODEM_3GPP_NETWORK_AVAILABILITY_CURRENT; case 3: return MM_MODEM_3GPP_NETWORK_AVAILABILITY_FORBIDDEN; default: break; } mm_obj_warn (log_object, "unknown network availability value: %u", val); return MM_MODEM_3GPP_NETWORK_AVAILABILITY_UNKNOWN; } static MMModemAccessTechnology get_mm_access_tech_from_etsi_access_tech (guint val, gpointer log_object) { /* See ETSI TS 27.007 */ switch (val) { case 0: /* GSM */ case 8: /* EC-GSM-IoT (A/Gb mode) */ return MM_MODEM_ACCESS_TECHNOLOGY_GSM; case 1: return MM_MODEM_ACCESS_TECHNOLOGY_GSM_COMPACT; case 2: return MM_MODEM_ACCESS_TECHNOLOGY_UMTS; case 3: return MM_MODEM_ACCESS_TECHNOLOGY_EDGE; case 4: return MM_MODEM_ACCESS_TECHNOLOGY_HSDPA; case 5: return MM_MODEM_ACCESS_TECHNOLOGY_HSUPA; case 6: return MM_MODEM_ACCESS_TECHNOLOGY_HSPA; case 7: /* E-UTRAN */ case 9: /* E-UTRAN (NB-S1) */ case 10: /* E-UTRA connected to a 5GCN */ return MM_MODEM_ACCESS_TECHNOLOGY_LTE; case 11: /* NR connected to a 5G CN */ case 12: /* NG-RAN */ return MM_MODEM_ACCESS_TECHNOLOGY_5GNR; case 13: /* E-UTRA-NR dual connectivity */ return (MM_MODEM_ACCESS_TECHNOLOGY_5GNR | MM_MODEM_ACCESS_TECHNOLOGY_LTE); default: break; } mm_obj_warn (log_object, "unknown access technology value: %u", val); return MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN; } GList * mm_3gpp_parse_cops_test_response (const gchar *reply, MMModemCharset cur_charset, gpointer log_object, GError **error) { g_autoptr(GRegex) r = NULL; g_autoptr(GMatchInfo) match_info = NULL; GList *info_list = NULL; gboolean umts_format = TRUE; g_return_val_if_fail (reply != NULL, NULL); if (error) g_return_val_if_fail (*error == NULL, NULL); if (!strstr (reply, "+COPS: ")) { g_set_error_literal (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Could not parse scan results."); return NULL; } reply = strstr (reply, "+COPS: ") + 7; /* Cell access technology (GSM, UTRAN, etc) got added later and not all * modems implement it. Some modesm have quirks that make it hard to * use one regular experession for matching both pre-UMTS and UMTS * responses. So try UMTS-format first and fall back to pre-UMTS if * we get no UMTS-formst matches. */ /* Quirk: Sony-Ericsson TM-506 sometimes includes a stray ')' like so, * which is what makes it hard to match both pre-UMTS and UMTS in * the same regex: * * +COPS: (2,"","T-Mobile","31026",0),(1,"AT&T","AT&T","310410"),0) */ r = g_regex_new ("\\((\\d),\"([^\"\\)]*)\",([^,\\)]*),([^,\\)]*)[\\)]?,(\\d+)\\)", G_REGEX_UNGREEDY, 0, NULL); g_assert (r); /* If we didn't get any hits, try the pre-UMTS format match */ if (!g_regex_match (r, reply, 0, &match_info)) { g_clear_pointer (&r, g_regex_unref); g_clear_pointer (&match_info, g_match_info_free); /* Pre-UMTS format doesn't include the cell access technology after * the numeric operator element. * * Ex: Motorola C-series (BUSlink SCWi275u) like so: * * +COPS: (2,"T-Mobile","","310260"),(0,"Cingular Wireless","","310410") */ /* Quirk: Some Nokia phones (N80) don't send the quotes for empty values: * * +COPS: (2,"T - Mobile",,"31026"),(1,"Einstein PCS",,"31064"),(1,"Cingular",,"31041"),,(0,1,3),(0,2) */ r = g_regex_new ("\\((\\d),([^,\\)]*),([^,\\)]*),([^\\)]*)\\)", G_REGEX_UNGREEDY, 0, NULL); g_assert (r); g_regex_match (r, reply, 0, &match_info); umts_format = FALSE; } /* Parse the results */ while (g_match_info_matches (match_info)) { MM3gppNetworkInfo *info; guint net_value = 0; gboolean valid = FALSE; info = g_new0 (MM3gppNetworkInfo, 1); /* the regex makes sure this is a number, it won't fail */ mm_get_uint_from_match_info (match_info, 1, &net_value); info->status = get_mm_network_availability_from_3gpp_network_availability (net_value, log_object); info->operator_long = mm_get_string_unquoted_from_match_info (match_info, 2); info->operator_short = mm_get_string_unquoted_from_match_info (match_info, 3); info->operator_code = mm_get_string_unquoted_from_match_info (match_info, 4); /* The returned strings may be given in e.g. UCS2 */ mm_3gpp_normalize_operator (&info->operator_long, cur_charset, log_object); mm_3gpp_normalize_operator (&info->operator_short, cur_charset, log_object); mm_3gpp_normalize_operator (&info->operator_code, cur_charset, log_object); /* Only try for access technology with UMTS-format matches. * If none give, assume GSM */ if (umts_format) { guint act_value = 0; /* the regex makes sure this is a number, it won't fail */ mm_get_uint_from_match_info (match_info, 5, &act_value); info->access_tech = get_mm_access_tech_from_etsi_access_tech (act_value, log_object); } else info->access_tech = MM_MODEM_ACCESS_TECHNOLOGY_GSM; /* If the operator number isn't valid (ie, at least 5 digits), * ignore the scan result; it's probably the parameter stuff at the * end of the +COPS response. The regex will sometimes catch this * but there's no good way to ignore it. */ if (info->operator_code && (strlen (info->operator_code) >= 5)) { gchar *tmp; valid = TRUE; tmp = info->operator_code; while (*tmp) { if (!isdigit (*tmp) && (*tmp != '-')) { valid = FALSE; break; } tmp++; } } if (valid) { g_autofree gchar *access_tech_str = NULL; access_tech_str = mm_modem_access_technology_build_string_from_mask (info->access_tech); mm_obj_dbg (log_object, "found network '%s' ('%s','%s'); availability: %s, access tech: %s", info->operator_code, info->operator_short ? info->operator_short : "no short name", info->operator_long ? info->operator_long : "no long name", mm_modem_3gpp_network_availability_get_string (info->status), access_tech_str); info_list = g_list_prepend (info_list, info); } else mm_3gpp_network_info_free (info); g_match_info_next (match_info, NULL); } return info_list; } /*************************************************************************/ gboolean mm_3gpp_parse_cops_read_response (const gchar *response, guint *out_mode, guint *out_format, gchar **out_operator, MMModemAccessTechnology *out_act, gpointer log_object, GError **error) { g_autoptr(GRegex) r = NULL; g_autoptr(GMatchInfo) match_info = NULL; 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: ,, * or: * +COPS: ,,, */ 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, log_object); } out: 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; } /*************************************************************************/ /* Logic to compare two APN names */ gboolean mm_3gpp_cmp_apn_name (const gchar *requested, const gchar *existing) { size_t requested_len; size_t existing_len; /* If both empty, that's a good match */ if ((!existing || !existing[0]) && (!requested || !requested[0])) return TRUE; /* Both must be given to compare properly */ if (!existing || !existing[0] || !requested || !requested[0]) return FALSE; requested_len = strlen (requested); /* * 1) The requested APN should be at least the prefix of the existing one. */ if (g_ascii_strncasecmp (existing, requested, requested_len) != 0) return FALSE; /* * 2) If the existing one is actually the same as the requested one (i.e. * there are no more different chars in the existing one), we're done. */ if (existing[requested_len] == '\0') return TRUE; existing_len = strlen (existing); /* 3) Special handling for PDP contexts reported by u-blox modems once the * contexts have been activated at least once: * "ac.vodafone.es.MNC001.MCC214.GPRS" should match "ac.vodafone.es" */ if ((existing_len > (requested_len + 14)) && g_ascii_strncasecmp (&existing[requested_len], ".mnc", 4) == 0 && g_ascii_strncasecmp (&existing[requested_len + 7], ".mcc", 4) == 0) return TRUE; /* No match */ return FALSE; } /*************************************************************************/ gboolean mm_3gpp_pdp_context_format_list_find_range (GList *pdp_format_list, MMBearerIpFamily ip_family, guint *out_min_cid, guint *out_max_cid) { GList *l; for (l = pdp_format_list; l; l = g_list_next (l)) { MM3gppPdpContextFormat *format = l->data; /* Found exact PDP type? */ if (format->pdp_type == ip_family) { if (out_min_cid) *out_min_cid = format->min_cid; if (out_max_cid) *out_max_cid = format->max_cid; return TRUE; } } return FALSE; } /*************************************************************************/ MM3gppProfile * mm_3gpp_profile_new_from_pdp_context (MM3gppPdpContext *pdp_context) { MM3gppProfile *profile; profile = mm_3gpp_profile_new (); mm_3gpp_profile_set_profile_id (profile, pdp_context->cid); mm_3gpp_profile_set_apn (profile, pdp_context->apn); mm_3gpp_profile_set_ip_type (profile, pdp_context->pdp_type); return profile; } GList * mm_3gpp_profile_list_new_from_pdp_context_list (GList *pdp_context_list) { GList *profile_list = NULL; GList *l; for (l = pdp_context_list; l; l = g_list_next (l)) { MM3gppPdpContext *pdp_context; MM3gppProfile *profile; pdp_context = (MM3gppPdpContext *)l->data; profile = mm_3gpp_profile_new_from_pdp_context (pdp_context); profile_list = g_list_append (profile_list, profile); } return profile_list; } void mm_3gpp_profile_list_free (GList *profile_list) { g_list_free_full (profile_list, g_object_unref); } MM3gppProfile * mm_3gpp_profile_list_find_by_profile_id (GList *profile_list, gint profile_id, GError **error) { GList *l; for (l = profile_list; l; l = g_list_next (l)) { MM3gppProfile *iter_profile = l->data; if (mm_3gpp_profile_get_profile_id (iter_profile) == profile_id) return g_object_ref (iter_profile); } g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_NOT_FOUND, "Profile '%d' not found", profile_id); return NULL; } MM3gppProfile * mm_3gpp_profile_list_find_by_apn_type (GList *profile_list, MMBearerApnType apn_type, GError **error) { g_autofree gchar *apn_type_str = NULL; GList *l; for (l = profile_list; l; l = g_list_next (l)) { MM3gppProfile *iter_profile = l->data; if (mm_3gpp_profile_get_apn_type (iter_profile) == apn_type) return g_object_ref (iter_profile); } apn_type_str = mm_bearer_apn_type_build_string_from_mask (apn_type); g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_NOT_FOUND, "Profile '%s' not found", apn_type_str); return NULL; } gint mm_3gpp_profile_list_find_empty (GList *profile_list, gint min_profile_id, gint max_profile_id, GError **error) { GList *l; gint profile_id; profile_id = min_profile_id; for (l = profile_list; l; l = g_list_next (l)) { MM3gppProfile *iter_profile = l->data; gint iter_profile_id; iter_profile_id = mm_3gpp_profile_get_profile_id (iter_profile); if (iter_profile_id > profile_id) break; if (iter_profile_id == profile_id) profile_id++; } if (profile_id > max_profile_id) { g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_NOT_FOUND, "No empty profile available"); return MM_3GPP_PROFILE_ID_UNKNOWN; } return profile_id; } gint mm_3gpp_profile_list_find_best (GList *profile_list, MM3gppProfile *requested, GEqualFunc cmp_apn, MM3gppProfileCmpFlags cmp_flags, gint min_profile_id, gint max_profile_id, gpointer log_object, MM3gppProfile **out_reused, gboolean *out_overwritten) { GList *l; MMBearerIpFamily requested_ip_type; gint prev_profile_id = 0; gint unused_profile_id = 0; gint max_found_profile_id = 0; gint max_allowed_profile_id = 0; gint blank_profile_id = 0; g_assert (out_reused); g_assert (out_overwritten); requested_ip_type = mm_3gpp_profile_get_ip_type (requested); /* When looking for exact profile matches we should not compare * the profile id, as the requested profile won't have one set */ cmp_flags |= MM_3GPP_PROFILE_CMP_FLAGS_NO_PROFILE_ID; /* Look for the exact PDP context we want */ for (l = profile_list; l; l = g_list_next (l)) { MM3gppProfile *iter_profile = l->data; MMBearerIpFamily iter_ip_type; const gchar *iter_apn; gint iter_profile_id; iter_profile_id = mm_3gpp_profile_get_profile_id (iter_profile); if (iter_profile_id < min_profile_id) { mm_obj_dbg (log_object, "skipping context at profile %d: out of bounds", iter_profile_id); continue; } /* Always prefer an exact match; compare all supported fields except for profile id */ if (mm_3gpp_profile_cmp (iter_profile, requested, cmp_apn, cmp_flags)) { mm_obj_dbg (log_object, "found exact context at profile %d", iter_profile_id); *out_reused = g_object_ref (iter_profile); *out_overwritten = FALSE; return iter_profile_id; } /* Same PDP type but with no APN set? we may use that one if no exact match found */ iter_ip_type = mm_3gpp_profile_get_ip_type (iter_profile); iter_apn = mm_3gpp_profile_get_apn (iter_profile); if ((iter_ip_type == requested_ip_type) && (!iter_apn || !iter_apn[0]) && !blank_profile_id) blank_profile_id = iter_profile_id; /* 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_profile_id && prev_profile_id && ((prev_profile_id + 1) < iter_profile_id)) unused_profile_id = prev_profile_id + 1; /* Update previous CID value to the current CID for use in the next loop, * unless an unused CID was already found. */ if (!unused_profile_id) prev_profile_id = iter_profile_id; /* Update max CID if we found a bigger one */ if (max_found_profile_id < iter_profile_id) max_found_profile_id = iter_profile_id; } /* Try to use an unused CID detected in between the already defined contexts */ if (unused_profile_id) { mm_obj_dbg (log_object, "found unused profile %d", unused_profile_id); *out_reused = NULL; *out_overwritten = FALSE; return unused_profile_id; } /* 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_profile_id = max_profile_id; if (max_found_profile_id && (max_found_profile_id < max_allowed_profile_id)) { mm_obj_dbg (log_object, "found unused profile %d (<%d)", max_found_profile_id + 1, max_allowed_profile_id); *out_reused = NULL; *out_overwritten = FALSE; return (max_found_profile_id + 1); } /* Rewrite a context defined with no APN, if any */ if (blank_profile_id) { mm_obj_dbg (log_object, "rewriting profile %d with empty APN", blank_profile_id); *out_reused = NULL; *out_overwritten = TRUE; return blank_profile_id; } /* Rewrite the last existing one found */ if (max_found_profile_id) { mm_obj_dbg (log_object, "rewriting last profile %d detected", max_found_profile_id); *out_reused = NULL; *out_overwritten = TRUE; return max_found_profile_id; } /* Otherwise, just fallback to min CID */ mm_obj_dbg (log_object, "falling back to profile %d", min_profile_id); *out_reused = NULL; *out_overwritten = TRUE; return min_profile_id; } /*************************************************************************/ static void mm_3gpp_pdp_context_format_free (MM3gppPdpContextFormat *format) { g_slice_free (MM3gppPdpContextFormat, format); } void mm_3gpp_pdp_context_format_list_free (GList *pdp_format_list) { g_list_free_full (pdp_format_list, (GDestroyNotify) mm_3gpp_pdp_context_format_free); } GList * mm_3gpp_parse_cgdcont_test_response (const gchar *response, gpointer log_object, GError **error) { g_autoptr(GRegex) r = NULL; g_autoptr(GMatchInfo) match_info = NULL; GError *inner_error = NULL; GList *list = NULL; if (!response || !g_str_has_prefix (response, "+CGDCONT:")) { g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Missing +CGDCONT prefix"); return NULL; } r = g_regex_new ("\\+CGDCONT:\\s*\\(\\s*(\\d+)\\s*-?\\s*(\\d+)?[^\\)]*\\)\\s*,\\s*\\(?\"(\\S+)\"", G_REGEX_DOLLAR_ENDONLY | G_REGEX_RAW, 0, &inner_error); g_assert (r != NULL); g_regex_match_full (r, response, strlen (response), 0, 0, &match_info, &inner_error); while (!inner_error && g_match_info_matches (match_info)) { gchar *pdp_type_str; guint min_cid; guint max_cid; MMBearerIpFamily pdp_type; /* Read PDP type */ pdp_type_str = mm_get_string_unquoted_from_match_info (match_info, 3); pdp_type = mm_3gpp_get_ip_family_from_pdp_type (pdp_type_str); if (pdp_type == MM_BEARER_IP_FAMILY_NONE) mm_obj_dbg (log_object, "unhandled PDP type in CGDCONT=? reply: '%s'", pdp_type_str); else { /* Read min CID */ if (!mm_get_uint_from_match_info (match_info, 1, &min_cid)) mm_obj_warn (log_object, "invalid min CID in CGDCONT=? reply for PDP type '%s'", pdp_type_str); else { MM3gppPdpContextFormat *format; /* Read max CID: Optional! If no value given, we default to min CID */ if (!mm_get_uint_from_match_info (match_info, 2, &max_cid)) max_cid = min_cid; format = g_slice_new (MM3gppPdpContextFormat); format->pdp_type = pdp_type; format->min_cid = min_cid; format->max_cid = max_cid; list = g_list_prepend (list, format); } } g_free (pdp_type_str); g_match_info_next (match_info, &inner_error); } if (inner_error) { mm_obj_warn (log_object, "unexpected error matching +CGDCONT response: '%s'", inner_error->message); g_error_free (inner_error); } return list; } /*************************************************************************/ static void mm_3gpp_pdp_context_free (MM3gppPdpContext *pdp) { g_free (pdp->apn); g_slice_free (MM3gppPdpContext, pdp); } void mm_3gpp_pdp_context_list_free (GList *list) { g_list_free_full (list, (GDestroyNotify) mm_3gpp_pdp_context_free); } static gint mm_3gpp_pdp_context_cmp (MM3gppPdpContext *a, MM3gppPdpContext *b) { return (a->cid - b->cid); } GList * mm_3gpp_parse_cgdcont_read_response (const gchar *reply, GError **error) { GError *inner_error = NULL; g_autoptr(GRegex) r = NULL; g_autoptr(GMatchInfo) match_info = NULL; GList *list = NULL; if (!reply || !reply[0]) /* No APNs configured, all done */ return NULL; r = g_regex_new ("\\+CGDCONT:\\s*(\\d+)\\s*,([^, \\)]*)\\s*,([^, \\)]*)\\s*,([^, \\)]*)", G_REGEX_DOLLAR_ENDONLY | G_REGEX_RAW, 0, NULL); g_assert (r); g_regex_match_full (r, reply, strlen (reply), 0, 0, &match_info, &inner_error); while (!inner_error && g_match_info_matches (match_info)) { gchar *str; MMBearerIpFamily ip_family; str = mm_get_string_unquoted_from_match_info (match_info, 2); ip_family = mm_3gpp_get_ip_family_from_pdp_type (str); if (ip_family != MM_BEARER_IP_FAMILY_NONE) { MM3gppPdpContext *pdp; pdp = g_slice_new0 (MM3gppPdpContext); if (!mm_get_uint_from_match_info (match_info, 1, &pdp->cid)) { inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Couldn't parse CID from reply: '%s'", reply); break; } pdp->pdp_type = ip_family; pdp->apn = mm_get_string_unquoted_from_match_info (match_info, 3); list = g_list_prepend (list, pdp); } g_free (str); g_match_info_next (match_info, &inner_error); } if (inner_error) { mm_3gpp_pdp_context_list_free (list); g_propagate_error (error, inner_error); g_prefix_error (error, "Couldn't properly parse list of PDP contexts. "); return NULL; } return g_list_sort (list, (GCompareFunc)mm_3gpp_pdp_context_cmp); } /*************************************************************************/ static void mm_3gpp_pdp_context_active_free (MM3gppPdpContextActive *pdp_active) { g_slice_free (MM3gppPdpContextActive, pdp_active); } void mm_3gpp_pdp_context_active_list_free (GList *pdp_active_list) { g_list_free_full (pdp_active_list, (GDestroyNotify) mm_3gpp_pdp_context_active_free); } gint mm_3gpp_pdp_context_active_cmp (MM3gppPdpContextActive *a, MM3gppPdpContextActive *b) { return (a->cid - b->cid); } GList * mm_3gpp_parse_cgact_read_response (const gchar *reply, GError **error) { g_autoptr(GRegex) r = NULL; g_autoptr(GMatchInfo) match_info = NULL; GError *inner_error = NULL; GList *list = NULL; if (!reply || !reply[0]) /* Nothing configured, all done */ return NULL; r = g_regex_new ("\\+CGACT:\\s*(\\d+),(\\d+)", G_REGEX_DOLLAR_ENDONLY | G_REGEX_RAW, 0, &inner_error); g_assert (r); g_regex_match_full (r, reply, strlen (reply), 0, 0, &match_info, &inner_error); while (!inner_error && g_match_info_matches (match_info)) { MM3gppPdpContextActive *pdp_active; guint cid = 0; guint aux = 0; if (!mm_get_uint_from_match_info (match_info, 1, &cid)) { inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Couldn't parse CID from reply: '%s'", reply); break; } if (!mm_get_uint_from_match_info (match_info, 2, &aux) || (aux != 0 && aux != 1)) { inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Couldn't parse context status from reply: '%s'", reply); break; } pdp_active = g_slice_new0 (MM3gppPdpContextActive); pdp_active->cid = cid; pdp_active->active = (gboolean) aux; list = g_list_prepend (list, pdp_active); g_match_info_next (match_info, &inner_error); } if (inner_error) { mm_3gpp_pdp_context_active_list_free (list); g_propagate_error (error, inner_error); g_prefix_error (error, "Couldn't properly parse list of active/inactive PDP contexts. "); return NULL; } list = g_list_sort (list, (GCompareFunc) mm_3gpp_pdp_context_active_cmp); return list; } /*************************************************************************/ static gboolean item_is_lac_not_stat (GMatchInfo *info, guint32 item) { gchar *str; gboolean is_lac = FALSE; /* A will always be a single digit, without quotes */ str = g_match_info_fetch (info, item); g_assert (str); is_lac = (strchr (str, '"') || strlen (str) > 1); g_free (str); return is_lac; } gboolean mm_3gpp_parse_creg_response (GMatchInfo *info, gpointer log_object, MMModem3gppRegistrationState *out_reg_state, gulong *out_lac, gulong *out_ci, MMModemAccessTechnology *out_act, gboolean *out_cgreg, gboolean *out_cereg, gboolean *out_c5greg, GError **error) { gint n_matches, act = -1; guint stat = 0; guint64 lac = 0, ci = 0; guint istat = 0, ilac = 0, ici = 0, iact = 0; gchar *str; g_assert (info != NULL); g_assert (out_reg_state != NULL); g_assert (out_lac != NULL); g_assert (out_ci != NULL); g_assert (out_act != NULL); g_assert (out_cgreg != NULL); g_assert (out_cereg != NULL); g_assert (out_c5greg != NULL); str = g_match_info_fetch (info, 1); *out_cgreg = (str && strstr (str, "CGREG")) ? TRUE : FALSE; *out_cereg = (str && strstr (str, "CEREG")) ? TRUE : FALSE; *out_c5greg = (str && strstr (str, "C5GREG")) ? TRUE : FALSE; g_free (str); /* Normally the number of matches could be used to determine what each * item is, but we have overlap in one case. */ n_matches = g_match_info_get_match_count (info); if (n_matches == 3) { /* CREG=1: +CREG: */ istat = 2; } else if (n_matches == 4) { /* Solicited response: +CREG: , */ istat = 3; } else if (n_matches == 5) { /* CREG=2 (GSM 07.07): +CREG: ,, */ istat = 2; ilac = 3; ici = 4; } else if (n_matches == 6) { /* CREG=2 (ETSI 27.007): +CREG: ,,, * CREG=2 (non-standard): +CREG: ,,, */ /* Check if the third item is the LAC to distinguish the two cases */ if (item_is_lac_not_stat (info, 3)) { istat = 2; ilac = 3; ici = 4; iact = 5; } else { istat = 3; ilac = 4; ici = 5; } } else if (n_matches == 7) { /* CREG=2 (solicited): +CREG: ,,,, * CREG=2 (unsolicited with RAC): +CREG: ,,,, * CEREG=2 (solicited): +CEREG: ,,,, * CEREG=2 (unsolicited with RAC): +CEREG: ,,,, */ if (*out_cereg) { /* Check if the third item is the LAC to distinguish the two cases */ if (item_is_lac_not_stat (info, 3)) { istat = 2; ilac = 3; } else { istat = 3; ilac = 4; } ici = 5; iact = 6; } else { /* Check if the third item is the LAC to distinguish the two cases */ if (item_is_lac_not_stat (info, 3)) { istat = 2; ilac = 3; ici = 4; iact = 5; } else { istat = 3; ilac = 4; ici = 5; iact = 6; } } } else if (n_matches == 8) { /* CEREG=2 (solicited with RAC): +CEREG: ,,,,, * C5GREG=2 (unsolicited): +C5GREG: ,,,,, */ if (*out_cereg) { istat = 3; ilac = 4; ici = 6; iact = 7; } else if (*out_c5greg) { istat = 2; ilac = 3; ici = 4; iact = 5; } } else if (n_matches == 9) { /* C5GREG=2 (solicited): +C5GREG: ,,,,,, */ istat = 3; ilac = 4; ici = 5; iact = 6; } /* Status */ if (!mm_get_uint_from_match_info (info, istat, &stat)) { g_set_error_literal (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Could not parse the registration status response"); return FALSE; } /* 'attached RLOS' is the last valid state */ if (stat > MM_MODEM_3GPP_REGISTRATION_STATE_ATTACHED_RLOS) { mm_obj_warn (log_object, "unknown registration state value '%u'", stat); stat = MM_MODEM_3GPP_REGISTRATION_STATE_UNKNOWN; } /* Location Area Code/Tracking Area Code * FIXME: some phones apparently swap the LAC bytes (LG, SonyEricsson, * Sagem). Need to handle that. */ if (ilac) mm_get_u64_from_hex_match_info (info, ilac, &lac); /* Cell ID */ if (ici) mm_get_u64_from_hex_match_info (info, ici, &ci); /* Access Technology */ if (iact) mm_get_int_from_match_info (info, iact, &act); *out_reg_state = (MMModem3gppRegistrationState) stat; if (stat != MM_MODEM_3GPP_REGISTRATION_STATE_UNKNOWN) { /* Don't fill in lac/ci/act if the device's state is unknown */ *out_lac = (gulong)lac; *out_ci = (gulong)ci; *out_act = (act >= 0 ? get_mm_access_tech_from_etsi_access_tech (act, log_object) : MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN); } return TRUE; } /*************************************************************************/ #define CMGF_TAG "+CMGF:" gboolean mm_3gpp_parse_cmgf_test_response (const gchar *reply, gboolean *sms_pdu_supported, gboolean *sms_text_supported, GError **error) { g_autoptr(GRegex) r = NULL; g_autoptr(GMatchInfo) match_info = NULL; gchar *s; guint32 min = -1; guint32 max = -1; /* Strip whitespace and response tag */ if (g_str_has_prefix (reply, CMGF_TAG)) reply += strlen (CMGF_TAG); while (isspace (*reply)) reply++; r = g_regex_new ("\\(?\\s*(\\d+)\\s*[-,]?\\s*(\\d+)?\\s*\\)?", 0, 0, error); if (!r) return FALSE; if (!g_regex_match (r, reply, 0, &match_info)) { g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Failed to parse CMGF query result '%s'", reply); return FALSE; } s = g_match_info_fetch (match_info, 1); if (s) min = atoi (s); g_free (s); s = g_match_info_fetch (match_info, 2); if (s) max = atoi (s); g_free (s); /* CMGF=0 for PDU mode */ *sms_pdu_supported = (min == 0); /* CMGF=1 for Text mode */ *sms_text_supported = (max >= 1); return TRUE; } /*************************************************************************/ MM3gppPduInfo * mm_3gpp_parse_cmgr_read_response (const gchar *reply, guint index, GError **error) { g_autoptr(GRegex) r = NULL; g_autoptr(GMatchInfo) match_info = NULL; gint count; gint status; gchar *pdu; MM3gppPduInfo *info = NULL; /* +CMGR: ,,(whitespace) */ /* The and fields are matched, but not currently used */ r = g_regex_new ("\\+CMGR:\\s*(\\d+)\\s*,([^,]*),\\s*(\\d+)\\s*([^\\r\\n]*)", 0, 0, NULL); g_assert (r); if (!g_regex_match (r, reply, 0, &match_info)) { g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Failed to parse CMGR read result: response didn't match '%s'", reply); return NULL; } /* g_match_info_get_match_count includes match #0 */ if ((count = g_match_info_get_match_count (match_info)) != 5) { g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Failed to match CMGR fields (matched %d) '%s'", count, reply); return NULL; } if (!mm_get_int_from_match_info (match_info, 1, &status)) { g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Failed to extract CMGR status field '%s'", reply); return NULL; } pdu = mm_get_string_unquoted_from_match_info (match_info, 4); if (!pdu) { g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Failed to extract CMGR pdu field '%s'", reply); return NULL; } info = g_new0 (MM3gppPduInfo, 1); info->index = index; info->status = status; info->pdu = pdu; return info; } /*****************************************************************************/ /* AT+CRSM response parser */ gboolean mm_3gpp_parse_crsm_response (const gchar *reply, guint *sw1, guint *sw2, gchar **hex, GError **error) { g_autoptr(GRegex) r = NULL; g_autoptr(GMatchInfo) match_info = NULL; g_assert (sw1 != NULL); g_assert (sw2 != NULL); g_assert (hex != NULL); *sw1 = 0; *sw2 = 0; *hex = NULL; if (!reply || !g_str_has_prefix (reply, "+CRSM:")) { g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Missing +CRSM prefix"); return FALSE; } r = g_regex_new ("\\+CRSM:\\s*(\\d+)\\s*,\\s*(\\d+)\\s*,\\s*\"?([0-9a-fA-F]+)\"?", G_REGEX_RAW, 0, NULL); g_assert (r != NULL); if (g_regex_match (r, reply, 0, &match_info) && mm_get_uint_from_match_info (match_info, 1, sw1) && mm_get_uint_from_match_info (match_info, 2, sw2)) { *hex = mm_get_string_unquoted_from_match_info (match_info, 3); } if (*hex == NULL) { g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Failed to parse CRSM query result '%s'", reply); return FALSE; } return TRUE; } /*************************************************************************/ /* CGCONTRDP=N response parser */ static gboolean split_local_address_and_subnet (const gchar *str, gchar **local_address, gchar **subnet) { const gchar *separator; guint count = 0; /* E.g. split: "2.43.2.44.255.255.255.255" * into: * local address: "2.43.2.44", * subnet: "255.255.255.255" */ g_assert (str); g_assert (local_address); g_assert (subnet); separator = str; while (1) { separator = strchr (separator, '.'); if (separator) { count++; if (count == 4) { if (local_address) *local_address = g_strndup (str, (separator - str)); if (subnet) *subnet = g_strdup (++separator); return TRUE; } separator++; continue; } /* Not even the full IP? report error parsing */ if (count < 3) return FALSE; if (count == 3) { if (local_address) *local_address = g_strdup (str); if (subnet) *subnet = NULL; return TRUE; } } } gboolean mm_3gpp_parse_cgcontrdp_response (const gchar *response, guint *out_cid, guint *out_bearer_id, gchar **out_apn, gchar **out_local_address, gchar **out_subnet, gchar **out_gateway_address, gchar **out_dns_primary_address, gchar **out_dns_secondary_address, GError **error) { g_autoptr(GRegex) r = NULL; g_autoptr(GMatchInfo) match_info = NULL; GError *inner_error = NULL; guint cid = 0; guint bearer_id = 0; g_autofree gchar *apn = NULL; g_autofree gchar *local_address_and_subnet = NULL; g_autofree gchar *local_address = NULL; g_autofree gchar *subnet = NULL; g_autofree gchar *gateway_address = NULL; g_autofree gchar *dns_primary_address = NULL; g_autofree gchar *dns_secondary_address = NULL; guint field_format_extra_index = 0; /* Response may be e.g.: * +CGCONTRDP: 4,5,"ibox.tim.it.mnc001.mcc222.gprs","2.197.17.49.255.255.255.255","2.197.17.49","10.207.43.46","10.206.56.132","0.0.0.0","0.0.0.0",0 * * We assume only ONE line is returned; because we request +CGCONTRDP with * a specific N CID. Also, we don't parse all fields, we stop after * secondary DNS. * * Only the 3 first parameters (cid, bearer id, apn) are mandatory in the * response, all the others are optional, but, we'll anyway assume that APN * may be empty. * * The format of the response changed in TS 27.007 v9.4.0, we try to detect * both formats ('a' if >= v9.4.0, 'b' if < v9.4.0) with a single regex here. */ r = g_regex_new ("\\+CGCONTRDP: " "(\\d+),(\\d+),([^,]*)" /* cid, bearer id, apn */ "(?:,([^,]*))?" /* (a)ip+mask or (b)ip */ "(?:,([^,]*))?" /* (a)gateway or (b)mask */ "(?:,([^,]*))?" /* (a)dns1 or (b)gateway */ "(?:,([^,]*))?" /* (a)dns2 or (b)dns1 */ "(?:,([^,]*))?" /* (a)p-cscf primary or (b)dns2 */ "(?:,(.*))?" /* others, ignored */ "(?:\\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) { g_propagate_error (error, inner_error); return FALSE; } if (!g_match_info_matches (match_info)) { g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_INVALID_ARGS, "Couldn't match +CGCONTRDP response"); return FALSE; } if (out_cid && !mm_get_uint_from_match_info (match_info, 1, &cid)) { g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Error parsing cid"); return FALSE; } if (out_bearer_id && !mm_get_uint_from_match_info (match_info, 2, &bearer_id)) { g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Error parsing bearer id"); return FALSE; } /* Remaining strings are optional or empty allowed */ apn = mm_get_string_unquoted_from_match_info (match_info, 3); /* * The +CGCONTRDP=[cid] response format before version TS 27.007 v9.4.0 had * the subnet in its own comma-separated field. Try to detect that. */ local_address_and_subnet = mm_get_string_unquoted_from_match_info (match_info, 4); if (local_address_and_subnet && !split_local_address_and_subnet (local_address_and_subnet, &local_address, &subnet)) { g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Error parsing local address and subnet"); return FALSE; } /* If we don't have a subnet in field 4, we're using the old format with subnet in an extra field */ if (!subnet) { subnet = mm_get_string_unquoted_from_match_info (match_info, 5); field_format_extra_index = 1; } gateway_address = mm_get_string_unquoted_from_match_info (match_info, 5 + field_format_extra_index); dns_primary_address = mm_get_string_unquoted_from_match_info (match_info, 6 + field_format_extra_index); dns_secondary_address = mm_get_string_unquoted_from_match_info (match_info, 7 + field_format_extra_index); if (out_cid) *out_cid = cid; if (out_bearer_id) *out_bearer_id = bearer_id; if (out_apn) *out_apn = g_steal_pointer (&apn); /* Local address and subnet may always be retrieved, even if not requested * by the caller, as we need them to know which +CGCONTRDP=[cid] response is * being parsed. So make sure we free them if not needed. */ if (out_local_address) *out_local_address = g_steal_pointer (&local_address); if (out_subnet) *out_subnet = g_steal_pointer (&subnet); if (out_gateway_address) *out_gateway_address = g_steal_pointer (&gateway_address); if (out_dns_primary_address) *out_dns_primary_address = g_steal_pointer (&dns_primary_address); if (out_dns_secondary_address) *out_dns_secondary_address = g_steal_pointer (&dns_secondary_address); return TRUE; } /*************************************************************************/ gboolean mm_3gpp_parse_cfun_query_response (const gchar *response, guint *out_state, GError **error) { g_autoptr(GRegex) r = NULL; g_autoptr(GMatchInfo) match_info = NULL; GError *inner_error = NULL; guint state = G_MAXUINT; g_assert (out_state != NULL); /* Response may be e.g.: * +CFUN: 1,0 * ..but we don't care about the second number */ r = g_regex_new ("\\+CFUN: (\\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 parse +CFUN response: %s", response); goto out; } if (!mm_get_uint_from_match_info (match_info, 1, &state)) { inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Couldn't read power state value"); goto out; } *out_state = state; out: if (inner_error) { g_propagate_error (error, inner_error); return FALSE; } return TRUE; } /*****************************************************************************/ /* +CESQ response parser */ gboolean mm_3gpp_parse_cesq_response (const gchar *response, guint *out_rxlev, guint *out_ber, guint *out_rscp, guint *out_ecn0, guint *out_rsrq, guint *out_rsrp, GError **error) { g_autoptr(GRegex) r = NULL; g_autoptr(GMatchInfo) match_info = NULL; GError *inner_error = NULL; guint rxlev = 99; guint ber = 99; guint rscp = 255; guint ecn0 = 255; guint rsrq = 255; guint rsrp = 255; gboolean success = FALSE; g_assert (out_rxlev); g_assert (out_ber); g_assert (out_rscp); g_assert (out_ecn0); g_assert (out_rsrq); g_assert (out_rsrp); /* Response may be e.g.: * +CESQ: 99,99,255,255,20,80 */ r = g_regex_new ("\\+CESQ: (\\d+),(\\d+),(\\d+),(\\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 && g_match_info_matches (match_info)) { if (!mm_get_uint_from_match_info (match_info, 1, &rxlev)) { inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Couldn't read RXLEV"); goto out; } if (!mm_get_uint_from_match_info (match_info, 2, &ber)) { inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Couldn't read BER"); goto out; } if (!mm_get_uint_from_match_info (match_info, 3, &rscp)) { inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Couldn't read RSCP"); goto out; } if (!mm_get_uint_from_match_info (match_info, 4, &ecn0)) { inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Couldn't read Ec/N0"); goto out; } if (!mm_get_uint_from_match_info (match_info, 5, &rsrq)) { inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Couldn't read RSRQ"); goto out; } if (!mm_get_uint_from_match_info (match_info, 6, &rsrp)) { inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Couldn't read RSRP"); goto out; } success = TRUE; } out: if (inner_error) { g_propagate_error (error, inner_error); return FALSE; } if (!success) { g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Couldn't parse +CESQ response: %s", response); return FALSE; } *out_rxlev = rxlev; *out_ber = ber; *out_rscp = rscp; *out_ecn0 = ecn0; *out_rsrq = rsrq; *out_rsrp = rsrp; return TRUE; } gboolean mm_3gpp_rxlev_to_rssi (guint rxlev, gpointer log_object, gdouble *out_rssi) { if (rxlev <= 63) { *out_rssi = -111.0 + rxlev; return TRUE; } if (rxlev != 99) mm_obj_warn (log_object, "unexpected rxlev: %u", rxlev); return FALSE; } gboolean mm_3gpp_rscp_level_to_rscp (guint rscp_level, gpointer log_object, gdouble *out_rscp) { if (rscp_level <= 96) { *out_rscp = -121.0 + rscp_level; return TRUE; } if (rscp_level != 255) mm_obj_warn (log_object, "unexpected rscp level: %u", rscp_level); return FALSE; } gboolean mm_3gpp_ecn0_level_to_ecio (guint ecn0_level, gpointer log_object, gdouble *out_ecio) { if (ecn0_level <= 49) { *out_ecio = -24.5 + (((gdouble) ecn0_level) * 0.5); return TRUE; } if (ecn0_level != 255) mm_obj_warn (log_object, "unexpected Ec/N0 level: %u", ecn0_level); return FALSE; } gboolean mm_3gpp_rsrq_level_to_rsrq (guint rsrq_level, gpointer log_object, gdouble *out_rsrq) { if (rsrq_level <= 34) { *out_rsrq = -20.0 + (((gdouble) rsrq_level) * 0.5); return TRUE; } if (rsrq_level != 255) mm_obj_warn (log_object, "unexpected RSRQ level: %u", rsrq_level); return FALSE; } gboolean mm_3gpp_rsrp_level_to_rsrp (guint rsrp_level, gpointer log_object, gdouble *out_rsrp) { if (rsrp_level <= 97) { *out_rsrp = -141.0 + rsrp_level; return TRUE; } if (rsrp_level != 255) mm_obj_warn (log_object, "unexpected RSRP level: %u", rsrp_level); return FALSE; } gboolean mm_3gpp_cesq_response_to_signal_info (const gchar *response, gpointer log_object, MMSignal **out_gsm, MMSignal **out_umts, MMSignal **out_lte, GError **error) { guint rxlev = 0; guint ber = 0; guint rscp_level = 0; guint ecn0_level = 0; guint rsrq_level = 0; guint rsrp_level = 0; gdouble rssi = -G_MAXDOUBLE; gdouble rscp = -G_MAXDOUBLE; gdouble ecio = -G_MAXDOUBLE; gdouble rsrq = -G_MAXDOUBLE; gdouble rsrp = -G_MAXDOUBLE; MMSignal *gsm = NULL; MMSignal *umts = NULL; MMSignal *lte = NULL; if (!mm_3gpp_parse_cesq_response (response, &rxlev, &ber, &rscp_level, &ecn0_level, &rsrq_level, &rsrp_level, error)) return FALSE; /* GERAN RSSI */ if (mm_3gpp_rxlev_to_rssi (rxlev, log_object, &rssi)) { gsm = mm_signal_new (); mm_signal_set_rssi (gsm, rssi); } /* ignore BER */ /* UMTS RSCP */ if (mm_3gpp_rscp_level_to_rscp (rscp_level, log_object, &rscp)) { umts = mm_signal_new (); mm_signal_set_rscp (umts, rscp); } /* UMTS EcIo (assumed EcN0) */ if (mm_3gpp_ecn0_level_to_ecio (ecn0_level, log_object, &ecio)) { if (!umts) umts = mm_signal_new (); mm_signal_set_ecio (umts, ecio); } /* LTE RSRQ */ if (mm_3gpp_rsrq_level_to_rsrq (rsrq_level, log_object, &rsrq)) { lte = mm_signal_new (); mm_signal_set_rsrq (lte, rsrq); } /* LTE RSRP */ if (mm_3gpp_rsrp_level_to_rsrp (rsrp_level, log_object, &rsrp)) { if (!lte) lte = mm_signal_new (); mm_signal_set_rsrp (lte, rsrp); } if (!gsm && !umts && !lte) { g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Couldn't build detailed signal info"); return FALSE; } if (gsm) *out_gsm = gsm; if (umts) *out_umts = umts; if (lte) *out_lte = lte; return TRUE; } gboolean mm_3gpp_parse_cfun_query_generic_response (const gchar *response, MMModemPowerState *out_state, GError **error) { guint state; if (!mm_3gpp_parse_cfun_query_response (response, &state, error)) return FALSE; switch (state) { case 0: *out_state = MM_MODEM_POWER_STATE_OFF; return TRUE; case 1: *out_state = MM_MODEM_POWER_STATE_ON; return TRUE; case 4: *out_state = MM_MODEM_POWER_STATE_LOW; return TRUE; default: g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Unknown +CFUN state: %u", state); return FALSE; } } static MMModem3gppEpsUeModeOperation cemode_values[] = { [0] = MM_MODEM_3GPP_EPS_UE_MODE_OPERATION_PS_2, [1] = MM_MODEM_3GPP_EPS_UE_MODE_OPERATION_CSPS_1, [2] = MM_MODEM_3GPP_EPS_UE_MODE_OPERATION_CSPS_2, [3] = MM_MODEM_3GPP_EPS_UE_MODE_OPERATION_PS_1, }; gchar * mm_3gpp_build_cemode_set_request (MMModem3gppEpsUeModeOperation mode) { guint i; g_return_val_if_fail (mode != MM_MODEM_3GPP_EPS_UE_MODE_OPERATION_UNKNOWN, NULL); for (i = 0; i < G_N_ELEMENTS (cemode_values); i++) { if (mode == cemode_values[i]) return g_strdup_printf ("+CEMODE=%u", i); } g_assert_not_reached (); return NULL; } gboolean mm_3gpp_parse_cemode_query_response (const gchar *response, MMModem3gppEpsUeModeOperation *out_mode, GError **error) { guint value = 0; response = mm_strip_tag (response, "+CEMODE:"); if (mm_get_uint_from_str (response, &value) && value < G_N_ELEMENTS (cemode_values)) { if (out_mode) *out_mode = cemode_values[value]; return TRUE; } g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Couldn't parse UE mode of operation: '%s' (value %u)", response, value); return FALSE; } /*************************************************************************/ /* CCWA service query response parser */ gboolean mm_3gpp_parse_ccwa_service_query_response (const gchar *response, gpointer log_object, gboolean *status, GError **error) { g_autoptr(GRegex) r = NULL; g_autoptr(GMatchInfo) match_info = NULL; GError *inner_error = NULL; gint class_1_status = -1; /* * AT+CCWA=[,] * +CCWA: , * [+CCWA: , * [...]] * OK * * If is 255 it applies to ALL classes. * * We're only interested in class 1 (voice) */ r = g_regex_new ("\\+CCWA:\\s*(\\d+),\\s*(\\d+)$", G_REGEX_RAW | G_REGEX_MULTILINE | G_REGEX_NEWLINE_CRLF, G_REGEX_MATCH_NEWLINE_CRLF, NULL); g_assert (r != NULL); g_regex_match_full (r, response, strlen (response), 0, 0, &match_info, &inner_error); if (inner_error) goto out; /* Parse the results */ while (g_match_info_matches (match_info)) { guint st; guint class; if (!mm_get_uint_from_match_info (match_info, 2, &class)) mm_obj_warn (log_object, "couldn't parse class from +CCWA line"); else if (class == 1 || class == 255) { if (!mm_get_uint_from_match_info (match_info, 1, &st)) mm_obj_warn (log_object, "couldn't parse status from +CCWA line"); else { class_1_status = st; break; } } g_match_info_next (match_info, NULL); } out: if (inner_error) { g_propagate_error (error, inner_error); return FALSE; } if (class_1_status < 0) { g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_NOT_FOUND, "call waiting status for voice class missing"); return FALSE; } if (class_1_status != 0 && class_1_status != 1) { g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED, "call waiting status for voice class invalid: %d", class_1_status); return FALSE; } if (status) *status = (gboolean) class_1_status; return TRUE; } /*************************************************************************/ /* CGATT helpers */ gchar * mm_3gpp_build_cgatt_set_request (MMModem3gppPacketServiceState state) { guint cgatt_action; switch (state) { case MM_MODEM_3GPP_PACKET_SERVICE_STATE_ATTACHED: cgatt_action = 1; break; case MM_MODEM_3GPP_PACKET_SERVICE_STATE_DETACHED: cgatt_action = 0; break; case MM_MODEM_3GPP_PACKET_SERVICE_STATE_UNKNOWN: default: return NULL; } return g_strdup_printf ("+CGATT=%u", cgatt_action); } /*************************************************************************/ static MMSmsStorage storage_from_str (const gchar *str) { if (g_str_equal (str, "SM")) return MM_SMS_STORAGE_SM; if (g_str_equal (str, "ME")) return MM_SMS_STORAGE_ME; if (g_str_equal (str, "MT")) return MM_SMS_STORAGE_MT; if (g_str_equal (str, "SR")) return MM_SMS_STORAGE_SR; if (g_str_equal (str, "BM")) return MM_SMS_STORAGE_BM; if (g_str_equal (str, "TA")) return MM_SMS_STORAGE_TA; return MM_SMS_STORAGE_UNKNOWN; } gboolean mm_3gpp_parse_cpms_test_response (const gchar *reply, GArray **mem1, GArray **mem2, GArray **mem3, GError **error) { guint i; g_autoptr(GRegex) r = NULL; g_autoptr(GArray) tmp1 = NULL; g_autoptr(GArray) tmp2 = NULL; g_autoptr(GArray) tmp3 = NULL; g_auto(GStrv) split = NULL; g_assert (mem1 != NULL); g_assert (mem2 != NULL); g_assert (mem3 != NULL); #define N_EXPECTED_GROUPS 3 split = mm_split_string_groups (mm_strip_tag (reply, "+CPMS:")); if (!split) { g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Couldn't split +CPMS test response in groups"); return FALSE; } if (g_strv_length (split) != N_EXPECTED_GROUPS) { g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Cannot parse +CPMS test response: invalid number of groups (%u != %u)", g_strv_length (split), N_EXPECTED_GROUPS); return FALSE; } r = g_regex_new ("\\s*\"([^,\\)]+)\"\\s*", 0, 0, NULL); g_assert (r); for (i = 0; i < N_EXPECTED_GROUPS; i++) { g_autoptr(GMatchInfo) match_info = NULL; GArray *array; /* We always return a valid array, even if it may be empty */ array = g_array_new (FALSE, FALSE, sizeof (MMSmsStorage)); /* Got a range group to match */ if (g_regex_match (r, split[i], 0, &match_info)) { while (g_match_info_matches (match_info)) { gchar *str; str = g_match_info_fetch (match_info, 1); if (str) { MMSmsStorage storage; storage = storage_from_str (str); g_array_append_val (array, storage); g_free (str); } g_match_info_next (match_info, NULL); } } if (!tmp1) tmp1 = array; else if (!tmp2) tmp2 = array; else if (!tmp3) tmp3 = array; else g_assert_not_reached (); } /* Only return TRUE if all sets have been parsed correctly * (even if the arrays may be empty) */ if (tmp1 && tmp2 && tmp3) { *mem1 = g_steal_pointer (&tmp1); *mem2 = g_steal_pointer (&tmp2); *mem3 = g_steal_pointer (&tmp3); return TRUE; } g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Cannot parse +CPMS test response: mem1 %s, mem2 %s, mem3 %s", tmp1 ? "yes" : "no", tmp2 ? "yes" : "no", tmp3 ? "yes" : "no"); return FALSE; } /********************************************************************** * AT+CPMS? * +CPMS: ,,,,,, ,, */ #define CPMS_QUERY_REGEX "\\+CPMS:\\s*\"(?P.*)\",[0-9]+,[0-9]+,\"(?P.*)\",[0-9]+,[0-9]+,\"(?P.*)\",[0-9]+,[0-9]" gboolean mm_3gpp_parse_cpms_query_response (const gchar *reply, MMSmsStorage *memr, MMSmsStorage *memw, GError **error) { g_autoptr(GRegex) r = NULL; g_autoptr(GMatchInfo) match_info = NULL; r = g_regex_new (CPMS_QUERY_REGEX, G_REGEX_RAW, 0, NULL); g_assert (r); if (!g_regex_match (r, reply, 0, &match_info)) { g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Could not parse CPMS query response '%s'", reply); return FALSE; } if (!g_match_info_matches (match_info)) { g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Could not find matches in CPMS query reply '%s'", reply); return FALSE; } if (!mm_3gpp_get_cpms_storage_match (match_info, "memr", memr, error)) return FALSE; if (!mm_3gpp_get_cpms_storage_match (match_info, "memw", memw, error)) return FALSE; return TRUE; } gboolean mm_3gpp_get_cpms_storage_match (GMatchInfo *match_info, const gchar *match_name, MMSmsStorage *storage, GError **error) { gboolean ret = TRUE; gchar *str = NULL; str = g_match_info_fetch_named (match_info, match_name); if (str == NULL || str[0] == '\0') { g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Could not find '%s' from CPMS reply", match_name); ret = FALSE; } else { *storage = storage_from_str (str); } g_free (str); return ret; } /*************************************************************************/ gboolean mm_3gpp_parse_cscs_test_response (const gchar *reply, MMModemCharset *out_charsets) { g_autoptr(GRegex) r = NULL; g_autoptr(GMatchInfo) match_info = NULL; MMModemCharset charsets = MM_MODEM_CHARSET_UNKNOWN; gchar *p; gchar *str; gboolean success = FALSE; g_return_val_if_fail (reply != NULL, FALSE); g_return_val_if_fail (out_charsets != NULL, FALSE); /* Find the first '(' or '"'; the general format is: * * +CSCS: ("IRA","GSM","UCS2") * * but some devices (some Blackberries) don't include the (). */ p = strchr (reply, '('); if (p) p++; else { p = strchr (reply, '"'); if (!p) return FALSE; } /* Now parse each charset */ r = g_regex_new ("\\s*([^,\\)]+)\\s*", 0, 0, NULL); g_assert (r); if (g_regex_match (r, p, 0, &match_info)) { while (g_match_info_matches (match_info)) { str = g_match_info_fetch (match_info, 1); charsets |= mm_modem_charset_from_string (str); g_free (str); g_match_info_next (match_info, NULL); success = TRUE; } } if (success) *out_charsets = charsets; return success; } /*************************************************************************/ gboolean mm_3gpp_parse_clck_test_response (const gchar *reply, MMModem3gppFacility *out_facilities) { g_autoptr(GRegex) r = NULL; g_autoptr(GMatchInfo) match_info = NULL; g_return_val_if_fail (reply != NULL, FALSE); g_return_val_if_fail (out_facilities != NULL, FALSE); /* the general format is: * * +CLCK: ("SC","AO","AI","PN") */ reply = mm_strip_tag (reply, "+CLCK:"); /* Now parse each facility */ r = g_regex_new ("\\s*\"([^,\\)]+)\"\\s*", 0, 0, NULL); g_assert (r != NULL); *out_facilities = MM_MODEM_3GPP_FACILITY_NONE; if (g_regex_match (r, reply, 0, &match_info)) { while (g_match_info_matches (match_info)) { gchar *str; str = g_match_info_fetch (match_info, 1); if (str) { *out_facilities |= mm_3gpp_acronym_to_facility (str); g_free (str); } g_match_info_next (match_info, NULL); } } return (*out_facilities != MM_MODEM_3GPP_FACILITY_NONE); } /*************************************************************************/ gboolean mm_3gpp_parse_clck_write_response (const gchar *reply, gboolean *enabled) { g_autoptr(GRegex) r = NULL; g_autoptr(GMatchInfo) match_info = NULL; g_return_val_if_fail (reply != NULL, FALSE); g_return_val_if_fail (enabled != NULL, FALSE); reply = mm_strip_tag (reply, "+CLCK:"); r = g_regex_new ("\\s*([01])\\s*", 0, 0, NULL); g_assert (r != NULL); if (g_regex_match (r, reply, 0, &match_info)) { g_autofree gchar *str = NULL; str = g_match_info_fetch (match_info, 1); if (str) { /* We're trying to match either '0' or '1', * so we don't expect any other thing */ if (*str == '0') *enabled = FALSE; else if (*str == '1') *enabled = TRUE; else g_assert_not_reached (); return TRUE; } } return FALSE; } /*************************************************************************/ GStrv mm_3gpp_parse_cnum_exec_response (const gchar *reply) { g_autoptr(GPtrArray) array = NULL; g_autoptr(GRegex) r = NULL; g_autoptr(GMatchInfo) match_info = NULL; /* Empty strings also return NULL list */ if (!reply || !reply[0]) return NULL; r = g_regex_new ("\\+CNUM:\\s*((\"([^\"]|(\\\"))*\")|([^,]*)),\"(?\\S+)\",\\d", G_REGEX_UNGREEDY, 0, NULL); g_assert (r != NULL); array = g_ptr_array_new (); g_regex_match (r, reply, 0, &match_info); while (g_match_info_matches (match_info)) { g_autofree gchar *number = NULL; number = g_match_info_fetch_named (match_info, "num"); if (number && number[0]) g_ptr_array_add (array, g_steal_pointer (&number)); g_match_info_next (match_info, NULL); } if (!array->len) return NULL; g_ptr_array_add (array, NULL); return (GStrv) g_ptr_array_free (g_steal_pointer (&array), FALSE); } /*************************************************************************/ gchar * mm_3gpp_build_cmer_set_request (MM3gppCmerMode mode, MM3gppCmerInd ind) { guint mode_val; guint ind_val; if (mode == MM_3GPP_CMER_MODE_DISCARD_URCS) return g_strdup ("+CMER=0"); if (mode < MM_3GPP_CMER_MODE_DISCARD_URCS || mode > MM_3GPP_CMER_MODE_FORWARD_URCS) return NULL; mode_val = mm_find_bit_set (mode); if (ind < MM_3GPP_CMER_IND_DISABLE || ind > MM_3GPP_CMER_IND_ENABLE_ALL) return NULL; ind_val = mm_find_bit_set (ind); return g_strdup_printf ("+CMER=%u,0,0,%u", mode_val, ind_val); } gboolean mm_3gpp_parse_cmer_test_response (const gchar *response, gpointer log_object, MM3gppCmerMode *out_supported_modes, MM3gppCmerInd *out_supported_inds, GError **error) { gchar **split; GError *inner_error = NULL; GArray *array_supported_modes = NULL; GArray *array_supported_inds = NULL; gchar *aux = NULL; gboolean ret = FALSE; MM3gppCmerMode supported_modes = 0; MM3gppCmerInd supported_inds = 0; guint i; /* * AT+CMER=? * +CMER: 1,0,0,(0-1),0 * * AT+CMER=? * +CMER: (0-3),(0),(0),(0-1),(0-1) * * AT+CMER=? * +CMER: (1,2),0,0,(0-1),0 */ split = mm_split_string_groups (mm_strip_tag (response, "+CMER:")); if (!split) { inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Couldn't split +CMER test response in groups"); goto out; } /* We want 1st and 4th groups */ if (g_strv_length (split) < 4) { inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Missing groups in +CMER test response (%u < 4)", g_strv_length (split)); goto out; } /* Modes in 1st group */ if (!(array_supported_modes = mm_parse_uint_list (split[0], &inner_error))) goto out; g_clear_pointer (&aux, g_free); /* Ind settings in 4th group */ if (!(array_supported_inds = mm_parse_uint_list (split[3], &inner_error))) goto out; g_clear_pointer (&aux, g_free); for (i = 0; i < array_supported_modes->len; i++) { guint mode_val; mode_val = g_array_index (array_supported_modes, guint, i); if (mode_val <= 3) supported_modes |= (MM3gppCmerMode) (1 << mode_val); else mm_obj_dbg (log_object, "unknown +CMER mode reported: %u", mode_val); } for (i = 0; i < array_supported_inds->len; i++) { guint ind_val; ind_val = g_array_index (array_supported_inds, guint, i); if (ind_val <= 2) supported_inds |= (MM3gppCmerInd) (1 << ind_val); else mm_obj_dbg (log_object, "unknown +CMER ind reported: %u", ind_val); } if (out_supported_modes) *out_supported_modes = supported_modes; if (out_supported_inds) *out_supported_inds = supported_inds; ret = TRUE; out: if (array_supported_modes) g_array_unref (array_supported_modes); if (array_supported_inds) g_array_unref (array_supported_inds); g_clear_pointer (&aux, g_free); g_strfreev (split); if (inner_error) g_propagate_error (error, inner_error); return ret; } /*************************************************************************/ struct MM3gppCindResponse { gchar *desc; guint idx; gint min; gint max; }; static MM3gppCindResponse * cind_response_new (const gchar *desc, guint idx, gint min, gint max) { MM3gppCindResponse *r; gchar *p; g_return_val_if_fail (desc != NULL, NULL); r = g_malloc0 (sizeof (MM3gppCindResponse)); /* Strip quotes */ r->desc = p = g_malloc0 (strlen (desc) + 1); while (*desc) { if (*desc != '"' && !isspace (*desc)) *p++ = tolower (*desc); desc++; } r->idx = idx; r->max = max; r->min = min; return r; } static void cind_response_free (MM3gppCindResponse *r) { g_return_if_fail (r != NULL); g_free (r->desc); memset (r, 0, sizeof (MM3gppCindResponse)); g_free (r); } const gchar * mm_3gpp_cind_response_get_desc (MM3gppCindResponse *r) { g_return_val_if_fail (r != NULL, NULL); return r->desc; } guint mm_3gpp_cind_response_get_index (MM3gppCindResponse *r) { g_return_val_if_fail (r != NULL, 0); return r->idx; } gint mm_3gpp_cind_response_get_min (MM3gppCindResponse *r) { g_return_val_if_fail (r != NULL, -1); return r->min; } gint mm_3gpp_cind_response_get_max (MM3gppCindResponse *r) { g_return_val_if_fail (r != NULL, -1); return r->max; } #define CIND_TAG "+CIND:" GHashTable * mm_3gpp_parse_cind_test_response (const gchar *reply, GError **error) { g_autoptr(GRegex) r = NULL; g_autoptr(GMatchInfo) match_info = NULL; GHashTable *hash; guint idx = 1; g_return_val_if_fail (reply != NULL, NULL); /* Strip whitespace and response tag */ if (g_str_has_prefix (reply, CIND_TAG)) reply += strlen (CIND_TAG); while (isspace (*reply)) reply++; r = g_regex_new ("\\(([^,]*),\\((\\d+)[-,](\\d+).*\\)", G_REGEX_UNGREEDY, 0, NULL); g_assert (r); hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) cind_response_free); if (g_regex_match (r, reply, 0, &match_info)) { while (g_match_info_matches (match_info)) { MM3gppCindResponse *resp; gchar *desc, *tmp; gint min = 0, max = 0; desc = g_match_info_fetch (match_info, 1); tmp = g_match_info_fetch (match_info, 2); min = atoi (tmp); g_free (tmp); tmp = g_match_info_fetch (match_info, 3); max = atoi (tmp); g_free (tmp); resp = cind_response_new (desc, idx++, min, max); if (resp) g_hash_table_insert (hash, g_strdup (resp->desc), resp); g_free (desc); g_match_info_next (match_info, NULL); } } return hash; } /*************************************************************************/ GByteArray * mm_3gpp_parse_cind_read_response (const gchar *reply, GError **error) { g_autoptr(GRegex) r = NULL; g_autoptr(GMatchInfo) match_info = NULL; GByteArray *array = NULL; GError *inner_error = NULL; guint8 t; g_return_val_if_fail (reply != NULL, NULL); if (!g_str_has_prefix (reply, CIND_TAG)) { g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Could not parse the +CIND response '%s': no CIND tag found", reply); return NULL; } reply = mm_strip_tag (reply, CIND_TAG); r = g_regex_new ("(\\d+)[^0-9]+", G_REGEX_UNGREEDY, 0, NULL); g_assert (r != NULL); if (!g_regex_match (r, reply, 0, &match_info)) { g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Could not parse the +CIND response '%s': didn't match", reply); return NULL; } array = g_byte_array_sized_new (g_match_info_get_match_count (match_info)); /* Add a zero element so callers can use 1-based indexes returned by * mm_3gpp_cind_response_get_index(). */ t = 0; g_byte_array_append (array, &t, 1); while (!inner_error && g_match_info_matches (match_info)) { g_autofree gchar *str = NULL; guint val = 0; str = g_match_info_fetch (match_info, 1); if (mm_get_uint_from_str (str, &val) && val < 255) { t = (guint8) val; g_byte_array_append (array, &t, 1); } else { inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Could not parse the +CIND response: invalid index '%s'", str); } g_match_info_next (match_info, NULL); } if (inner_error) { g_propagate_error (error, inner_error); g_clear_pointer (&array, g_byte_array_unref); } return array; } /*************************************************************************/ /* +CGEV indication parser * * We provide full parsing support, including parameters, for these messages: * +CGEV: NW DETACH * +CGEV: ME DETACH * +CGEV: NW PDN ACT * +CGEV: ME PDN ACT [,[,]] * +CGEV: NW ACT , , * +CGEV: ME ACT , , * +CGEV: NW DEACT , , [] * +CGEV: ME DEACT , , [] * +CGEV: NW PDN DEACT * +CGEV: ME PDN DEACT * +CGEV: NW DEACT , , * +CGEV: ME DEACT , , * +CGEV: REJECT , * +CGEV: NW REACT , , [] * * We don't provide parameter parsing for these messages: * +CGEV: NW CLASS * +CGEV: ME CLASS * +CGEV: NW MODIFY , , * +CGEV: ME MODIFY , , */ static gboolean deact_secondary (const gchar *str) { /* We need to detect the ME/NW DEACT format. * Either, * +CGEV: NW DEACT , , [] * +CGEV: ME DEACT , , [] * or, * +CGEV: NW DEACT , , * +CGEV: ME DEACT , , */ str = strstr (str, "DEACT") + 5; while (*str == ' ') str++; /* We will look for because we know it's NUMERIC */ return g_ascii_isdigit (*str); } MM3gppCgev mm_3gpp_parse_cgev_indication_action (const gchar *str) { str = mm_strip_tag (str, "+CGEV:"); if (g_str_has_prefix (str, "NW DETACH")) return MM_3GPP_CGEV_NW_DETACH; if (g_str_has_prefix (str, "ME DETACH")) return MM_3GPP_CGEV_ME_DETACH; if (g_str_has_prefix (str, "NW CLASS")) return MM_3GPP_CGEV_NW_CLASS; if (g_str_has_prefix (str, "ME CLASS")) return MM_3GPP_CGEV_ME_CLASS; if (g_str_has_prefix (str, "NW PDN ACT")) return MM_3GPP_CGEV_NW_ACT_PRIMARY; if (g_str_has_prefix (str, "ME PDN ACT")) return MM_3GPP_CGEV_ME_ACT_PRIMARY; if (g_str_has_prefix (str, "NW ACT")) return MM_3GPP_CGEV_NW_ACT_SECONDARY; if (g_str_has_prefix (str, "ME ACT")) return MM_3GPP_CGEV_ME_ACT_SECONDARY; if (g_str_has_prefix (str, "NW DEACT")) return (deact_secondary (str) ? MM_3GPP_CGEV_NW_DEACT_SECONDARY : MM_3GPP_CGEV_NW_DEACT_PDP); if (g_str_has_prefix (str, "ME DEACT")) return (deact_secondary (str) ? MM_3GPP_CGEV_ME_DEACT_SECONDARY : MM_3GPP_CGEV_ME_DEACT_PDP); if (g_str_has_prefix (str, "NW PDN DEACT")) return MM_3GPP_CGEV_NW_DEACT_PRIMARY; if (g_str_has_prefix (str, "ME PDN DEACT")) return MM_3GPP_CGEV_ME_DEACT_PRIMARY; if (g_str_has_prefix (str, "NW MODIFY")) return MM_3GPP_CGEV_NW_MODIFY; if (g_str_has_prefix (str, "ME MODIFY")) return MM_3GPP_CGEV_ME_MODIFY; if (g_str_has_prefix (str, "NW REACT")) return MM_3GPP_CGEV_NW_REACT; if (g_str_has_prefix (str, "REJECT")) return MM_3GPP_CGEV_REJECT; return MM_3GPP_CGEV_UNKNOWN; } /* * +CGEV: NW DEACT , , [] * +CGEV: ME DEACT , , [] * +CGEV: REJECT , * +CGEV: NW REACT , , [] */ gboolean mm_3gpp_parse_cgev_indication_pdp (const gchar *str, MM3gppCgev type, gchar **out_pdp_type, gchar **out_pdp_addr, guint *out_cid, GError **error) { g_autoptr(GRegex) r = NULL; g_autoptr(GMatchInfo) match_info = NULL; GError *inner_error = NULL; g_autofree gchar *pdp_type = NULL; g_autofree gchar *pdp_addr = NULL; guint cid = 0; g_assert (type == MM_3GPP_CGEV_REJECT || type == MM_3GPP_CGEV_NW_REACT || type == MM_3GPP_CGEV_NW_DEACT_PDP || type == MM_3GPP_CGEV_ME_DEACT_PDP); r = g_regex_new ("(?:" "REJECT|" "NW REACT|" "NW DEACT|ME DEACT" ")\\s*([^,]*),\\s*([^,]*)(?:,\\s*([0-9]+))?", 0, 0, NULL); g_assert (r); str = mm_strip_tag (str, "+CGEV:"); g_regex_match_full (r, str, strlen (str), 0, 0, &match_info, &inner_error); if (inner_error) { g_propagate_error (error, inner_error); return FALSE; } if (!g_match_info_matches (match_info)) { g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Couldn't match response"); return FALSE; } if (out_pdp_type && !(pdp_type = mm_get_string_unquoted_from_match_info (match_info, 1))) { g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Error parsing PDP type"); return FALSE; } if (out_pdp_addr && !(pdp_addr = mm_get_string_unquoted_from_match_info (match_info, 2))) { g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Error parsing PDP addr"); return FALSE; } /* CID is optional */ if (out_cid && (g_match_info_get_match_count (match_info) >= 4) && !mm_get_uint_from_match_info (match_info, 3, &cid)) { g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Error parsing CID"); return FALSE; } if (out_pdp_type) *out_pdp_type = g_steal_pointer (&pdp_type); if (out_pdp_addr) *out_pdp_addr = g_steal_pointer (&pdp_addr); if (out_cid) *out_cid = cid; return TRUE; } /* * +CGEV: NW PDN ACT * +CGEV: ME PDN ACT [,[,]] * +CGEV: NW PDN DEACT * +CGEV: ME PDN DEACT * * NOTE: the special case of a "ME PDN ACT" notification with the additional * and fields is telling us that was NOT connected * but was connected instead, which may happen when trying to * connect a IPv4v6 context but the modem ended up connecting a IPv4-only or * IPv6-only context instead. We are right now ignoring this, and assuming the * that we requested is the one reported as connected. */ gboolean mm_3gpp_parse_cgev_indication_primary (const gchar *str, MM3gppCgev type, guint *out_cid, GError **error) { g_autoptr(GRegex) r = NULL; g_autoptr(GMatchInfo) match_info = NULL; GError *inner_error = NULL; guint cid = 0; g_assert ((type == MM_3GPP_CGEV_NW_ACT_PRIMARY) || (type == MM_3GPP_CGEV_ME_ACT_PRIMARY) || (type == MM_3GPP_CGEV_NW_DEACT_PRIMARY) || (type == MM_3GPP_CGEV_ME_DEACT_PRIMARY)); r = g_regex_new ("(?:" "NW PDN ACT|ME PDN ACT|" "NW PDN DEACT|ME PDN DEACT|" ")\\s*([0-9]+)", 0, 0, NULL); str = mm_strip_tag (str, "+CGEV:"); g_regex_match_full (r, str, strlen (str), 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_cid && !mm_get_uint_from_match_info (match_info, 1, &cid)) { inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Error parsing CID"); goto out; } out: if (inner_error) { g_propagate_error (error, inner_error); return FALSE; } if (out_cid) *out_cid = cid; return TRUE; } /* * +CGEV: NW ACT , , * +CGEV: ME ACT , , * +CGEV: NW DEACT , , * +CGEV: ME DEACT , , */ gboolean mm_3gpp_parse_cgev_indication_secondary (const gchar *str, MM3gppCgev type, guint *out_p_cid, guint *out_cid, guint *out_event_type, GError **error) { g_autoptr(GRegex) r = NULL; g_autoptr(GMatchInfo) match_info = NULL; GError *inner_error = NULL; guint p_cid = 0; guint cid = 0; guint event_type = 0; g_assert (type == MM_3GPP_CGEV_NW_ACT_SECONDARY || type == MM_3GPP_CGEV_ME_ACT_SECONDARY || type == MM_3GPP_CGEV_NW_DEACT_SECONDARY || type == MM_3GPP_CGEV_ME_DEACT_SECONDARY); r = g_regex_new ("(?:" "NW ACT|ME ACT|" "NW DEACT|ME DEACT" ")\\s*([0-9]+),\\s*([0-9]+),\\s*([0-9]+)", 0, 0, NULL); str = mm_strip_tag (str, "+CGEV:"); g_regex_match_full (r, str, strlen (str), 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_p_cid && !mm_get_uint_from_match_info (match_info, 1, &p_cid)) { inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Error parsing primary CID"); goto out; } if (out_cid && !mm_get_uint_from_match_info (match_info, 2, &cid)) { inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Error parsing secondary CID"); goto out; } if (out_event_type && !mm_get_uint_from_match_info (match_info, 3, &event_type)) { inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Error parsing event type"); goto out; } out: if (inner_error) { g_propagate_error (error, inner_error); return FALSE; } if (out_p_cid) *out_p_cid = p_cid; if (out_cid) *out_cid = cid; if (out_event_type) *out_event_type = event_type; return TRUE; } /*************************************************************************/ void mm_3gpp_pdu_info_free (MM3gppPduInfo *info) { g_free (info->pdu); g_free (info); } void mm_3gpp_pdu_info_list_free (GList *info_list) { g_list_free_full (info_list, (GDestroyNotify)mm_3gpp_pdu_info_free); } GList * mm_3gpp_parse_pdu_cmgl_response (const gchar *str, GError **error) { g_autoptr(GRegex) r = NULL; g_autoptr(GMatchInfo) match_info = NULL; GError *inner_error = NULL; GList *list = NULL; /* * +CMGL: , , [], * or * +CMGL: , , * * We just read , and the PDU itself. */ r = g_regex_new ("\\+CMGL:\\s*(\\d+)\\s*,\\s*(\\d+)\\s*,(.*)\\r\\n([^\\r\\n]*)(\\r\\n)?", G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); g_assert (r != NULL); g_regex_match_full (r, str, strlen (str), 0, 0, &match_info, &inner_error); while (!inner_error && g_match_info_matches (match_info)) { MM3gppPduInfo *info; info = g_new0 (MM3gppPduInfo, 1); if (mm_get_int_from_match_info (match_info, 1, &info->index) && mm_get_int_from_match_info (match_info, 2, &info->status) && (info->pdu = mm_get_string_unquoted_from_match_info (match_info, 4)) != NULL) { /* Append to our list of results and keep on */ list = g_list_append (list, info); g_match_info_next (match_info, &inner_error); } else { mm_3gpp_pdu_info_free (info); inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Error parsing +CMGL response: '%s'", str); } } if (inner_error) { g_propagate_error (error, inner_error); mm_3gpp_pdu_info_list_free (list); return NULL; } return list; } /*************************************************************************/ /* Map two letter facility codes into flag values. There are * many more facilities defined (for various flavors of call * barring); we only map the ones we care about. */ typedef struct { MMModem3gppFacility facility; const gchar *acronym; } FacilityAcronym; static const FacilityAcronym facility_acronyms[] = { { MM_MODEM_3GPP_FACILITY_SIM, "SC" }, { MM_MODEM_3GPP_FACILITY_PH_SIM, "PS" }, { MM_MODEM_3GPP_FACILITY_PH_FSIM, "PF" }, { MM_MODEM_3GPP_FACILITY_FIXED_DIALING, "FD" }, { MM_MODEM_3GPP_FACILITY_NET_PERS, "PN" }, { MM_MODEM_3GPP_FACILITY_NET_SUB_PERS, "PU" }, { MM_MODEM_3GPP_FACILITY_PROVIDER_PERS, "PP" }, { MM_MODEM_3GPP_FACILITY_CORP_PERS, "PC" } }; MMModem3gppFacility mm_3gpp_acronym_to_facility (const gchar *str) { guint i; for (i = 0; i < G_N_ELEMENTS (facility_acronyms); i++) { if (g_str_equal (facility_acronyms[i].acronym, str)) return facility_acronyms[i].facility; } return MM_MODEM_3GPP_FACILITY_NONE; } const gchar * mm_3gpp_facility_to_acronym (MMModem3gppFacility facility) { guint i; for (i = 0; i < G_N_ELEMENTS (facility_acronyms); i++) { if (facility_acronyms[i].facility == facility) return facility_acronyms[i].acronym; } return NULL; } /*************************************************************************/ MMModemAccessTechnology mm_string_to_access_tech (const gchar *string) { MMModemAccessTechnology act = MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN; gsize len; g_return_val_if_fail (string != NULL, MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN); /* We're returning a MASK of technologies found; so we can include more * than one technology in the result */ if (strcasestr (string, "LTE") || strcasestr (string, "4G")) act |= MM_MODEM_ACCESS_TECHNOLOGY_LTE; if (strcasestr (string, "HSPA+")) act |= MM_MODEM_ACCESS_TECHNOLOGY_HSPA_PLUS; else if (strcasestr (string, "HSPA")) act |= MM_MODEM_ACCESS_TECHNOLOGY_HSPA; if (strcasestr (string, "HSUPA")) act |= MM_MODEM_ACCESS_TECHNOLOGY_HSUPA; if (strcasestr (string, "HSDPA")) act |= MM_MODEM_ACCESS_TECHNOLOGY_HSDPA; if (strcasestr (string, "UMTS") || strcasestr (string, "3G") || strcasestr (string, "WCDMA")) act |= MM_MODEM_ACCESS_TECHNOLOGY_UMTS; if (strcasestr (string, "EDGE")) act |= MM_MODEM_ACCESS_TECHNOLOGY_EDGE; if (strcasestr (string, "GPRS")) act |= MM_MODEM_ACCESS_TECHNOLOGY_GPRS; if (strcasestr (string, "GSM")) act |= MM_MODEM_ACCESS_TECHNOLOGY_GSM; if (strcasestr (string, "EvDO Rel0")) act |= MM_MODEM_ACCESS_TECHNOLOGY_EVDO0; if (strcasestr (string, "EvDO RelA")) act |= MM_MODEM_ACCESS_TECHNOLOGY_EVDOA; if (strcasestr (string, "EvDO RelB")) act |= MM_MODEM_ACCESS_TECHNOLOGY_EVDOB; if (strcasestr (string, "1xRTT") || strcasestr (string, "CDMA2000 1X")) act |= MM_MODEM_ACCESS_TECHNOLOGY_1XRTT; /* Check "EVDO" and "CDMA" as standalone strings since their characters * are included in other strings too. */ len = strlen (string); if (strncmp (string, "EVDO", 4) && (len >= 4 && !isalnum (string[4]))) act |= MM_MODEM_ACCESS_TECHNOLOGY_EVDO0; if (strncmp (string, "CDMA", 4) && (len >= 4 && !isalnum (string[4]))) act |= MM_MODEM_ACCESS_TECHNOLOGY_1XRTT; if (strncmp (string, "CDMA-EVDO", 9) && (len >= 9 && !isalnum (string[9]))) act |= MM_MODEM_ACCESS_TECHNOLOGY_1XRTT | MM_MODEM_ACCESS_TECHNOLOGY_EVDO0; return act; } /*************************************************************************/ void mm_3gpp_normalize_operator (gchar **operator, MMModemCharset cur_charset, gpointer log_object) { g_autofree gchar *normalized = NULL; g_assert (operator); if (*operator == NULL) return; /* Despite +CSCS? may claim supporting UCS2, Some modems (e.g. Huawei) * always report the operator name in ASCII in a +COPS response. */ if (cur_charset != MM_MODEM_CHARSET_UNKNOWN) { g_autoptr(GError) error = NULL; normalized = mm_modem_charset_str_to_utf8 (*operator, -1, cur_charset, TRUE, &error); if (normalized) goto out; mm_obj_dbg (log_object, "couldn't convert operator string '%s' from charset '%s': %s", *operator, mm_modem_charset_to_string (cur_charset), error->message); } /* Charset is unknown or there was an error in conversion; try to see * if the contents we got are valid UTF-8 already. */ if (g_utf8_validate (*operator, -1, NULL)) normalized = g_strdup (*operator); out: /* Some modems (Novatel LTE) return the operator name as "Unknown" when * it fails to obtain the operator name. Return NULL in such case. */ if (!normalized || g_ascii_strcasecmp (normalized, "unknown") == 0) { /* If normalization failed, just cleanup the string */ g_clear_pointer (operator, g_free); return; } mm_obj_dbg (log_object, "operator normalized '%s'->'%s'", *operator, normalized); g_clear_pointer (operator, g_free); *operator = g_steal_pointer (&normalized); } /*************************************************************************/ const gchar * mm_3gpp_get_pdp_type_from_ip_family (MMBearerIpFamily family) { switch (family) { case MM_BEARER_IP_FAMILY_IPV4: return "IP"; case MM_BEARER_IP_FAMILY_IPV6: return "IPV6"; case MM_BEARER_IP_FAMILY_IPV4V6: return "IPV4V6"; case MM_BEARER_IP_FAMILY_NON_IP: return "Non-IP"; case MM_BEARER_IP_FAMILY_NONE: case MM_BEARER_IP_FAMILY_ANY: default: return NULL; } } MMBearerIpFamily mm_3gpp_get_ip_family_from_pdp_type (const gchar *pdp_type) { if (!pdp_type) return MM_BEARER_IP_FAMILY_NONE; if (g_str_equal (pdp_type, "IP")) return MM_BEARER_IP_FAMILY_IPV4; if (g_str_equal (pdp_type, "IPV4")) return MM_BEARER_IP_FAMILY_IPV4; if (g_str_equal (pdp_type, "IPV6")) return MM_BEARER_IP_FAMILY_IPV6; if (g_str_equal (pdp_type, "IPV4V6")) return MM_BEARER_IP_FAMILY_IPV4V6; if (g_str_equal (pdp_type, "Non-IP")) return MM_BEARER_IP_FAMILY_NON_IP; return MM_BEARER_IP_FAMILY_NONE; } gboolean mm_3gpp_normalize_ip_family (MMBearerIpFamily *family) { /* if nothing specific requested, default to IPv4 */ if (*family == MM_BEARER_IP_FAMILY_NONE || *family == MM_BEARER_IP_FAMILY_ANY) { *family = MM_BEARER_IP_FAMILY_IPV4; return TRUE; } /* no need to normalize */ return FALSE; } /*************************************************************************/ /* ICCID validation */ /* * 89: telecom (2 digits) * cc: country (2 digits) * oo: operator (2 digits) * aaaaaaaaaaaaa: operator-specific account number (13 digits) * x: checksum (1 digit) */ char * mm_3gpp_parse_iccid (const char *raw_iccid, GError **error) { gboolean swap; char *buf, *swapped = NULL; gsize len = 0; int i; g_return_val_if_fail (raw_iccid != NULL, NULL); /* Skip spaces and quotes */ while (raw_iccid && *raw_iccid && (isspace (*raw_iccid) || *raw_iccid == '"')) raw_iccid++; /* Make sure the buffer is only digits or 'F' */ buf = g_strdup (raw_iccid); for (len = 0; buf[len]; len++) { /* Digit values allowed anywhere */ if (g_ascii_isdigit (buf[len])) continue; /* There are operators (e.g. the Chinese CMCC operator) that abuse the * fact that 4 bits are used to store the BCD encoded numbers, and also * use the [A-F] range as valid characters for the ICCID. Explicitly * allow this range in the operator-specific part. */ if (len >= 6 && g_ascii_isxdigit (buf[len])) { /* canonicalize hex digit */ buf[len] = g_ascii_toupper (buf[len]); continue; } if (buf[len] == '\"') { buf[len] = 0; break; } /* Invalid character */ g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "ICCID response contained invalid character '%c' at index '%zu'", buf[len], len); goto error; } /* ICCIDs are 19 or 20 digits long */ if (len < 19 || len > 20) { g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Invalid ICCID response size (was %zd, expected 19 or 20)", len); goto error; } /* The leading two digits of an ICCID is the major industry identifier and * should be '89' for telecommunication purposes according to ISO/IEC 7812. */ if (buf[0] == '8' && buf[1] == '9') { swap = FALSE; } else if (buf[0] == '9' && buf[1] == '8') { /* swapped digits are only expected in raw +CRSM responses, which must all * be 20-bytes long */ if (len != 20) { g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Invalid ICCID response size while swap needed (expected 20)"); goto error; } swap = TRUE; } else { /* FIXME: Instead of erroring out, revisit this solution if we find any SIM * that doesn't use '89' as the major industry identifier of the ICCID. */ g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Invalid ICCID response (leading two digits are not 89)"); goto error; } if (swap) { /* Swap digits in the ICCID response to get the actual ICCID, each * group of 2 digits is reversed. * * 21436587 -> 12345678 */ g_assert (len == 20); swapped = g_malloc0 (21); for (i = 0; i < 10; i++) { swapped[i * 2] = buf[(i * 2) + 1]; swapped[(i * 2) + 1] = buf[i * 2]; } } else swapped = g_strdup (buf); /* Zero out the F for 19 digit ICCIDs */ if (swapped[len - 1] == 'F') swapped[len - 1] = 0; g_free (buf); return swapped; error: g_free (buf); g_free (swapped); return NULL; } /*************************************************************************/ gboolean mm_3gpp_parse_operator_id (const gchar *operator_id, guint16 *mcc, guint16 *mnc, gboolean *three_digit_mnc, GError **error) { guint len; guint i; gchar aux[4]; guint16 tmp; g_assert (operator_id != NULL); len = strlen (operator_id); if (len != 5 && len != 6) { g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Operator ID must have 5 or 6 digits"); return FALSE; } for (i = 0; i < len; i++) { if (!g_ascii_isdigit (operator_id[i])) { g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Operator ID must only contain digits"); return FALSE; } } memcpy (&aux[0], operator_id, 3); aux[3] = '\0'; tmp = atoi (aux); if (tmp == 0) { g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "MCC must not be zero"); return FALSE; } if (mcc) *mcc = tmp; if (mnc) { if (len == 5) { memcpy (&aux[0], &operator_id[3], 2); aux[2] = '\0'; } else memcpy (&aux[0], &operator_id[3], 3); *mnc = atoi (aux); } if (three_digit_mnc) *three_digit_mnc = len == 6; return TRUE; } /*************************************************************************/ /* Emergency numbers (+CRSM output) */ GStrv mm_3gpp_parse_emergency_numbers (const char *raw, GError **error) { gsize rawlen; guint8 *bin; gsize binlen; gsize max_items; GPtrArray *out; guint i; /* The emergency call code is of a variable length with a maximum length of * 6 digits. Each emergency call code is coded on three bytes, with each * digit within the code being coded on four bits. If a code of less that 6 * digits is chosen, then the unused nibbles shall be set to 'F'. */ rawlen = strlen (raw); if (!rawlen) { g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_INVALID_ARGS, "empty emergency numbers list"); return NULL; } if (rawlen % 6 != 0) { g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_INVALID_ARGS, "invalid raw emergency numbers list length: %" G_GSIZE_FORMAT, rawlen); return NULL; } bin = mm_utils_hexstr2bin (raw, -1, &binlen, error); if (!bin) { g_prefix_error (error, "invalid raw emergency numbers list contents: "); return NULL; } max_items = binlen / 3; out = g_ptr_array_sized_new (max_items + 1); for (i = 0; i < max_items; i++) { gchar *number; number = mm_bcd_to_string (&bin[i*3], 3, TRUE /* low_nybble_first */); if (number && number[0]) g_ptr_array_add (out, number); else g_free (number); } g_free (bin); if (!out->len) { g_ptr_array_unref (out); g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED, "uninitialized emergency numbers list"); return NULL; } g_ptr_array_add (out, NULL); return (GStrv) g_ptr_array_free (out, FALSE); } /*************************************************************************/ gboolean mm_cdma_parse_spservice_read_response (const gchar *reply, MMModemCdmaRegistrationState *out_cdma_1x_state, MMModemCdmaRegistrationState *out_evdo_state) { const gchar *p; g_return_val_if_fail (reply != NULL, FALSE); g_return_val_if_fail (out_cdma_1x_state != NULL, FALSE); g_return_val_if_fail (out_evdo_state != NULL, FALSE); p = mm_strip_tag (reply, "+SPSERVICE:"); if (!isdigit (*p)) return FALSE; switch (atoi (p)) { case 0: /* no service */ *out_cdma_1x_state = MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN; *out_evdo_state = MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN; break; case 1: /* 1xRTT */ *out_cdma_1x_state = MM_MODEM_CDMA_REGISTRATION_STATE_REGISTERED; *out_evdo_state = MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN; break; case 2: /* EVDO rev 0 */ case 3: /* EVDO rev A */ *out_cdma_1x_state = MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN; *out_evdo_state = MM_MODEM_CDMA_REGISTRATION_STATE_REGISTERED; break; default: return FALSE; } return TRUE; } /*************************************************************************/ typedef struct { gint num; gboolean roam_ind; const gchar *banner; } EriItem; /* NOTE: these may be Sprint-specific for now... */ static const EriItem eris[] = { { 0, TRUE, "Digital or Analog Roaming" }, { 1, FALSE, "Home" }, { 2, TRUE, "Digital or Analog Roaming" }, { 3, TRUE, "Out of neighborhood" }, { 4, TRUE, "Out of building" }, { 5, TRUE, "Preferred system" }, { 6, TRUE, "Available System" }, { 7, TRUE, "Alliance Partner" }, { 8, TRUE, "Premium Partner" }, { 9, TRUE, "Full Service Functionality" }, { 10, TRUE, "Partial Service Functionality" }, { 64, TRUE, "Preferred system" }, { 65, TRUE, "Available System" }, { 66, TRUE, "Alliance Partner" }, { 67, TRUE, "Premium Partner" }, { 68, TRUE, "Full Service Functionality" }, { 69, TRUE, "Partial Service Functionality" }, { 70, TRUE, "Analog A" }, { 71, TRUE, "Analog B" }, { 72, TRUE, "CDMA 800 A" }, { 73, TRUE, "CDMA 800 B" }, { 74, TRUE, "International Roaming" }, { 75, TRUE, "Extended Network" }, { 76, FALSE, "Campus" }, { 77, FALSE, "In Building" }, { 78, TRUE, "Regional" }, { 79, TRUE, "Community" }, { 80, TRUE, "Business" }, { 81, TRUE, "Zone 1" }, { 82, TRUE, "Zone 2" }, { 83, TRUE, "National" }, { 84, TRUE, "Local" }, { 85, TRUE, "City" }, { 86, TRUE, "Government" }, { 87, TRUE, "USA" }, { 88, TRUE, "State" }, { 89, TRUE, "Resort" }, { 90, TRUE, "Headquarters" }, { 91, TRUE, "Personal" }, { 92, FALSE, "Home" }, { 93, TRUE, "Residential" }, { 94, TRUE, "University" }, { 95, TRUE, "College" }, { 96, TRUE, "Hotel Guest" }, { 97, TRUE, "Rental" }, { 98, FALSE, "Corporate" }, { 99, FALSE, "Home Provider" }, { 100, FALSE, "Campus" }, { 101, FALSE, "In Building" }, { 102, TRUE, "Regional" }, { 103, TRUE, "Community" }, { 104, TRUE, "Business" }, { 105, TRUE, "Zone 1" }, { 106, TRUE, "Zone 2" }, { 107, TRUE, "National" }, { 108, TRUE, "Local" }, { 109, TRUE, "City" }, { 110, TRUE, "Government" }, { 111, TRUE, "USA" }, { 112, TRUE, "State" }, { 113, TRUE, "Resort" }, { 114, TRUE, "Headquarters" }, { 115, TRUE, "Personal" }, { 116, FALSE, "Home" }, { 117, TRUE, "Residential" }, { 118, TRUE, "University" }, { 119, TRUE, "College" }, { 120, TRUE, "Hotel Guest" }, { 121, TRUE, "Rental" }, { 122, FALSE, "Corporate" }, { 123, FALSE, "Home Provider" }, { 124, TRUE, "International" }, { 125, TRUE, "International" }, { 126, TRUE, "International" }, { 127, FALSE, "Premium Service" }, { 128, FALSE, "Enhanced Service" }, { 129, FALSE, "Enhanced Digital" }, { 130, FALSE, "Enhanced Roaming" }, { 131, FALSE, "Alliance Service" }, { 132, FALSE, "Alliance Network" }, { 133, FALSE, "Data Roaming" }, /* Sprint: Vision Roaming */ { 134, FALSE, "Extended Service" }, { 135, FALSE, "Expanded Services" }, { 136, FALSE, "Expanded Network" }, { 137, TRUE, "Premium Service" }, { 138, TRUE, "Enhanced Service" }, { 139, TRUE, "Enhanced Digital" }, { 140, TRUE, "Enhanced Roaming" }, { 141, TRUE, "Alliance Service" }, { 142, TRUE, "Alliance Network" }, { 143, TRUE, "Data Roaming" }, /* Sprint: Vision Roaming */ { 144, TRUE, "Extended Service" }, { 145, TRUE, "Expanded Services" }, { 146, TRUE, "Expanded Network" }, { 147, TRUE, "Premium Service" }, { 148, TRUE, "Enhanced Service" }, { 149, TRUE, "Enhanced Digital" }, { 150, TRUE, "Enhanced Roaming" }, { 151, TRUE, "Alliance Service" }, { 152, TRUE, "Alliance Network" }, { 153, TRUE, "Data Roaming" }, /* Sprint: Vision Roaming */ { 154, TRUE, "Extended Service" }, { 155, TRUE, "Expanded Services" }, { 156, TRUE, "Expanded Network" }, { 157, TRUE, "Premium International" }, { 158, TRUE, "Premium International" }, { 159, TRUE, "Premium International" }, { 160, TRUE, NULL }, { 161, TRUE, NULL }, { 162, FALSE, NULL }, { 163, FALSE, NULL }, { 164, FALSE, "Extended Voice/Data Network" }, { 165, FALSE, "Extended Voice/Data Network" }, { 166, TRUE, "Extended Voice/Data Network" }, { 167, FALSE, "Extended Broadband" }, { 168, FALSE, "Extended Broadband" }, { 169, TRUE, "Extended Broadband" }, { 170, FALSE, "Extended Data" }, { 171, FALSE, "Extended Data" }, { 172, TRUE, "Extended Data" }, { 173, FALSE, "Extended Data Network" }, { 174, FALSE, "Extended Data Network" }, { 175, TRUE, "Extended Data Network" }, { 176, FALSE, "Extended Network" }, { 177, FALSE, "Extended Network" }, { 178, TRUE, "Extended Network" }, { 179, FALSE, "Extended Service" }, { 180, TRUE, "Extended Service" }, { 181, FALSE, "Extended Voice" }, { 182, FALSE, "Extended Voice" }, { 183, TRUE, "Extended Voice" }, { 184, FALSE, "Extended Voice/Data" }, { 185, FALSE, "Extended Voice/Data" }, { 186, TRUE, "Extended Voice/Data" }, { 187, FALSE, "Extended Voice Network" }, { 188, FALSE, "Extended Voice Network" }, { 189, TRUE, "Extended Voice Network" }, { 190, FALSE, "Extended Voice/Data" }, { 191, FALSE, "Extended Voice/Data" }, { 192, TRUE, "Extended Voice/Data" }, { 193, TRUE, "International" }, { 194, FALSE, "International Services" }, { 195, FALSE, "International Voice" }, { 196, FALSE, "International Voice/Data" }, { 197, FALSE, "International Voice/Data" }, { 198, TRUE, "International Voice/Data" }, { 199, FALSE, "Extended Voice/Data Network" }, { 200, TRUE, "Extended Voice/Data Network" }, { 201, TRUE, "Extended Voice/Data Network" }, { 202, FALSE, "Extended Broadband" }, { 203, TRUE, "Extended Broadband" }, { 204, TRUE, "Extended Broadband" }, { 205, FALSE, "Extended Data" }, { 206, TRUE, "Extended Data" }, { 207, TRUE, "Extended Data" }, { 208, FALSE, "Extended Data Network" }, { 209, TRUE, "Extended Data Network" }, { 210, TRUE, "Extended Data Network" }, { 211, FALSE, "Extended Network" }, { 212, TRUE, "Extended Network" }, { 213, FALSE, "Extended Service" }, { 214, TRUE, "Extended Service" }, { 215, TRUE, "Extended Service" }, { 216, FALSE, "Extended Voice" }, { 217, TRUE, "Extended Voice" }, { 218, TRUE, "Extended Voice" }, { 219, FALSE, "Extended Voice/Data" }, { 220, TRUE, "Extended Voice/Data" }, { 221, TRUE, "Extended Voice/Data" }, { 222, FALSE, "Extended Voice Network" }, { 223, FALSE, "Extended Voice Network" }, { 224, TRUE, "Extended Voice Network" }, { 225, FALSE, "Extended Voice/Data" }, { 226, TRUE, "Extended Voice/Data" }, { 227, TRUE, "Extended Voice/Data" }, { 228, TRUE, "International" }, { 229, TRUE, "International" }, { 230, TRUE, "International Services" }, { 231, TRUE, "International Voice" }, { 232, FALSE, "International Voice/Data" }, { 233, TRUE, "International Voice/Data" }, { 234, TRUE, "International Voice/Data" }, { 235, TRUE, "Premium International" }, { 236, TRUE, NULL }, { 237, TRUE, NULL }, { 238, FALSE, NULL }, { 239, FALSE, NULL }, { -1, FALSE, NULL }, }; gboolean mm_cdma_parse_eri (const gchar *reply, gboolean *out_roaming, guint *out_ind, const gchar **out_desc) { guint ind; const EriItem *iter = &eris[0]; gboolean found = FALSE; g_return_val_if_fail (reply != NULL, FALSE); g_return_val_if_fail (out_roaming != NULL, FALSE); if (mm_get_uint_from_str (reply, &ind)) { if (out_ind) *out_ind = ind; while (iter->num != -1) { if ((guint)iter->num == ind) { *out_roaming = iter->roam_ind; if (out_desc) *out_desc = iter->banner; found = TRUE; break; } iter++; } } return found; } /*************************************************************************/ gboolean mm_cdma_parse_crm_test_response (const gchar *reply, MMModemCdmaRmProtocol *min, MMModemCdmaRmProtocol *max, GError **error) { g_autoptr(GRegex) r = NULL; g_autoptr(GMatchInfo) match_info = NULL; gboolean result = FALSE; GError *match_error = NULL; /* Expected reply format is: * ---> AT+CRM=? * <--- +CRM: (0-2) */ r = g_regex_new ("\\+CRM:\\s*\\((\\d+)-(\\d+)\\)", G_REGEX_DOLLAR_ENDONLY | G_REGEX_RAW, 0, error); g_assert (r != NULL); if (g_regex_match_full (r, reply, strlen (reply), 0, 0, &match_info, &match_error)) { gchar *aux; guint min_val = 0; guint max_val = 0; aux = g_match_info_fetch (match_info, 1); min_val = (guint) atoi (aux); g_free (aux); aux = g_match_info_fetch (match_info, 2); max_val = (guint) atoi (aux); g_free (aux); if (min_val == 0 || max_val == 0 || min_val >= max_val) { g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Couldn't parse CRM range: " "Unexpected range of RM protocols (%u,%u)", min_val, max_val); } else { *min = mm_cdma_get_rm_protocol_from_index (min_val, error); if (*min != MM_MODEM_CDMA_RM_PROTOCOL_UNKNOWN) { *max = mm_cdma_get_rm_protocol_from_index (max_val, error); if (*max != MM_MODEM_CDMA_RM_PROTOCOL_UNKNOWN) result = TRUE; } } } else if (match_error) { g_propagate_error (error, match_error); } else { g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Couldn't parse CRM range: response didn't match (%s)", reply); } return result; } /*************************************************************************/ MMModemCdmaRmProtocol mm_cdma_get_rm_protocol_from_index (guint index, GError **error) { guint protocol; /* just adding 1 from the index value should give us the enum */ protocol = index + 1 ; if (protocol > MM_MODEM_CDMA_RM_PROTOCOL_STU_III) { g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Unexpected RM protocol index (%u)", index); protocol = MM_MODEM_CDMA_RM_PROTOCOL_UNKNOWN; } return (MMModemCdmaRmProtocol)protocol; } guint mm_cdma_get_index_from_rm_protocol (MMModemCdmaRmProtocol protocol, GError **error) { if (protocol == MM_MODEM_CDMA_RM_PROTOCOL_UNKNOWN) { g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Unexpected RM protocol (%s)", mm_modem_cdma_rm_protocol_get_string (protocol)); return 0; } /* just subtracting 1 from the enum value should give us the index */ return (protocol - 1); } /*************************************************************************/ gint mm_cdma_normalize_class (const gchar *orig_class) { gchar class; g_return_val_if_fail (orig_class != NULL, '0'); class = toupper (orig_class[0]); /* Cellular (850MHz) */ if (class == '1' || class == 'C') return 1; /* PCS (1900MHz) */ if (class == '2' || class == 'P') return 2; /* Unknown/not registered */ return 0; } /*************************************************************************/ gchar mm_cdma_normalize_band (const gchar *long_band, gint *out_class) { gchar band; g_return_val_if_fail (long_band != NULL, 'Z'); /* There are two response formats for the band; one includes the band * class and the other doesn't. For modems that include the band class * (ex Novatel S720) you'll see "Px" or "Cx" depending on whether the modem * is registered on a PCS/1900 (P) or Cellular/850 (C) system. */ band = toupper (long_band[0]); /* Possible band class in first position; return it */ if (band == 'C' || band == 'P') { gchar tmp[2] = { band, '\0' }; *out_class = mm_cdma_normalize_class (tmp); band = toupper (long_band[1]); } /* normalize to A - F, and Z */ if (band >= 'A' && band <= 'F') return band; /* Unknown/not registered */ return 'Z'; } /*************************************************************************/ /* Caller must strip any "+GSN:" or "+CGSN" from @gsn */ gboolean mm_parse_gsn (const char *gsn, gchar **out_imei, gchar **out_meid, gchar **out_esn) { gchar **items, **iter; gchar *meid = NULL, *esn = NULL, *imei = NULL, *p; gboolean success = FALSE; if (!gsn || !gsn[0]) return FALSE; /* IMEI is 15 numeric digits */ /* ESNs take one of two formats: * (1) 7 or 8 hexadecimal digits * (2) 10 or 11 decimal digits * * In addition, leading zeros may be present or absent, and hexadecimal * ESNs may or may not be prefixed with "0x". */ /* MEIDs take one of two formats: * (1) 14 hexadecimal digits, sometimes padded to 16 digits with leading zeros * (2) 18 decimal digits * * As with ESNs, leading zeros may be present or absent, and hexadecimal * MEIDs may or may not be prefixed with "0x". */ items = g_strsplit_set (gsn, "\r\n\t: ,", 0); for (iter = items; iter && *iter && (!esn || !meid); iter++) { gboolean expect_hex = FALSE, is_hex, is_digit; gchar *s = *iter; guint len = 0; if (!s[0]) continue; if (g_str_has_prefix (s, "0x") || g_str_has_prefix (s, "0X")) { expect_hex = TRUE; s += 2; /* Skip any leading zeros */ while (*s == '0') s++; } /* Check whether all digits are hex or decimal */ is_hex = is_digit = TRUE; p = s; while (*p && (is_hex || is_digit)) { if (!g_ascii_isxdigit (*p)) is_hex = FALSE; if (!g_ascii_isdigit (*p)) is_digit = FALSE; p++, len++; } /* Note that some hex strings are also valid digit strings */ if (is_hex) { if (len == 7 || len == 8) { /* ESN */ if (!esn) { if (len == 7) esn = g_strdup_printf ("0%s", s); else esn = g_strdup (s); } } else if (len == 14) { /* MEID */ if (!meid) meid = g_strdup (s); } } if (is_digit) { if (!is_hex) g_warn_if_fail (expect_hex == FALSE); if (len == 15) { if (!imei) imei = g_strdup (s); } /* Decimal ESN/MEID unhandled for now; conversion from decimal to * hex isn't a straight dec->hex conversion, as the first 2 digits * of the ESN and first 3 digits of the MEID are the manufacturer * identifier and must be converted separately from serial number * and then concatenated with it. */ } } g_strfreev (items); success = meid || esn || imei; if (out_imei) *out_imei = imei; else g_free (imei); if (out_meid) *out_meid = meid; else g_free (meid); if (out_esn) *out_esn = esn; else g_free (esn); return success; } /*****************************************************************************/ /* +CCLK response parser */ gboolean mm_parse_cclk_response (const char *response, gchar **iso8601p, MMNetworkTimezone **tzp, GError **error) { g_autoptr(GRegex) r = NULL; g_autoptr(GMatchInfo) match_info = NULL; GError *match_error = NULL; guint year = 0; guint month = 0; guint day = 0; guint hour = 0; guint minute = 0; guint second = 0; gint tz = 0; g_assert (iso8601p || tzp); /* at least one */ /* Sample replies: * +CCLK: "15/03/05,14:14:26-32" * +CCLK: 17/07/26,11:42:15+01 */ r = g_regex_new ("\\+CCLK:\\s*\"?(\\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 +CCLK results: "); } else { g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Couldn't match +CCLK reply: %s", response); } return FALSE; } /* Remember that g_match_info_get_match_count() includes match #0 */ g_assert (g_match_info_get_match_count (match_info) >= 7); /* Parse mandatory date and time fields */ 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)) { g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Failed to parse +CCLK reply: %s", response); return FALSE; } /* Read optional time zone offset; if not given assume UTC (tz = 0). * Note that timezone offset is given in 15 minute intervals. */ if ((g_match_info_get_match_count (match_info) >= 8) && (!mm_get_int_from_match_info (match_info, 7, &tz))) { g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Failed to parse timezone in +CCLK reply: %s", response); return FALSE; } /* Adjust year to support YYYY format, as per +CSDF in 3GPP TS 27.007. Also, * don't assume the reported date is actually the current real one, as some * devices report an initial date of e.g. January 1st 1980. */ if (year < 100) { if (year >= 70) year += 1900; else year += 2000; } if (tzp) { *tzp = mm_network_timezone_new (); mm_network_timezone_set_offset (*tzp, tz * 15); } if (iso8601p) { /* Return ISO-8601 format date/time string */ *iso8601p = mm_new_iso8601_time (year, month, day, hour, minute, second, TRUE, (tz * 15), error); return (*iso8601p != NULL); } return TRUE; } /*****************************************************************************/ /* +CSIM response parser */ #define MM_MIN_SIM_RETRY_HEX 0x63C0 #define MM_MAX_SIM_RETRY_HEX 0x63CF gint mm_parse_csim_response (const gchar *response, GError **error) { g_autoptr(GRegex) r = NULL; g_autoptr(GMatchInfo) match_info = NULL; g_autofree gchar *str_code = NULL; gint retries = -1; guint hex_code; GError *inner_error = NULL; r = g_regex_new ("\\+CSIM:\\s*[0-9]+,\\s*\".*([0-9a-fA-F]{4})\"", G_REGEX_RAW, 0, NULL); g_regex_match (r, response, 0, &match_info); if (!g_match_info_matches (match_info)) { inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Could not recognize +CSIM response '%s'", response); goto out; } str_code = mm_get_string_unquoted_from_match_info (match_info, 1); if (str_code == NULL) { inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Could not find expected string code in response '%s'", response); goto out; } if (!mm_get_uint_from_hex_str (str_code, &hex_code)) { inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Could not recognize expected hex code in response '%s'", response); goto out; } switch (hex_code) { case 0x6300: inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "SIM verification failed"); goto out; case 0x6983: inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "SIM authentication method blocked"); goto out; case 0x6984: inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "SIM reference data invalidated"); goto out; case 0x6A86: inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Incorrect parameters in SIM request"); goto out; case 0x6A88: inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "SIM reference data not found"); goto out; default: break; } if (hex_code < MM_MIN_SIM_RETRY_HEX || hex_code > MM_MAX_SIM_RETRY_HEX) { inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Unknown error returned '0x%04x'", hex_code); goto out; } retries = (gint)(hex_code - MM_MIN_SIM_RETRY_HEX); out: if (inner_error) { g_propagate_error (error, inner_error); return -1; } g_assert (retries >= 0); return retries; } /*****************************************************************************/ gboolean mm_parse_supl_address (const gchar *supl, gchar **out_fqdn, guint32 *out_ip, guint16 *out_port, GError **error) { gboolean valid = FALSE; gchar **split; guint port; guint32 ip; split = g_strsplit (supl, ":", -1); if (g_strv_length (split) != 2) { g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_INVALID_ARGS, "Invalid SUPL address format: expected FQDN:PORT or IP:PORT"); goto out; } if (!mm_get_uint_from_str (split[1], &port)) { g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_INVALID_ARGS, "Invalid SUPL port number specified: not a number: %s", split[1]); goto out; } if (port == 0 || port > G_MAXUINT16) { g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_INVALID_ARGS, "Invalid SUPL port number specified: out of range: %u", port); goto out; } /* Port is valid */ if (out_port) *out_port = (guint16) port; /* Try to parse first item as IP */ if (inet_pton (AF_INET, split[0], &ip) <= 0) { /* Otherwise, assume it's a domain name */ if (out_fqdn) *out_fqdn = g_strdup (split[0]); if (out_ip) *out_ip = 0; } else { if (out_ip) *out_ip = ip; if (out_fqdn) *out_fqdn = NULL; } valid = TRUE; out: g_strfreev (split); return valid; } /*****************************************************************************/ gboolean mm_sim_parse_cpol_query_response (const gchar *response, guint *out_index, gchar **out_operator_code, gboolean *out_gsm_act, gboolean *out_gsm_compact_act, gboolean *out_utran_act, gboolean *out_eutran_act, gboolean *out_ngran_act, guint *out_act_count, GError **error) { g_autoptr(GMatchInfo) match_info = NULL; g_autoptr(GRegex) r = NULL; g_autofree gchar *operator_code = NULL; guint format = 0; guint act = 0; guint match_count; r = g_regex_new ("\\+CPOL:\\s*(\\d+),\\s*(\\d+),\\s*\"?(\\d+)\"?" "(?:,\\s*(\\d+))?" /* GSM_AcTn */ "(?:,\\s*(\\d+))?" /* GSM_Compact_AcTn */ "(?:,\\s*(\\d+))?" /* UTRAN_AcTn */ "(?:,\\s*(\\d+))?" /* E-UTRAN_AcTn */ "(?:,\\s*(\\d+))?", /* NG-RAN_AcTn */ G_REGEX_RAW, 0, NULL); g_regex_match (r, response, 0, &match_info); if (!g_match_info_matches (match_info)) { g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Couldn't parse +CPOL reply: %s", response); return FALSE; } match_count = g_match_info_get_match_count (match_info); /* Remember that g_match_info_get_match_count() includes match #0 */ g_assert (match_count >= 4); if (!mm_get_uint_from_match_info (match_info, 2, &format) || !(operator_code = mm_get_string_unquoted_from_match_info (match_info, 3))) { g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Couldn't parse +CPOL reply parameters: %s", response); return FALSE; } if (format != 2) { g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "+CPOL reply not using numeric operator code: %s", response); return FALSE; } if (out_index) if (!mm_get_uint_from_match_info (match_info, 1, out_index)) { g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Couldn't parse +CPOL index: %s", response); return FALSE; } if (out_operator_code) *out_operator_code = g_steal_pointer (&operator_code); if (out_gsm_act) *out_gsm_act = match_count >= 5 && mm_get_uint_from_match_info (match_info, 4, &act) && act != 0; if (out_gsm_compact_act) *out_gsm_compact_act = match_count >= 6 && mm_get_uint_from_match_info (match_info, 5, &act) && act != 0; if (out_utran_act) *out_utran_act = match_count >= 7 && mm_get_uint_from_match_info (match_info, 6, &act) && act != 0; if (out_eutran_act) *out_eutran_act = match_count >= 8 && mm_get_uint_from_match_info (match_info, 7, &act) && act != 0; if (out_ngran_act) *out_ngran_act = match_count >= 9 && mm_get_uint_from_match_info (match_info, 8, &act) && act != 0; /* number of access technologies (0...5) in modem response */ if (out_act_count) *out_act_count = match_count - 4; return TRUE; } gboolean mm_sim_parse_cpol_test_response (const gchar *response, guint *out_min_index, guint *out_max_index, GError **error) { g_autoptr(GMatchInfo) match_info = NULL; g_autoptr(GRegex) r = NULL; guint match_count; guint min_index; guint max_index; r = g_regex_new ("\\+CPOL:\\s*\\((\\d+)\\s*-\\s*(\\d+)\\)", G_REGEX_RAW, 0, NULL); g_regex_match (r, response, 0, &match_info); if (!g_match_info_matches (match_info)) { g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Couldn't parse +CPOL=? reply: %s", response); return FALSE; } match_count = g_match_info_get_match_count (match_info); /* Remember that g_match_info_get_match_count() includes match #0 */ g_assert (match_count >= 3); if (!mm_get_uint_from_match_info (match_info, 1, &min_index) || !mm_get_uint_from_match_info (match_info, 2, &max_index)) { g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Couldn't parse indices in +CPOL=? reply: %s", response); return FALSE; } if (out_min_index) *out_min_index = min_index; if (out_max_index) *out_max_index = max_index; return TRUE; } #define EID_BYTE_LENGTH 16 gchar * mm_decode_eid (const gchar *eid, gsize eid_len) { if (eid_len != EID_BYTE_LENGTH) return NULL; return mm_bcd_to_string ((const guint8 *) eid, eid_len, FALSE /* low_nybble_first */); }