aboutsummaryrefslogtreecommitdiff
path: root/src/plugins/altair/mm-modem-helpers-altair-lte.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/plugins/altair/mm-modem-helpers-altair-lte.c')
-rw-r--r--src/plugins/altair/mm-modem-helpers-altair-lte.c259
1 files changed, 259 insertions, 0 deletions
diff --git a/src/plugins/altair/mm-modem-helpers-altair-lte.c b/src/plugins/altair/mm-modem-helpers-altair-lte.c
new file mode 100644
index 00000000..d2fd9af7
--- /dev/null
+++ b/src/plugins/altair/mm-modem-helpers-altair-lte.c
@@ -0,0 +1,259 @@
+/* -*- 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) 2013 Google Inc.
+ *
+ */
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <ModemManager.h>
+#define _LIBMM_INSIDE_MM
+#include <libmm-glib.h>
+
+#include "mm-modem-helpers-altair-lte.h"
+
+#define MM_ALTAIR_IMS_PDN_CID 1
+#define MM_ALTAIR_INTERNET_PDN_CID 3
+
+/*****************************************************************************/
+/* Bands response parser */
+
+GArray *
+mm_altair_parse_bands_response (const gchar *response)
+{
+ gchar **split;
+ GArray *bands;
+ guint i;
+
+ /*
+ * Response is "<band>[,<band>...]"
+ */
+ split = g_strsplit_set (response, ",", -1);
+ if (!split)
+ return NULL;
+
+ bands = g_array_sized_new (FALSE, FALSE, sizeof (MMModemBand), g_strv_length (split));
+
+ for (i = 0; split[i]; i++) {
+ guint32 band_value;
+ MMModemBand band;
+
+ band_value = (guint32)strtoul (split[i], NULL, 10);
+ band = MM_MODEM_BAND_EUTRAN_1 - 1 + band_value;
+
+ /* Due to a firmware issue, the modem may incorrectly includes 0 in the
+ * bands response. We thus ignore any band value outside the range of
+ * E-UTRAN operating bands. */
+ if (band >= MM_MODEM_BAND_EUTRAN_1 && band <= MM_MODEM_BAND_EUTRAN_44)
+ g_array_append_val (bands, band);
+ }
+
+ g_strfreev (split);
+
+ return bands;
+}
+
+/*****************************************************************************/
+/* +CEER response parser */
+
+gchar *
+mm_altair_parse_ceer_response (const gchar *response,
+ GError **error)
+{
+ g_autoptr(GRegex) r = NULL;
+ g_autoptr(GMatchInfo) match_info = NULL;
+ gchar *ceer_response = NULL;
+
+
+ /* First accept an empty response as the no error case. Sometimes, the only
+ * response to the AT+CEER query is an OK.
+ */
+ if (g_strcmp0 ("", response) == 0) {
+ return g_strdup ("");
+ }
+
+ /* The response we are interested in looks so:
+ * +CEER: EPS_AND_NON_EPS_SERVICES_NOT_ALLOWED
+ */
+ r = g_regex_new ("\\+CEER:\\s*(\\w*)?",
+ G_REGEX_RAW,
+ 0, NULL);
+ g_assert (r != NULL);
+
+ if (!g_regex_match (r, response, 0, &match_info)) {
+ g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Could not parse +CEER response");
+ return NULL;
+ }
+
+ if (g_match_info_matches (match_info)) {
+ ceer_response = mm_get_string_unquoted_from_match_info (match_info, 1);
+ if (!ceer_response)
+ ceer_response = g_strdup ("");
+ }
+
+ return ceer_response;
+}
+
+/*****************************************************************************/
+/* %CGINFO="cid",1 response parser */
+
+gint
+mm_altair_parse_cid (const gchar *response, GError **error)
+{
+ g_autoptr(GRegex) regex = NULL;
+ g_autoptr(GMatchInfo) match_info = NULL;
+ guint cid = -1;
+
+ regex = g_regex_new ("\\%CGINFO:\\s*(\\d+)", G_REGEX_RAW, 0, NULL);
+ g_assert (regex);
+ if (!g_regex_match_full (regex, response, strlen (response), 0, 0, &match_info, error))
+ return -1;
+
+ if (!mm_get_uint_from_match_info (match_info, 1, &cid))
+ g_set_error (error,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Failed to parse %%CGINFO=\"cid\",1 response");
+
+ return cid;
+}
+
+/*****************************************************************************/
+/* %PCOINFO response parser */
+
+MMPco *
+mm_altair_parse_vendor_pco_info (const gchar *pco_info, GError **error)
+{
+ g_autoptr(GRegex) regex = NULL;
+ g_autoptr(GMatchInfo) match_info = NULL;
+ MMPco *pco = NULL;
+ gint num_matches;
+
+ if (!pco_info || !pco_info[0]) {
+ g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_INVALID_ARGS,
+ "No PCO info given");
+ return NULL;
+ }
+
+ /* Expected %PCOINFO response:
+ *
+ * Solicited response: %PCOINFO:<mode>,<cid>[,<pcoid>[,<payload>]]
+ * Unsolicited response: %PCOINFO:<cid>,<pcoid>[,<payload>]
+ */
+ regex = g_regex_new ("\\%PCOINFO:(?:\\s*\\d+\\s*,)?(\\d+)\\s*(,([^,\\)]*),([0-9A-Fa-f]*))?",
+ G_REGEX_DOLLAR_ENDONLY | G_REGEX_RAW,
+ 0, NULL);
+ g_assert (regex);
+
+ if (!g_regex_match_full (regex, pco_info, strlen (pco_info), 0, 0, &match_info, error))
+ return NULL;
+
+ num_matches = g_match_info_get_match_count (match_info);
+ if (num_matches != 5) {
+ g_set_error (error,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Failed to parse substrings, number of matches: %d",
+ num_matches);
+ return NULL;
+ }
+
+ while (g_match_info_matches (match_info)) {
+ guint pco_cid;
+ g_autofree gchar *pco_id = NULL;
+ g_autofree gchar *pco_payload = NULL;
+ g_autofree guint8 *pco_payload_bytes = NULL;
+ gsize pco_payload_bytes_len;
+ guint8 pco_prefix[6];
+ GByteArray *pco_raw;
+ gsize pco_raw_len;
+
+ if (!mm_get_uint_from_match_info (match_info, 1, &pco_cid)) {
+ g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "Couldn't parse CID from PCO info: '%s'", pco_info);
+ break;
+ }
+
+ /* We are only interested in IMS and Internet PDN PCO. */
+ if (pco_cid != MM_ALTAIR_IMS_PDN_CID && pco_cid != MM_ALTAIR_INTERNET_PDN_CID) {
+ g_match_info_next (match_info, error);
+ continue;
+ }
+
+ pco_id = mm_get_string_unquoted_from_match_info (match_info, 3);
+ if (!pco_id) {
+ g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "Couldn't parse PCO ID from PCO info: '%s'", pco_info);
+ break;
+ }
+
+ if (g_strcmp0 (pco_id, "FF00")) {
+ g_match_info_next (match_info, error);
+ continue;
+ }
+
+ pco_payload = mm_get_string_unquoted_from_match_info (match_info, 4);
+ if (!pco_payload) {
+ g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "Couldn't parse PCO payload from PCO info: '%s'", pco_info);
+ break;
+ }
+
+ pco_payload_bytes = mm_utils_hexstr2bin (pco_payload, -1, &pco_payload_bytes_len, error);
+ if (!pco_payload_bytes) {
+ g_prefix_error (error, "Invalid PCO payload from PCO info '%s': ", pco_info);
+ break;
+ }
+
+ /* Protocol Configuration Options (PCO) is an information element with an
+ * identifier (IEI) 0x27 and contains between 3 and 253 octets. See 3GPP TS
+ * 24.008 for more details on PCO.
+ *
+ * NOTE: The standard uses one-based indexing, but to better correlate to the
+ * code, zero-based indexing is used in the description hereinafter.
+ *
+ * Octet | Value
+ * --------+--------------------------------------------
+ * 0 | PCO IEI (= 0x27)
+ * 1 | Length of PCO contents (= total length - 2)
+ * 2 | bit 7 : ext
+ * | bit 6 to 3 : spare (= 0b0000)
+ * | bit 2 to 0 : Configuration protocol
+ * 3 to 4 | Element 1 ID
+ * 5 | Length of element 1 contents
+ * 6 to m | Element 1 contents
+ * ... |
+ */
+ pco_raw_len = sizeof (pco_prefix) + pco_payload_bytes_len;
+ pco_prefix[0] = 0x27;
+ pco_prefix[1] = pco_raw_len - 2;
+ pco_prefix[2] = 0x80;
+ /* Verizon uses element ID 0xFF00 for carrier-specific PCO content. */
+ pco_prefix[3] = 0xFF;
+ pco_prefix[4] = 0x00;
+ pco_prefix[5] = pco_payload_bytes_len;
+
+ pco_raw = g_byte_array_sized_new (pco_raw_len);
+ g_byte_array_append (pco_raw, pco_prefix, sizeof (pco_prefix));
+ g_byte_array_append (pco_raw, pco_payload_bytes, pco_payload_bytes_len);
+
+ pco = mm_pco_new ();
+ mm_pco_set_session_id (pco, pco_cid);
+ mm_pco_set_complete (pco, TRUE);
+ mm_pco_set_data (pco, pco_raw->data, pco_raw->len);
+ break;
+ }
+
+ return pco;
+}