aboutsummaryrefslogtreecommitdiff
path: root/src/mm-generic-gsm.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/mm-generic-gsm.c')
-rw-r--r--src/mm-generic-gsm.c1228
1 files changed, 1035 insertions, 193 deletions
diff --git a/src/mm-generic-gsm.c b/src/mm-generic-gsm.c
index cee1bd6..23378b2 100644
--- a/src/mm-generic-gsm.c
+++ b/src/mm-generic-gsm.c
@@ -20,6 +20,7 @@
#include <stdio.h>
#include <string.h>
#include <ctype.h>
+#include <errno.h>
#include "mm-generic-gsm.h"
#include "mm-modem-gsm-card.h"
@@ -129,6 +130,17 @@ typedef struct {
/* SMS */
GHashTable *sms_present;
+ /* Map from SMS index numbers to parsed PDUs (themselves as hash tables) */
+ GHashTable *sms_contents;
+ /*
+ * Map from multipart SMS reference numbers to SMSMultiPartMessage
+ * structures.
+ */
+ GHashTable *sms_parts;
+ gboolean sms_pdu_mode;
+ gboolean sms_pdu_supported;
+
+ guint sms_fetch_pending;
} MMGenericGsmPrivate;
static void get_registration_status (MMAtSerialPort *port, MMCallbackInfo *info);
@@ -146,11 +158,6 @@ static void reg_state_changed (MMAtSerialPort *port,
GMatchInfo *match_info,
gpointer user_data);
-static void get_reg_status_done (MMAtSerialPort *port,
- GString *response,
- GError *error,
- gpointer user_data);
-
static gboolean handle_reg_status_response (MMGenericGsm *self,
GString *response,
GError **error);
@@ -334,7 +341,7 @@ pin_check_done (MMAtSerialPort *port,
info->error = g_error_new (MM_MODEM_ERROR,
MM_MODEM_ERROR_GENERAL,
"Could not parse PIN request response '%s'",
- response->str);
+ response ? response->str : "(unknown)");
}
}
@@ -1329,6 +1336,242 @@ ciev_received (MMAtSerialPort *port,
/* FIXME: handle roaming and service indicators */
}
+typedef struct {
+ /*
+ * The key index number that refers to this multipart message -
+ * usually the index number of the first part received.
+ */
+ guint index;
+
+ /* Number of parts in the complete message */
+ guint numparts;
+
+ /* Number of parts missing from the message */
+ guint missing;
+
+ /* Array of (index numbers of) message parts, in order */
+ guint *parts;
+} SMSMultiPartMessage;
+
+static void
+sms_cache_insert (MMModem *modem, GHashTable *properties, guint idx)
+{
+ MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (modem);
+ GHashTable *old_properties;
+ GValue *ref;
+
+ ref = g_hash_table_lookup (properties, "concat-reference");
+ if (ref != NULL) {
+ GValue *max, *seq;
+ guint refnum, maxnum, seqnum;
+ SMSMultiPartMessage *mpm;
+
+ max = g_hash_table_lookup (properties, "concat-max");
+ seq = g_hash_table_lookup (properties, "concat-sequence");
+ if (max == NULL || seq == NULL) {
+ /* Internal error - not all required data present */
+ return;
+ }
+
+ refnum = g_value_get_uint (ref);
+ maxnum = g_value_get_uint (max);
+ seqnum = g_value_get_uint (seq);
+
+ if (seqnum > maxnum) {
+ /* Error - SMS says "part N of M", but N > M */
+ return;
+ }
+
+ mpm = g_hash_table_lookup (priv->sms_parts, GUINT_TO_POINTER (refnum));
+ if (mpm == NULL) {
+ /* Create a new one */
+ if (maxnum > 255)
+ maxnum = 255;
+ mpm = g_malloc0 (sizeof (*mpm));
+ mpm->index = idx;
+ mpm->numparts = maxnum;
+ mpm->missing = maxnum;
+ mpm->parts = g_malloc0 (maxnum * sizeof(*mpm->parts));
+ g_hash_table_insert (priv->sms_parts, GUINT_TO_POINTER (refnum),
+ mpm);
+ }
+
+ if (maxnum != mpm->numparts) {
+ /* Error - other messages with this refnum claim a different number of parts */
+ return;
+ }
+
+ if (mpm->parts[seqnum - 1] != 0) {
+ /* Error - two SMS segments have claimed to be the same part of the same message. */
+ return;
+ }
+
+ mpm->parts[seqnum - 1] = idx;
+ mpm->missing--;
+ }
+
+ old_properties = g_hash_table_lookup (priv->sms_contents, GUINT_TO_POINTER (idx));
+ if (old_properties != NULL)
+ g_hash_table_unref (old_properties);
+
+ g_hash_table_insert (priv->sms_contents, GUINT_TO_POINTER (idx),
+ g_hash_table_ref (properties));
+}
+
+/*
+ * Takes a hash table representing a (possibly partial) SMS and
+ * determines if it is the key part of a complete SMS. The complete
+ * SMS, if any, is returned. If there is no such SMS (for example, not
+ * all parts are present yet), NULL is returned. The passed-in hash
+ * table is dereferenced, and the returned hash table is referenced.
+ */
+static GHashTable *
+sms_cache_lookup_full (MMModem *modem,
+ GHashTable *properties,
+ GError **error)
+{
+ MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (modem);
+ int i, refnum, indexnum;
+ SMSMultiPartMessage *mpm;
+ GHashTable *full, *part, *first;
+ GHashTableIter iter;
+ gpointer key, value;
+ char *fulltext;
+ char **textparts;
+ GValue *ref, *idx, *text;
+
+ ref = g_hash_table_lookup (properties, "concat-reference");
+ if (ref == NULL)
+ return properties;
+ refnum = g_value_get_uint (ref);
+
+ idx = g_hash_table_lookup (properties, "index");
+ if (idx == NULL) {
+ g_hash_table_unref (properties);
+ return NULL;
+ }
+
+ indexnum = g_value_get_uint (idx);
+ g_hash_table_unref (properties);
+
+ mpm = g_hash_table_lookup (priv->sms_parts,
+ GUINT_TO_POINTER (refnum));
+ if (mpm == NULL) {
+ g_set_error_literal (error, MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL,
+ "Internal error - no multipart structure for multipart SMS");
+ return NULL;
+ }
+
+ /* Check that this is the key */
+ if (indexnum != mpm->index)
+ return NULL;
+
+ if (mpm->missing != 0)
+ return NULL;
+
+ /* Complete multipart message is present. Assemble it */
+ textparts = g_malloc0((1 + mpm->numparts) * sizeof (*textparts));
+ for (i = 0 ; i < mpm->numparts ; i++) {
+ part = g_hash_table_lookup (priv->sms_contents,
+ GUINT_TO_POINTER (mpm->parts[i]));
+ if (part == NULL) {
+ g_set_error (error, MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL,
+ "Internal error - part %d (index %d) is missing",
+ i, mpm->parts[i]);
+ g_free (textparts);
+ return NULL;
+ }
+ text = g_hash_table_lookup (part, "text");
+ if (text == NULL) {
+ g_set_error (error, MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL,
+ "Internal error - part %d (index %d) has no text element",
+ i, mpm->parts[i]);
+ g_free (textparts);
+ return NULL;
+ }
+ textparts[i] = g_value_dup_string (text);
+ }
+ textparts[i] = NULL;
+ fulltext = g_strjoinv (NULL, textparts);
+ g_strfreev (textparts);
+
+ first = g_hash_table_lookup (priv->sms_contents,
+ GUINT_TO_POINTER (mpm->parts[0]));
+ full = g_hash_table_new_full (g_str_hash, g_str_equal, NULL,
+ simple_free_gvalue);
+ g_hash_table_iter_init (&iter, first);
+ while (g_hash_table_iter_next (&iter, &key, &value)) {
+ const char *keystr = key;
+ if (strncmp (keystr, "concat-", 7) == 0)
+ continue;
+ if (strcmp (keystr, "text") == 0 ||
+ strcmp (keystr, "index") == 0)
+ continue;
+ if (strcmp (keystr, "class") == 0) {
+ GValue *val;
+ val = g_slice_new0 (GValue);
+ g_value_init (val, G_TYPE_UINT);
+ g_value_copy (value, val);
+ g_hash_table_insert (full, key, val);
+ } else {
+ GValue *val;
+ val = g_slice_new0 (GValue);
+ g_value_init (val, G_TYPE_STRING);
+ g_value_copy (value, val);
+ g_hash_table_insert (full, key, val);
+ }
+ }
+
+ g_hash_table_insert (full, "index", simple_uint_value (mpm->index));
+ g_hash_table_insert (full, "text", simple_string_value (fulltext));
+ g_free (fulltext);
+
+ return full;
+}
+
+static void
+cmti_received_has_sms (MMModemGsmSms *modem,
+ GHashTable *properties,
+ GError *error,
+ gpointer user_data)
+{
+ MMGenericGsm *self = MM_GENERIC_GSM (user_data);
+ MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (self);
+ guint idx;
+ gboolean complete;
+ GValue *ref;
+
+ if (properties == NULL)
+ return;
+
+ ref = g_hash_table_lookup (properties, "concat-reference");
+ if (ref == NULL) {
+ /* single-part message */
+ GValue *idxval = g_hash_table_lookup (properties, "index");
+ if (idxval == NULL)
+ return;
+ idx = g_value_get_uint (idxval);
+ complete = TRUE;
+ } else {
+ SMSMultiPartMessage *mpm;
+ mpm = g_hash_table_lookup (priv->sms_parts,
+ GUINT_TO_POINTER (g_value_get_uint (ref)));
+ if (mpm == NULL)
+ return;
+ idx = mpm->index;
+ complete = (mpm->missing == 0);
+ }
+
+ if (complete)
+ mm_modem_gsm_sms_completed (MM_MODEM_GSM_SMS (self), idx, TRUE);
+
+ mm_modem_gsm_sms_received (MM_MODEM_GSM_SMS (self), idx, complete);
+}
+
+static void sms_get_invoke (MMCallbackInfo *info);
+static void sms_get_done (MMAtSerialPort *port, GString *response,
+ GError *error, gpointer user_data);
+
static void
cmti_received (MMAtSerialPort *port,
GMatchInfo *info,
@@ -1336,8 +1579,9 @@ cmti_received (MMAtSerialPort *port,
{
MMGenericGsm *self = MM_GENERIC_GSM (user_data);
MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (self);
+ MMCallbackInfo *cbinfo;
guint idx = 0;
- char *str;
+ char *str, *command;
str = g_match_info_fetch (info, 2);
if (str)
@@ -1351,12 +1595,24 @@ cmti_received (MMAtSerialPort *port,
/* Nothing is currently stored in the hash table - presence is all that matters. */
g_hash_table_insert (priv->sms_present, GINT_TO_POINTER (idx), NULL);
- /* todo: parse pdu to know if the sms is complete */
- mm_modem_gsm_sms_received (MM_MODEM_GSM_SMS (self),
- idx,
- TRUE);
+ /* Retrieve the message */
+ cbinfo = mm_callback_info_new_full (MM_MODEM (user_data),
+ sms_get_invoke,
+ G_CALLBACK (cmti_received_has_sms),
+ user_data);
+ mm_callback_info_set_data (cbinfo,
+ "complete-sms-only",
+ GUINT_TO_POINTER (FALSE),
+ NULL);
- /* todo: send mm_modem_gsm_sms_completed if complete */
+ if (priv->sms_fetch_pending != 0) {
+ mm_err("sms_fetch_pending is %d, not 0", priv->sms_fetch_pending);
+ }
+ priv->sms_fetch_pending = idx;
+
+ command = g_strdup_printf ("+CMGR=%d", idx);
+ mm_at_serial_port_queue_command (port, command, 10, sms_get_done, cbinfo);
+ /* Don't want to signal received here before we have the contents */
}
static void
@@ -1428,6 +1684,96 @@ cusd_enable_cb (MMAtSerialPort *port,
MM_GENERIC_GSM_GET_PRIVATE (user_data)->ussd_enabled = TRUE;
}
+static void
+sms_set_format_cb (MMAtSerialPort *port,
+ GString *response,
+ GError *error,
+ gpointer user_data)
+{
+ MMGenericGsm *self = MM_GENERIC_GSM (user_data);
+ MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (self);
+
+ if (error) {
+ mm_warn ("(%s): failed to set SMS mode, assuming text mode",
+ mm_port_get_device (MM_PORT (port)));
+ priv->sms_pdu_mode = FALSE;
+ priv->sms_pdu_supported = FALSE;
+ } else {
+ mm_info ("(%s): using %s mode for SMS",
+ mm_port_get_device (MM_PORT (port)),
+ priv->sms_pdu_mode ? "PDU" : "text");
+ }
+}
+
+
+#define CMGF_TAG "+CMGF:"
+
+static void
+sms_get_format_cb (MMAtSerialPort *port,
+ GString *response,
+ GError *error,
+ gpointer user_data)
+{
+ MMGenericGsm *self;
+ MMGenericGsmPrivate *priv;
+ const char *reply;
+ GRegex *r;
+ GMatchInfo *match_info;
+ char *s;
+ int min = -1, max = -1;
+
+ if (error) {
+ mm_warn ("(%s): failed to query SMS mode, assuming text mode",
+ mm_port_get_device (MM_PORT (port)));
+ return;
+ }
+
+ /* Strip whitespace and response tag */
+ reply = response->str;
+ 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, NULL);
+ if (!r) {
+ mm_warn ("(%s): failed to parse CMGF query result", mm_port_get_device (MM_PORT (port)));
+ return;
+ }
+
+ self = MM_GENERIC_GSM (user_data);
+ priv = MM_GENERIC_GSM_GET_PRIVATE (self);
+ if (g_regex_match_full (r, reply, strlen (reply), 0, 0, &match_info, NULL)) {
+ 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);
+
+ /* If the modem only supports PDU mode, use PDUs.
+ * FIXME: when the PDU code is more robust, default to PDU if the
+ * modem supports it.
+ */
+ if (min == 0 && max < 1) {
+ /* Will get reset to FALSE on receipt of error */
+ priv->sms_pdu_mode = TRUE;
+ mm_at_serial_port_queue_command (priv->primary, "AT+CMGF=0", 3, sms_set_format_cb, self);
+ } else
+ mm_at_serial_port_queue_command (priv->primary, "AT+CMGF=1", 3, sms_set_format_cb, self);
+
+ /* Save whether PDU mode is supported so we can fall back to it if text fails */
+ if (min == 0)
+ priv->sms_pdu_supported = TRUE;
+ }
+ g_match_info_free (match_info);
+
+ g_regex_unref (r);
+}
+
void
mm_generic_gsm_enable_complete (MMGenericGsm *self,
GError *error,
@@ -1472,6 +1818,9 @@ mm_generic_gsm_enable_complete (MMGenericGsm *self,
/* Enable SMS notifications */
mm_at_serial_port_queue_command (priv->primary, "+CNMI=2,1,2,1,0", 3, NULL, NULL);
+ /* Check and enable the right SMS mode */
+ mm_at_serial_port_queue_command (priv->primary, "AT+CMGF=?", 3, sms_get_format_cb, self);
+
/* Enable USSD notifications */
mm_at_serial_port_queue_command (priv->primary, "+CUSD=1", 3, cusd_enable_cb, self);
@@ -1524,6 +1873,27 @@ enable_done (MMAtSerialPort *port,
}
static void
+enable_power_up_check_needed_done (MMModem *self,
+ guint32 needed,
+ GError *error,
+ gpointer user_data)
+{
+ MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (self);
+ char *cmd = NULL;
+
+ if (needed)
+ g_object_get (G_OBJECT (self), MM_GENERIC_GSM_POWER_UP_CMD, &cmd, NULL);
+ else
+ mm_dbg ("Power-up not needed, skipping...");
+
+ if (cmd && strlen (cmd))
+ mm_at_serial_port_queue_command (MM_AT_SERIAL_PORT (priv->primary), cmd, 5, enable_done, user_data);
+ else
+ enable_done (MM_AT_SERIAL_PORT (priv->primary), NULL, NULL, user_data);
+ g_free (cmd);
+}
+
+static void
init_done (MMAtSerialPort *port,
GString *response,
GError *error,
@@ -1556,12 +1926,13 @@ init_done (MMAtSerialPort *port,
mm_at_serial_port_queue_command (port, cmd, 2, NULL, NULL);
g_free (cmd);
- g_object_get (G_OBJECT (info->modem), MM_GENERIC_GSM_POWER_UP_CMD, &cmd, NULL);
- if (cmd && strlen (cmd))
- mm_at_serial_port_queue_command (port, cmd, 5, enable_done, user_data);
+ /* Plugins can now check if they need the power up command or not */
+ if (MM_GENERIC_GSM_GET_CLASS (info->modem)->do_enable_power_up_check_needed)
+ MM_GENERIC_GSM_GET_CLASS (info->modem)->do_enable_power_up_check_needed (MM_GENERIC_GSM (info->modem),
+ enable_power_up_check_needed_done,
+ info);
else
- enable_done (port, NULL, NULL, user_data);
- g_free (cmd);
+ enable_power_up_check_needed_done (info->modem, TRUE, NULL, info);
}
static void
@@ -1734,12 +2105,29 @@ disable_flash_done (MMSerialPort *port,
}
static void
+mark_disabled (gpointer user_data)
+{
+ MMCallbackInfo *info = user_data;
+ MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (info->modem);
+
+ mm_modem_set_state (MM_MODEM (info->modem),
+ MM_MODEM_STATE_DISABLING,
+ MM_MODEM_STATE_REASON_NONE);
+
+ if (mm_port_get_connected (MM_PORT (priv->primary)))
+ mm_serial_port_flash (MM_SERIAL_PORT (priv->primary), 1000, TRUE, disable_flash_done, info);
+ else
+ disable_flash_done (MM_SERIAL_PORT (priv->primary), NULL, info);
+}
+
+static void
secondary_unsolicited_done (MMAtSerialPort *port,
GString *response,
GError *error,
gpointer user_data)
{
mm_serial_port_close_force (MM_SERIAL_PORT (port));
+ mark_disabled (user_data);
}
static void
@@ -1779,13 +2167,6 @@ disable (MMModem *modem,
update_lac_ci (self, 0, 0, 1);
_internal_update_access_technology (self, MM_MODEM_GSM_ACCESS_TECH_UNKNOWN);
- /* Clean up the secondary port if it's open */
- if (priv->secondary && mm_serial_port_is_open (MM_SERIAL_PORT (priv->secondary))) {
- mm_at_serial_port_queue_command (priv->secondary, "+CREG=0", 3, NULL, NULL);
- mm_at_serial_port_queue_command (priv->secondary, "+CGREG=0", 3, NULL, NULL);
- mm_at_serial_port_queue_command (priv->secondary, "+CMER=0", 3, secondary_unsolicited_done, NULL);
- }
-
info = mm_callback_info_new (modem, callback, user_data);
/* Cache the previous state so we can reset it if the operation fails */
@@ -1795,14 +2176,14 @@ disable (MMModem *modem,
GUINT_TO_POINTER (state),
NULL);
- mm_modem_set_state (MM_MODEM (info->modem),
- MM_MODEM_STATE_DISABLING,
- MM_MODEM_STATE_REASON_NONE);
-
- if (mm_port_get_connected (MM_PORT (priv->primary)))
- mm_serial_port_flash (MM_SERIAL_PORT (priv->primary), 1000, TRUE, disable_flash_done, info);
- else
- disable_flash_done (MM_SERIAL_PORT (priv->primary), NULL, info);
+ /* Clean up the secondary port if it's open */
+ if (priv->secondary && mm_serial_port_is_open (MM_SERIAL_PORT (priv->secondary))) {
+ mm_dbg("Shutting down secondary port");
+ mm_at_serial_port_queue_command (priv->secondary, "+CREG=0", 3, NULL, NULL);
+ mm_at_serial_port_queue_command (priv->secondary, "+CGREG=0", 3, NULL, NULL);
+ mm_at_serial_port_queue_command (priv->secondary, "+CMER=0", 3, secondary_unsolicited_done, info);
+ } else
+ mark_disabled (info);
}
static void
@@ -2470,35 +2851,6 @@ reg_info_updated (MMGenericGsm *self,
}
}
-static void
-convert_operator_from_ucs2 (char **operator)
-{
- const char *p;
- char *converted;
- size_t len;
-
- g_return_if_fail (operator != NULL);
- g_return_if_fail (*operator != NULL);
-
- p = *operator;
- len = strlen (p);
-
- /* Len needs to be a multiple of 4 for UCS2 */
- if ((len < 4) || ((len % 4) != 0))
- return;
-
- while (*p) {
- if (!isxdigit (*p++))
- return;
- }
-
- converted = mm_modem_charset_hex_to_utf8 (*operator, MM_MODEM_CHARSET_UCS2);
- if (converted) {
- g_free (*operator);
- *operator = converted;
- }
-}
-
static char *
parse_operator (const char *reply, MMModemCharset cur_charset)
{
@@ -2528,7 +2880,7 @@ parse_operator (const char *reply, MMModemCharset cur_charset)
* character set.
*/
if (cur_charset == MM_MODEM_CHARSET_UCS2)
- convert_operator_from_ucs2 (&operator);
+ operator = mm_charset_take_and_convert_to_utf8 (operator, MM_MODEM_CHARSET_UCS2);
/* Ensure the operator name is valid UTF-8 so that we can send it
* through D-Bus and such.
@@ -2828,7 +3180,7 @@ handle_reg_status_response (MMGenericGsm *self,
guint32 status = 0;
gulong lac = 0, ci = 0;
gint act = -1;
- gboolean cgreg = FALSE;
+ gboolean cgreg = FALSE, parsed;
guint i;
/* Try to match the response */
@@ -2848,47 +3200,38 @@ handle_reg_status_response (MMGenericGsm *self,
}
/* And parse it */
- if (!mm_gsm_parse_creg_response (match_info, &status, &lac, &ci, &act, &cgreg, error)) {
- g_match_info_free (match_info);
- return FALSE;
- }
-
- /* Success; update cached location information */
- update_lac_ci (self, lac, ci, cgreg ? 1 : 0);
-
- /* Only update access technology if it appeared in the CREG/CGREG response */
- if (act != -1)
- mm_generic_gsm_update_access_technology (self, etsi_act_to_mm_act (act));
-
- if (status >= 0) {
- /* Update cached registration status */
- reg_status_updated (self, cgreg_to_reg_type (cgreg), status, NULL);
+ parsed = mm_gsm_parse_creg_response (match_info, &status, &lac, &ci, &act, &cgreg, error);
+ g_match_info_free (match_info);
+ if (parsed) {
+ /* Success; update cached location information */
+ update_lac_ci (self, lac, ci, cgreg ? 1 : 0);
+
+ /* Only update access technology if it appeared in the CREG/CGREG response */
+ if (act != -1)
+ mm_generic_gsm_update_access_technology (self, etsi_act_to_mm_act (act));
+
+ if (status >= 0) {
+ /* Update cached registration status */
+ reg_status_updated (self, cgreg_to_reg_type (cgreg), status, NULL);
+ }
}
- return TRUE;
+ return parsed;
}
-#define CGREG_TRIED_TAG "cgreg-tried"
+#define CS_ERROR_TAG "cs-error"
+#define CS_DONE_TAG "cs-complete"
+#define PS_ERROR_TAG "ps-error"
+#define PS_DONE_TAG "ps-complete"
static void
-get_reg_status_done (MMAtSerialPort *port,
- GString *response,
- GError *error,
- gpointer user_data)
+check_reg_status_done (MMCallbackInfo *info)
{
- MMCallbackInfo *info = (MMCallbackInfo *) user_data;
- MMGenericGsm *self;
- MMGenericGsmPrivate *priv;
- guint id;
+ MMGenericGsm *self = MM_GENERIC_GSM (info->modem);
+ MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (self);
+ GError *cs_error, *ps_error;
MMModemGsmNetworkRegStatus status;
-
- /* If the modem has already been removed, return without
- * scheduling callback */
- if (mm_callback_info_check_modem_removed (info))
- return;
-
- self = MM_GENERIC_GSM (info->modem);
- priv = MM_GENERIC_GSM_GET_PRIVATE (self);
+ guint id;
/* This function should only get called during the connect sequence when
* polling for registration state, since explicit registration requests
@@ -2896,46 +3239,38 @@ get_reg_status_done (MMAtSerialPort *port,
*/
g_return_if_fail (info == priv->pending_reg_info);
- if (error) {
- gboolean cgreg_tried = !!mm_callback_info_get_data (info, CGREG_TRIED_TAG);
-
- /* If this was a +CREG error, try +CGREG. Some devices (blackberries)
- * respond to +CREG with an error but return a valid +CGREG response.
- * So try both. If we get an error from both +CREG and +CGREG, that's
- * obviously a hard fail.
- */
- if (cgreg_tried == FALSE) {
- mm_callback_info_set_data (info, CGREG_TRIED_TAG, GUINT_TO_POINTER (TRUE), NULL);
- mm_at_serial_port_queue_command (port, "+CGREG?", 10, get_reg_status_done, info);
- return;
- } else {
- info->error = g_error_copy (error);
- goto reg_done;
- }
- }
-
- /* The unsolicited registration state handlers will intercept the CREG
- * response and update the cached registration state for us, so we usually
- * just need to check the cached state here.
- */
+ /* Only process when both CS and PS checks are both done */
+ if ( !mm_callback_info_get_data (info, CS_DONE_TAG)
+ || !mm_callback_info_get_data (info, PS_DONE_TAG))
+ return;
- if (strlen (response->str)) {
- /* But just in case the unsolicited handlers doesn't do it... */
- if (!handle_reg_status_response (self, response, &info->error))
- goto reg_done;
+ /* If both CS and PS registration checks returned errors we fail */
+ cs_error = mm_callback_info_get_data (info, CS_ERROR_TAG);
+ ps_error = mm_callback_info_get_data (info, PS_ERROR_TAG);
+ if (cs_error && ps_error) {
+ /* Prefer the PS error */
+ info->error = g_error_copy (ps_error);
+ goto reg_done;
}
status = gsm_reg_status (self, NULL);
if ( status != MM_MODEM_GSM_NETWORK_REG_STATUS_HOME
&& status != MM_MODEM_GSM_NETWORK_REG_STATUS_ROAMING
&& status != MM_MODEM_GSM_NETWORK_REG_STATUS_DENIED) {
+
+ /* Clear state that should be reset every poll */
+ mm_callback_info_set_data (info, CS_DONE_TAG, NULL, NULL);
+ mm_callback_info_set_data (info, CS_ERROR_TAG, NULL, NULL);
+ mm_callback_info_set_data (info, PS_DONE_TAG, NULL, NULL);
+ mm_callback_info_set_data (info, PS_ERROR_TAG, NULL, NULL);
+
/* If we're still waiting for automatic registration to complete or
* fail, check again in a few seconds.
*/
id = g_timeout_add_seconds (1, reg_status_again, info);
mm_callback_info_set_data (info, REG_STATUS_AGAIN_TAG,
- GUINT_TO_POINTER (id),
- reg_status_again_remove);
+ GUINT_TO_POINTER (id),
+ reg_status_again_remove);
return;
}
@@ -2945,9 +3280,63 @@ reg_done:
}
static void
+generic_reg_status_done (MMCallbackInfo *info,
+ GString *response,
+ GError *error,
+ const char *error_tag,
+ const char *done_tag)
+{
+ MMGenericGsm *self = MM_GENERIC_GSM (info->modem);
+ GError *local = NULL;
+
+ if (error)
+ local = g_error_copy (error);
+ else if (strlen (response->str)) {
+ /* Unsolicited registration status handlers will usually process the
+ * response for us, but just in case they don't, do that here.
+ */
+ if (handle_reg_status_response (self, response, &local) == TRUE)
+ g_assert_no_error (local);
+ }
+
+ if (local)
+ mm_callback_info_set_data (info, error_tag, local, (GDestroyNotify) g_error_free);
+ mm_callback_info_set_data (info, done_tag, GUINT_TO_POINTER (1), NULL);
+}
+
+static void
+get_ps_reg_status_done (MMAtSerialPort *port,
+ GString *response,
+ GError *error,
+ gpointer user_data)
+{
+ MMCallbackInfo *info = (MMCallbackInfo *) user_data;
+
+ if (mm_callback_info_check_modem_removed (info) == FALSE) {
+ generic_reg_status_done (info, response, error, PS_ERROR_TAG, PS_DONE_TAG);
+ check_reg_status_done (info);
+ }
+}
+
+static void
+get_cs_reg_status_done (MMAtSerialPort *port,
+ GString *response,
+ GError *error,
+ gpointer user_data)
+{
+ MMCallbackInfo *info = (MMCallbackInfo *) user_data;
+
+ if (mm_callback_info_check_modem_removed (info) == FALSE) {
+ generic_reg_status_done (info, response, error, CS_ERROR_TAG, CS_DONE_TAG);
+ check_reg_status_done (info);
+ }
+}
+
+static void
get_registration_status (MMAtSerialPort *port, MMCallbackInfo *info)
{
- mm_at_serial_port_queue_command (port, "+CREG?", 10, get_reg_status_done, info);
+ mm_at_serial_port_queue_command (port, "+CREG?", 10, get_cs_reg_status_done, info);
+ mm_at_serial_port_queue_command (port, "+CGREG?", 10, get_ps_reg_status_done, info);
}
static void
@@ -2976,6 +3365,10 @@ register_done (MMAtSerialPort *port,
if (priv->pending_reg_info) {
g_warn_if_fail (info == priv->pending_reg_info);
+ if (error) {
+ g_clear_error (&info->error);
+ info->error = g_error_copy (error);
+ }
/* Don't use cached registration state here since it could be up to
* 30 seconds old. Get fresh registration state.
@@ -3064,9 +3457,9 @@ do_register (MMModemGsmNetwork *modem,
* complete. For those devices that delay the +COPS response (hso) the
* callback will be called from register_done(). For those devices that
* return the +COPS response immediately, we'll poll the registration state
- * and call the callback from get_reg_status_done() in response to the
- * polled response. The registration timeout will only be triggered when
- * the +COPS response is never received.
+ * and call the callback from get_[cs|ps]_reg_status_done() in response to
+ * the polled response. The registration timeout will only be triggered
+ * when the +COPS response is never received.
*/
mm_callback_info_ref (info);
@@ -3547,9 +3940,13 @@ cid_range_read (MMAtSerialPort *port,
g_match_info_next (match_info, NULL);
}
- if (cid == 0)
+ if (cid == 0) {
/* Choose something */
cid = 1;
+ }
+
+ g_match_info_free (match_info);
+ g_regex_unref (r);
}
} else
info->error = g_error_new_literal (MM_MODEM_ERROR,
@@ -3780,7 +4177,7 @@ get_csq_done (MMAtSerialPort *port,
gpointer user_data)
{
MMCallbackInfo *info = (MMCallbackInfo *) user_data;
- char *reply = response->str;
+ char *reply;
gboolean parsed = FALSE;
/* If the modem has already been removed, return without
@@ -3793,6 +4190,7 @@ get_csq_done (MMAtSerialPort *port,
goto done;
}
+ reply = response->str;
if (!strncmp (reply, "+CSQ: ", 6)) {
/* Got valid reply */
int quality;
@@ -4191,7 +4589,22 @@ mm_generic_gsm_get_charset (MMGenericGsm *self)
/*****************************************************************************/
/* MMModemGsmSms interface */
+static void
+sms_send_invoke (MMCallbackInfo *info)
+{
+ MMModemGsmSmsSendFn callback = (MMModemGsmSmsSendFn) info->callback;
+
+ callback (MM_MODEM_GSM_SMS (info->modem),
+ mm_callback_info_get_data (info, "indexes"),
+ info->error,
+ info->user_data);
+}
+static void
+free_indexes (gpointer data)
+{
+ g_array_free ((GArray *) data, TRUE);
+}
static void
sms_send_done (MMAtSerialPort *port,
@@ -4200,15 +4613,59 @@ sms_send_done (MMAtSerialPort *port,
gpointer user_data)
{
MMCallbackInfo *info = (MMCallbackInfo *) user_data;
+ const char *p, *pdu;
+ unsigned long num;
+ GArray *indexes = NULL;
+ guint32 idx = 0;
+ guint cmgs_pdu_size;
+ char *command;
/* If the modem has already been removed, return without
* scheduling callback */
if (mm_callback_info_check_modem_removed (info))
return;
- if (error)
- info->error = g_error_copy (error);
+ if (error) {
+ MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (info->modem);
+ /* If there was an error sending in text mode the retry with the PDU;
+ * text mode is pretty dumb on most devices and often fails. Later we'll
+ * just use text mode exclusively.
+ */
+ pdu = mm_callback_info_get_data (info, "pdu");
+ if (priv->sms_pdu_mode == FALSE && priv->sms_pdu_supported && pdu) {
+ cmgs_pdu_size = GPOINTER_TO_UINT (mm_callback_info_get_data (info, "cmgs-pdu-size"));
+ g_assert (cmgs_pdu_size);
+ command = g_strdup_printf ("+CMGS=%d\r%s\x1a", cmgs_pdu_size, pdu);
+ mm_at_serial_port_queue_command (port, command, 10, sms_send_done, info);
+ g_free (command);
+
+ /* Clear the PDU data so we don't keep getting here */
+ mm_callback_info_set_data (info, "pdu", NULL, NULL);
+ return;
+ }
+
+ /* Otherwise it's a hard error */
+ info->error = g_error_copy (error);
+ } else {
+ /* If the response happens to have a ">" in it from the interactive
+ * handling of the CMGS command, skip it.
+ */
+ p = strchr (response->str, '+');
+ if (p && *p) {
+ /* Check for the message index */
+ p = mm_strip_tag (p, "+CMGS:");
+ if (p && *p) {
+ errno = 0;
+ num = strtoul (p, NULL, 10);
+ if ((num < G_MAXUINT32) && (errno == 0))
+ idx = (guint32) num;
+ }
+ }
+ indexes = g_array_sized_new (FALSE, TRUE, sizeof (guint32), 1);
+ g_array_append_val (indexes, idx);
+ mm_callback_info_set_data (info, "indexes", indexes, free_indexes);
+ }
mm_callback_info_schedule (info);
}
@@ -4219,31 +4676,59 @@ sms_send (MMModemGsmSms *modem,
const char *smsc,
guint validity,
guint class,
- MMModemFn callback,
+ MMModemGsmSmsSendFn callback,
gpointer user_data)
{
MMCallbackInfo *info;
+ MMGenericGsm *self = MM_GENERIC_GSM (modem);
+ MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (self);
char *command;
MMAtSerialPort *port;
+ guint8 *pdu;
+ guint pdulen = 0, msgstart = 0;
+ char *hex;
- info = mm_callback_info_new (MM_MODEM (modem), callback, user_data);
+ info = mm_callback_info_new_full (MM_MODEM (modem),
+ sms_send_invoke,
+ G_CALLBACK (callback),
+ user_data);
- port = mm_generic_gsm_get_best_at_port (MM_GENERIC_GSM (modem), &info->error);
+ port = mm_generic_gsm_get_best_at_port (self, &info->error);
if (!port) {
mm_callback_info_schedule (info);
return;
}
- /* FIXME: use the PDU mode instead */
- mm_at_serial_port_queue_command (port, "AT+CMGF=1", 3, NULL, NULL);
+ /* Always create a PDU since we might need it for fallback from text mode */
+ pdu = sms_create_submit_pdu (number, text, smsc, validity, class, &pdulen, &msgstart, &info->error);
+ if (!pdu) {
+ mm_callback_info_schedule (info);
+ return;
+ }
+
+ hex = utils_bin2hexstr (pdu, pdulen);
+ g_free (pdu);
+ if (hex == NULL) {
+ g_set_error_literal (&info->error,
+ MM_MODEM_ERROR,
+ MM_MODEM_ERROR_GENERAL,
+ "Not enough memory to send SMS PDU");
+ mm_callback_info_schedule (info);
+ return;
+ }
+
+ mm_callback_info_set_data (info, "pdu", hex, g_free);
+ mm_callback_info_set_data (info, "cmgs-pdu-size", GUINT_TO_POINTER (pdulen - msgstart), NULL);
+ if (priv->sms_pdu_mode) {
+ /* CMGS length is the size of the PDU without SMSC information */
+ command = g_strdup_printf ("+CMGS=%d\r%s\x1a", pdulen - msgstart, hex);
+ } else
+ command = g_strdup_printf ("+CMGS=\"%s\"\r%s\x1a", number, text);
- command = g_strdup_printf ("+CMGS=\"%s\"\r%s\x1a", number, text);
mm_at_serial_port_queue_command (port, command, 10, sms_send_done, info);
g_free (command);
}
-
-
static void
sms_get_done (MMAtSerialPort *port,
GString *response,
@@ -4251,9 +4736,15 @@ sms_get_done (MMAtSerialPort *port,
gpointer user_data)
{
MMCallbackInfo *info = (MMCallbackInfo *) user_data;
+ MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (info->modem);
GHashTable *properties;
- int rv, status, tpdu_len, offset;
+ int rv, status, tpdu_len;
+ guint idx;
char pdu[SMS_MAX_PDU_LEN + 1];
+ gboolean look_for_complete;
+
+ idx = priv->sms_fetch_pending;
+ priv->sms_fetch_pending = 0;
/* If the modem has already been removed, return without
* scheduling callback */
@@ -4266,9 +4757,9 @@ sms_get_done (MMAtSerialPort *port,
}
/* 344 == SMS_MAX_PDU_LEN */
- rv = sscanf (response->str, "+CMGR: %d,,%d %344s %n",
- &status, &tpdu_len, pdu, &offset);
- if (rv != 4) {
+ rv = sscanf (response->str, "+CMGR: %d,,%d %344s",
+ &status, &tpdu_len, pdu);
+ if (rv != 3) {
info->error = g_error_new (MM_MODEM_ERROR,
MM_MODEM_ERROR_GENERAL,
"Failed to parse CMGR response (parsed %d items)",
@@ -4281,8 +4772,24 @@ sms_get_done (MMAtSerialPort *port,
goto out;
}
- mm_callback_info_set_data (info, GS_HASH_TAG, properties,
- (GDestroyNotify) g_hash_table_unref);
+ g_hash_table_insert (properties, "index", simple_uint_value (idx));
+ sms_cache_insert (info->modem, properties, idx);
+
+ look_for_complete = GPOINTER_TO_UINT (mm_callback_info_get_data(info,
+ "complete-sms-only"));
+
+ if (look_for_complete == TRUE) {
+ /*
+ * If this is a standalone message, or the key part of a
+ * multipart message, pass it along, otherwise report that there's
+ * no such message.
+ */
+ properties = sms_cache_lookup_full (info->modem, properties,
+ &info->error);
+ }
+ if (properties)
+ mm_callback_info_set_data (info, GS_HASH_TAG, properties,
+ (GDestroyNotify) g_hash_table_unref);
out:
mm_callback_info_schedule (info);
@@ -4307,11 +4814,35 @@ sms_get (MMModemGsmSms *modem,
MMCallbackInfo *info;
char *command;
MMAtSerialPort *port;
+ MMGenericGsmPrivate *priv =
+ MM_GENERIC_GSM_GET_PRIVATE (MM_GENERIC_GSM (modem));
+ GHashTable *properties;
+ GError *error = NULL;
+
+ properties = g_hash_table_lookup (priv->sms_contents, GUINT_TO_POINTER (idx));
+ if (properties != NULL) {
+ g_hash_table_ref (properties);
+ properties = sms_cache_lookup_full (MM_MODEM (modem), properties, &error);
+ if (properties == NULL) {
+ error = g_error_new (MM_MODEM_ERROR,
+ MM_MODEM_ERROR_GENERAL,
+ "No SMS found");
+ }
+ callback (modem, properties, error, user_data);
+ if (properties != NULL)
+ g_hash_table_unref (properties);
+ g_error_free (error);
+ return;
+ }
info = mm_callback_info_new_full (MM_MODEM (modem),
sms_get_invoke,
G_CALLBACK (callback),
user_data);
+ mm_callback_info_set_data (info,
+ "complete-sms-only",
+ GUINT_TO_POINTER (TRUE),
+ NULL);
port = mm_generic_gsm_get_best_at_port (MM_GENERIC_GSM (modem), &info->error);
if (!port) {
@@ -4319,10 +4850,18 @@ sms_get (MMModemGsmSms *modem,
return;
}
- command = g_strdup_printf ("+CMGR=%d\r\n", idx);
+ command = g_strdup_printf ("+CMGR=%d", idx);
+ priv->sms_fetch_pending = idx;
mm_at_serial_port_queue_command (port, command, 10, sms_get_done, info);
}
+typedef struct {
+ MMGenericGsmPrivate *priv;
+ MMCallbackInfo *info;
+ SMSMultiPartMessage *mpm;
+ int deleting;
+} SMSDeleteProgress;
+
static void
sms_delete_done (MMAtSerialPort *port,
GString *response,
@@ -4343,6 +4882,50 @@ sms_delete_done (MMAtSerialPort *port,
}
static void
+sms_delete_multi_next (MMAtSerialPort *port,
+ GString *response,
+ GError *error,
+ gpointer user_data)
+{
+ SMSDeleteProgress *progress = (SMSDeleteProgress *)user_data;
+
+ /* If the modem has already been removed, return without
+ * scheduling callback */
+ if (mm_callback_info_check_modem_removed (progress->info))
+ goto done;
+
+ if (error)
+ progress->info->error = g_error_copy (error);
+
+ for (progress->deleting++ ;
+ progress->deleting < progress->mpm->numparts ;
+ progress->deleting++)
+ if (progress->mpm->parts[progress->deleting] != 0)
+ break;
+ if (progress->deleting < progress->mpm->numparts) {
+ GHashTable *properties;
+ char *command;
+ guint idx;
+
+ idx = progress->mpm->parts[progress->deleting];
+ command = g_strdup_printf ("+CMGD=%d", idx);
+ mm_at_serial_port_queue_command (port, command, 10,
+ sms_delete_multi_next, progress);
+ properties = g_hash_table_lookup (progress->priv->sms_contents, GUINT_TO_POINTER (idx));
+ g_hash_table_remove (progress->priv->sms_contents, GUINT_TO_POINTER (idx));
+ g_hash_table_remove (progress->priv->sms_present, GUINT_TO_POINTER (idx));
+ g_hash_table_unref (properties);
+ return;
+ }
+
+ mm_callback_info_schedule (progress->info);
+done:
+ g_free (progress->mpm->parts);
+ g_free (progress->mpm);
+ g_free (progress);
+}
+
+static void
sms_delete (MMModemGsmSms *modem,
guint idx,
MMModemFn callback,
@@ -4352,18 +4935,262 @@ sms_delete (MMModemGsmSms *modem,
char *command;
MMAtSerialPort *port;
MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (MM_GENERIC_GSM (modem));
+ GHashTable *properties;
+ MMAtSerialResponseFn next_callback;
+ GValue *ref;
info = mm_callback_info_new (MM_MODEM (modem), callback, user_data);
- g_hash_table_remove (priv->sms_present, GINT_TO_POINTER (idx));
+
+ properties = g_hash_table_lookup (priv->sms_contents, GINT_TO_POINTER (idx));
+ if (properties == NULL) {
+ /*
+ * TODO(njw): This assumes our cache is valid. If we doubt this, we should just
+ * run the delete anyway and let that return the nonexistent-message error.
+ */
+ info->error = g_error_new (MM_MODEM_ERROR,
+ MM_MODEM_ERROR_GENERAL,
+ "No SMS to delete");
+ mm_callback_info_schedule (info);
+ return;
+ }
port = mm_generic_gsm_get_best_at_port (MM_GENERIC_GSM (modem), &info->error);
if (!port) {
mm_callback_info_schedule (info);
+ g_hash_table_remove (priv->sms_contents, GINT_TO_POINTER (idx));
+ g_hash_table_unref (properties);
return;
}
- command = g_strdup_printf ("+CMGD=%d\r\n", idx);
- mm_at_serial_port_queue_command (port, command, 10, sms_delete_done, info);
+ user_data = info;
+ next_callback = sms_delete_done;
+ ref = g_hash_table_lookup (properties, "concat-reference");
+ if (ref != NULL) {
+ SMSMultiPartMessage *mpm;
+ SMSDeleteProgress *progress;
+ guint refnum;
+
+ refnum = g_value_get_uint (ref);
+ mpm = g_hash_table_lookup (priv->sms_parts, GUINT_TO_POINTER (refnum));
+ if (mpm == NULL) {
+ info->error = g_error_new (MM_MODEM_ERROR,
+ MM_MODEM_ERROR_GENERAL,
+ "Internal error - no part array for multipart SMS");
+ mm_callback_info_schedule (info);
+ g_hash_table_remove (priv->sms_contents, GINT_TO_POINTER (idx));
+ g_hash_table_unref (properties);
+ return;
+ }
+ /* Only allow the delete operation on the main index number. */
+ if (idx != mpm->index) {
+ info->error = g_error_new (MM_MODEM_ERROR,
+ MM_MODEM_ERROR_GENERAL,
+ "No SMS to delete");
+ mm_callback_info_schedule (info);
+ return;
+ }
+
+ g_hash_table_remove (priv->sms_parts, GUINT_TO_POINTER (refnum));
+ progress = g_malloc0 (sizeof(*progress));
+ progress->priv = priv;
+ progress->info = info;
+ progress->mpm = mpm;
+ for (progress->deleting = 0 ;
+ progress->deleting < mpm->numparts ;
+ progress->deleting++)
+ if (mpm->parts[progress->deleting] != 0)
+ break;
+ user_data = progress;
+ next_callback = sms_delete_multi_next;
+ idx = progress->mpm->parts[progress->deleting];
+ properties = g_hash_table_lookup (priv->sms_contents, GINT_TO_POINTER (idx));
+ }
+ g_hash_table_remove (priv->sms_contents, GUINT_TO_POINTER (idx));
+ g_hash_table_remove (priv->sms_present, GUINT_TO_POINTER (idx));
+ g_hash_table_unref (properties);
+
+ command = g_strdup_printf ("+CMGD=%d", idx);
+ mm_at_serial_port_queue_command (port, command, 10, next_callback,
+ user_data);
+}
+
+static gboolean
+pdu_parse_cmgl (MMGenericGsm *self, const char *response, GError **error)
+{
+ int rv, status, tpdu_len, offset;
+ GHashTable *properties;
+
+ while (*response) {
+ int idx;
+ char pdu[SMS_MAX_PDU_LEN + 1];
+
+ rv = sscanf (response, "+CMGL: %d,%d,,%d %344s %n",
+ &idx, &status, &tpdu_len, pdu, &offset);
+ if (4 != rv) {
+ g_set_error (error,
+ MM_MODEM_ERROR,
+ MM_MODEM_ERROR_GENERAL,
+ "Failed to parse CMGL response: expected 4 results got %d", rv);
+ mm_err("Couldn't parse response to SMS LIST (%d)", rv);
+ return FALSE;
+ }
+ response += offset;
+
+ properties = sms_parse_pdu (pdu, NULL);
+ if (properties) {
+ g_hash_table_insert (properties, "index", simple_uint_value (idx));
+ sms_cache_insert (MM_MODEM (self), properties, idx);
+ /* The cache holds a reference, so we don't need it anymore */
+ g_hash_table_unref (properties);
+ }
+ }
+
+ return TRUE;
+}
+
+static gboolean
+get_match_uint (GMatchInfo *m, guint match_index, guint *out_val)
+{
+ char *s;
+ unsigned long num;
+
+ g_return_val_if_fail (out_val != NULL, FALSE);
+
+ s = g_match_info_fetch (m, match_index);
+ g_return_val_if_fail (s != NULL, FALSE);
+
+ errno = 0;
+ num = strtoul (s, NULL, 10);
+ g_free (s);
+
+ if (num <= 1000 && errno == 0) {
+ *out_val = (guint) num;
+ return TRUE;
+ }
+ return FALSE;
+}
+
+static char *
+get_match_string_unquoted (GMatchInfo *m, guint match_index)
+{
+ char *s, *p, *q, *ret = NULL;
+
+ q = s = g_match_info_fetch (m, match_index);
+ g_return_val_if_fail (s != NULL, FALSE);
+
+ /* remove quotes */
+ if (*q == '"')
+ q++;
+ p = strchr (q, '"');
+ if (p)
+ *p = '\0';
+ if (*q)
+ ret = g_strdup (q);
+ g_free (s);
+ return ret;
+}
+
+static gboolean
+text_parse_cmgl (MMGenericGsm *self, const char *response, GError **error)
+{
+ MMGenericGsmPrivate *priv;
+ GRegex *r;
+ GMatchInfo *match_info = NULL;
+
+ priv = MM_GENERIC_GSM_GET_PRIVATE (self);
+
+ /* +CMGL: <index>,<stat>,<oa/da>,[alpha],<scts><CR><LF><data><CR><LF> */
+ r = g_regex_new ("\\+CMGL:\\s*(\\d+)\\s*,\\s*([^,]*),\\s*([^,]*),\\s*([^,]*),\\s*([^\\r\\n]*)\\r\\n([^\\r\\n]*)", 0, 0, NULL);
+ g_assert (r);
+
+ if (!g_regex_match_full (r, response, strlen (response), 0, 0, &match_info, NULL)) {
+ g_set_error_literal (error, MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL,
+ "Failed to parse CMGL response");
+ mm_err("Couldn't parse response to SMS LIST");
+ g_regex_unref (r);
+ return FALSE;
+ }
+
+ while (g_match_info_matches (match_info)) {
+ GHashTable *properties;
+ guint matches, idx;
+ char *number = NULL, *timestamp, *text, *ucs2_text;
+ gsize ucs2_len = 0;
+ GByteArray *data;
+
+ matches = g_match_info_get_match_count (match_info);
+ if (matches != 7) {
+ mm_dbg ("Failed to match entire CMGL response (count %d)", matches);
+ goto next;
+ }
+
+ if (!get_match_uint (match_info, 1, &idx)) {
+ mm_dbg ("Failed to convert message index");
+ goto next;
+ }
+
+ /* <stat is ignored for now> */
+
+ /* Get and parse number */
+ number = get_match_string_unquoted (match_info, 3);
+ if (!number) {
+ mm_dbg ("Failed to get message sender number");
+ goto next;
+ }
+ number = mm_charset_take_and_convert_to_utf8 (number,
+ priv->cur_charset);
+
+ /* Get and parse timestamp (always expected in ASCII) */
+ timestamp = get_match_string_unquoted (match_info, 5);
+
+ /* Get and parse text */
+ text = mm_charset_take_and_convert_to_utf8 (g_match_info_fetch (match_info, 6),
+ priv->cur_charset);
+
+ /* The raw SMS data can only be GSM, UCS2, or unknown (8-bit), so we
+ * need to convert to UCS2 here.
+ */
+ ucs2_text = g_convert (text, -1, "UCS-2BE//TRANSLIT", "UTF-8", NULL, &ucs2_len, NULL);
+ g_assert (ucs2_text);
+ data = g_byte_array_sized_new (ucs2_len);
+ g_byte_array_append (data, (const guint8 *) ucs2_text, ucs2_len);
+ g_free (ucs2_text);
+
+ properties = sms_properties_hash_new (NULL,
+ number,
+ timestamp,
+ text,
+ data,
+ 2, /* DCS = UCS2 */
+ 0); /* class */
+ g_assert (properties);
+
+ g_free (number);
+ g_free (timestamp);
+ g_free (text);
+ g_byte_array_free (data, TRUE);
+
+ g_hash_table_insert (properties, "index", simple_uint_value (idx));
+ sms_cache_insert (MM_MODEM (self), properties, idx);
+ /* The cache holds a reference, so we don't need it anymore */
+ g_hash_table_unref (properties);
+
+next:
+ g_match_info_next (match_info, NULL);
+ }
+ g_match_info_free (match_info);
+
+ g_regex_unref (r);
+ return TRUE;
+}
+
+static void
+free_list_results (gpointer data)
+{
+ GPtrArray *results = (GPtrArray *) data;
+
+ g_ptr_array_foreach (results, (GFunc) g_hash_table_unref, NULL);
+ g_ptr_array_free (results, TRUE);
}
static void
@@ -4373,54 +5200,44 @@ sms_list_done (MMAtSerialPort *port,
gpointer user_data)
{
MMCallbackInfo *info = (MMCallbackInfo *) user_data;
+ MMGenericGsm *self = MM_GENERIC_GSM (info->modem);
+ MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (self);
+ GHashTableIter iter;
+ GHashTable *properties = NULL;
GPtrArray *results = NULL;
- int rv, status, tpdu_len, offset;
- char *rstr;
+ gboolean success;
/* If the modem has already been removed, return without
* scheduling callback */
if (mm_callback_info_check_modem_removed (info))
return;
- if (error)
+ if (error) {
info->error = g_error_copy (error);
- else {
+ mm_callback_info_schedule (info);
+ return;
+ }
+
+ if (priv->sms_pdu_mode)
+ success = pdu_parse_cmgl (self, response->str, &info->error);
+ else
+ success = text_parse_cmgl (self, response->str, &info->error);
+
+ if (success) {
results = g_ptr_array_new ();
- rstr = response->str;
-
- while (*rstr) {
- GHashTable *properties;
- GError *local;
- int idx;
- char pdu[SMS_MAX_PDU_LEN + 1];
-
- rv = sscanf (rstr, "+CMGL: %d,%d,,%d %344s %n",
- &idx, &status, &tpdu_len, pdu, &offset);
- if (4 != rv) {
- mm_err("Couldn't parse response to SMS LIST (%d)", rv);
- break;
- }
- rstr += offset;
- properties = sms_parse_pdu (pdu, &local);
- if (properties) {
- g_hash_table_insert (properties, "index",
- simple_uint_value (idx));
+ /* Add all the complete messages to the results */
+ g_hash_table_iter_init (&iter, priv->sms_contents);
+ while (g_hash_table_iter_next (&iter, NULL, (gpointer) &properties)) {
+ g_hash_table_ref (properties);
+ g_clear_error (&info->error);
+ properties = sms_cache_lookup_full (info->modem, properties, NULL);
+ if (properties)
g_ptr_array_add (results, properties);
- } else {
- /* Ignore the error */
- g_clear_error(&local);
- }
}
- /*
- * todo(njw): mm_gsm_destroy_scan_data does what we want
- * (destroys a GPtrArray of g_hash_tables), but it should be
- * renamed to describe that or there should be a function
- * named for what we're doing here.
- */
+
if (results)
- mm_callback_info_set_data (info, "list-sms", results,
- mm_gsm_destroy_scan_data);
+ mm_callback_info_set_data (info, "list-sms", results, free_list_results);
}
mm_callback_info_schedule (info);
@@ -4456,7 +5273,10 @@ sms_list (MMModemGsmSms *modem,
return;
}
- command = g_strdup_printf ("+CMGL=4\r\n");
+ if (MM_GENERIC_GSM_GET_PRIVATE (modem)->sms_pdu_mode)
+ command = g_strdup_printf ("+CMGL=4");
+ else
+ command = g_strdup_printf ("+CMGL=\"ALL\"");
mm_at_serial_port_queue_command (port, command, 10, sms_list_done, info);
}
@@ -4544,6 +5364,7 @@ decode_ussd_response (MMGenericGsm *self,
char **items, **iter, *p;
char *str = NULL;
gint encoding = -1;
+ char *decoded;
/* Look for the first ',' */
p = strchr (reply, ',');
@@ -4563,6 +5384,9 @@ decode_ussd_response (MMGenericGsm *self,
}
}
+ if (!str)
+ return NULL;
+
/* Strip quotes */
if (str[0] == '"')
str++;
@@ -4570,8 +5394,9 @@ decode_ussd_response (MMGenericGsm *self,
if (p)
*p = '\0';
- return mm_modem_gsm_ussd_decode (MM_MODEM_GSM_USSD (self), str,
- cur_charset);
+ decoded = mm_modem_gsm_ussd_decode (MM_MODEM_GSM_USSD (self), str, cur_charset);
+ g_strfreev (items);
+ return decoded;
}
static char*
@@ -4708,7 +5533,7 @@ ussd_send_done (MMAtSerialPort *port,
ussd_update_state (MM_GENERIC_GSM (info->modem), MM_MODEM_GSM_USSD_STATE_IDLE);
}
- /* Otherwise if no error wait for the response to show up via the
+ /* Otherwise if no error wait for the response to show up via the
* unsolicited response code.
*/
}
@@ -5150,10 +5975,20 @@ simple_status_got_signal_quality (MMModem *modem,
{
MMCallbackInfo *info = (MMCallbackInfo *) user_data;
GHashTable *properties;
+ gboolean error_no_network = FALSE;
- if (!error) {
+ /* Treat "no network" as zero strength */
+ if (g_error_matches (error, MM_MOBILE_ERROR, MM_MOBILE_ERROR_NO_NETWORK)) {
+ error_no_network = TRUE;
+ result = 0;
+ }
+
+ if (!error || error_no_network) {
properties = (GHashTable *) mm_callback_info_get_data (info, SS_HASH_TAG);
g_hash_table_insert (properties, "signal_quality", simple_uint_value (result));
+ } else {
+ g_clear_error (&info->error);
+ info->error = g_error_copy (error);
}
mm_callback_info_chain_complete_one (info);
}
@@ -5170,6 +6005,9 @@ simple_status_got_band (MMModem *modem,
if (!error) {
properties = (GHashTable *) mm_callback_info_get_data (info, SS_HASH_TAG);
g_hash_table_insert (properties, "band", simple_uint_value (result));
+ } else {
+ g_clear_error (&info->error);
+ info->error = g_error_copy (error);
}
mm_callback_info_chain_complete_one (info);
}
@@ -5189,9 +6027,10 @@ simple_status_got_reg_info (MMModemGsmNetwork *modem,
if (!modem || mm_callback_info_check_modem_removed (info))
return;
- if (error)
+ if (error) {
+ g_clear_error (&info->error);
info->error = g_error_copy (error);
- else {
+ } else {
properties = (GHashTable *) mm_callback_info_get_data (info, SS_HASH_TAG);
g_hash_table_insert (properties, "registration_status", simple_uint_value (status));
@@ -5503,6 +6342,8 @@ mm_generic_gsm_init (MMGenericGsm *self)
priv->reg_regex = mm_gsm_creg_regex_get (TRUE);
priv->roam_allowed = TRUE;
priv->sms_present = g_hash_table_new (g_direct_hash, g_direct_equal);
+ priv->sms_contents = g_hash_table_new (g_direct_hash, g_direct_equal);
+ priv->sms_parts = g_hash_table_new (g_direct_hash, g_direct_equal);
mm_properties_changed_signal_register_property (G_OBJECT (self),
MM_MODEM_GSM_NETWORK_ALLOWED_MODE,
@@ -5722,6 +6563,8 @@ finalize (GObject *object)
g_free (priv->oper_name);
g_free (priv->simid);
g_hash_table_destroy (priv->sms_present);
+ g_hash_table_destroy (priv->sms_contents);
+ g_hash_table_destroy (priv->sms_parts);
G_OBJECT_CLASS (mm_generic_gsm_parent_class)->finalize (object);
}
@@ -5841,4 +6684,3 @@ mm_generic_gsm_class_init (MMGenericGsmClass *klass)
"+IFC=1,1",
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
}
-