aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/mm-generic-gsm.c243
-rw-r--r--src/mm-generic-gsm.h2
2 files changed, 184 insertions, 61 deletions
diff --git a/src/mm-generic-gsm.c b/src/mm-generic-gsm.c
index bc718d12..d814c57c 100644
--- a/src/mm-generic-gsm.c
+++ b/src/mm-generic-gsm.c
@@ -120,7 +120,11 @@ typedef struct {
gboolean loc_enabled;
gboolean loc_signal;
+ gboolean ussd_enabled;
+ MMCallbackInfo *pending_ussd_info;
MMModemGsmUssdState ussd_state;
+ char *ussd_network_request;
+ char *ussd_network_notification;
/* SMS */
GHashTable *sms_present;
@@ -174,6 +178,10 @@ static void cmti_received (MMAtSerialPort *port,
GMatchInfo *info,
gpointer user_data);
+static void cusd_received (MMAtSerialPort *port,
+ GMatchInfo *info,
+ gpointer user_data);
+
#define GS_HASH_TAG "get-sms"
static GValue *simple_string_value (const char *str);
static GValue *simple_uint_value (guint32 i);
@@ -844,6 +852,10 @@ mm_generic_gsm_grab_port (MMGenericGsm *self,
mm_at_serial_port_add_unsolicited_msg_handler (MM_AT_SERIAL_PORT (port), regex, cmti_received, self, NULL);
g_regex_unref (regex);
+ regex = g_regex_new ("\\r\\n\\+CUSD:\\s*(.*)\\r\\n", G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
+ mm_at_serial_port_add_unsolicited_msg_handler (MM_AT_SERIAL_PORT (port), regex, cusd_received, self, NULL);
+ g_regex_unref (regex);
+
if (ptype == MM_PORT_TYPE_PRIMARY) {
priv->primary = MM_AT_SERIAL_PORT (port);
if (!priv->data) {
@@ -1412,6 +1424,21 @@ cind_cb (MMAtSerialPort *port,
}
}
+static void
+cusd_enable_cb (MMAtSerialPort *port,
+ GString *response,
+ GError *error,
+ gpointer user_data)
+{
+ if (error) {
+ mm_warn ("(%s): failed to enable USSD notifications.",
+ mm_port_get_device (MM_PORT (port)));
+ return;
+ }
+
+ MM_GENERIC_GSM_GET_PRIVATE (user_data)->ussd_enabled = TRUE;
+}
+
void
mm_generic_gsm_enable_complete (MMGenericGsm *self,
GError *error,
@@ -1462,6 +1489,9 @@ mm_generic_gsm_enable_complete (MMGenericGsm *self,
mm_at_serial_port_queue_command (priv->primary, cmd, 3, NULL, NULL);
g_free (cmd);
+ /* Enable USSD notifications */
+ mm_at_serial_port_queue_command (priv->primary, "+CUSD=1", 3, cusd_enable_cb, self);
+
mm_at_serial_port_queue_command (priv->primary, "+CIND=?", 3, cind_cb, self);
/* Try one more time to get the SIM ID */
@@ -1689,6 +1719,11 @@ disable_flash_done (MMSerialPort *port,
mm_at_serial_port_queue_command (MM_AT_SERIAL_PORT (port), "+CREG=0", 3, NULL, NULL);
mm_at_serial_port_queue_command (MM_AT_SERIAL_PORT (port), "+CGREG=0", 3, NULL, NULL);
+ if (priv->ussd_enabled) {
+ mm_at_serial_port_queue_command (MM_AT_SERIAL_PORT (port), "+CUSD=0", 3, NULL, NULL);
+ priv->ussd_enabled = FALSE;
+ }
+
if (priv->cmer_enabled) {
mm_at_serial_port_queue_command (MM_AT_SERIAL_PORT (port), "+CMER=0", 3, NULL, NULL);
@@ -1732,6 +1767,8 @@ disable (MMModem *modem,
mm_generic_gsm_pending_registration_stop (MM_GENERIC_GSM (modem));
+ mm_generic_gsm_ussd_cleanup (MM_GENERIC_GSM (modem));
+
if (priv->poll_id) {
g_source_remove (priv->poll_id);
priv->poll_id = 0;
@@ -4676,93 +4713,171 @@ ussd_update_state (MMGenericGsm *self, MMModemGsmUssdState new_state)
}
}
+void
+mm_generic_gsm_ussd_cleanup (MMGenericGsm *self)
+{
+ MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (self);
+
+ if (priv->pending_ussd_info) {
+ /* And schedule the callback */
+ g_clear_error (&priv->pending_ussd_info->error);
+ priv->pending_ussd_info->error = g_error_new_literal (MM_MODEM_ERROR,
+ MM_MODEM_ERROR_GENERAL,
+ "USSD session terminated without reply.");
+ mm_callback_info_schedule (priv->pending_ussd_info);
+ priv->pending_ussd_info = NULL;
+ }
+
+ ussd_update_state (self, MM_MODEM_GSM_USSD_STATE_IDLE);
+
+ g_free (priv->ussd_network_request);
+ priv->ussd_network_request = NULL;
+ g_object_notify (G_OBJECT (self), MM_MODEM_GSM_USSD_NETWORK_REQUEST);
+
+ g_free (priv->ussd_network_notification);
+ priv->ussd_network_notification = NULL;
+ g_object_notify (G_OBJECT (self), MM_MODEM_GSM_USSD_NETWORK_NOTIFICATION);
+}
+
+static char *
+decode_ussd_response (const char *reply, MMModemCharset cur_charset)
+{
+ char **items, **iter, *p;
+ char *str = NULL;
+ gint encoding = -1;
+
+ /* Look for the first ',' */
+ p = strchr (reply, ',');
+ if (p == NULL)
+ return NULL;
+
+ items = g_strsplit_set (p + 1, " ,", -1);
+ for (iter = items; iter && *iter; iter++) {
+ if (*iter[0] == '\0')
+ continue;
+ if (str == NULL)
+ str = *iter;
+ else if (encoding == -1) {
+ encoding = atoi (*iter);
+ mm_dbg ("USSD data coding scheme %d", encoding);
+ break; /* All done */
+ }
+ }
+
+ /* Strip quotes */
+ if (str[0] == '"')
+ str++;
+ p = strchr (str, '"');
+ if (p)
+ *p = '\0';
+
+ /* FIXME: actually use the given encoding scheme */
+ return mm_modem_charset_hex_to_utf8 (str, cur_charset);
+}
+
static void
-ussd_send_done (MMAtSerialPort *port,
- GString *response,
- GError *error,
- gpointer user_data)
+cusd_received (MMAtSerialPort *port,
+ GMatchInfo *info,
+ gpointer user_data)
{
- MMCallbackInfo *info = (MMCallbackInfo *) user_data;
- MMGenericGsmPrivate *priv;
+ MMGenericGsm *self = MM_GENERIC_GSM (user_data);
+ MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (self);
+ GError *error = NULL;
gint status;
- gboolean parsed = FALSE;
MMModemGsmUssdState ussd_state = MM_MODEM_GSM_USSD_STATE_IDLE;
- const char *str, *start = NULL, *end = NULL;
char *reply = NULL, *converted;
- /* If the modem has already been removed, return without
- * scheduling callback */
- if (mm_callback_info_check_modem_removed (info))
+ reply = g_match_info_fetch (info, 1);
+ if (!reply || !isdigit (*reply)) {
+ mm_warn ("Recieved invalid USSD response: '%s'", reply ? reply : "(none)");
+ g_free (reply);
return;
-
- if (error) {
- info->error = g_error_copy (error);
- goto done;
}
- priv = MM_GENERIC_GSM_GET_PRIVATE (info->modem);
- ussd_state = priv->ussd_state;
-
- str = mm_strip_tag (response->str, "+CUSD:");
- if (!str || !isdigit (*str))
- goto done;
-
- status = g_ascii_digit_value (*str);
+ status = g_ascii_digit_value (*reply);
switch (status) {
case 0: /* no further action required */
- ussd_state = MM_MODEM_GSM_USSD_STATE_IDLE;
+ converted = decode_ussd_response (reply, priv->cur_charset);
+ if (priv->pending_ussd_info) {
+ /* Response to the user's request */
+ mm_callback_info_set_result (priv->pending_ussd_info, converted, g_free);
+ } else {
+ /* Network-initiated USSD-Notify */
+ g_free (priv->ussd_network_notification);
+ priv->ussd_network_notification = converted;
+ g_object_notify (G_OBJECT (self), MM_MODEM_GSM_USSD_NETWORK_NOTIFICATION);
+ }
break;
case 1: /* further action required */
ussd_state = MM_MODEM_GSM_USSD_STATE_USER_RESPONSE;
+ converted = decode_ussd_response (reply, priv->cur_charset);
+ if (priv->pending_ussd_info) {
+ mm_callback_info_set_result (priv->pending_ussd_info, converted, g_free);
+ } else {
+ /* Network-initiated USSD-Request */
+ g_free (priv->ussd_network_request);
+ priv->ussd_network_request = converted;
+ g_object_notify (G_OBJECT (self), MM_MODEM_GSM_USSD_NETWORK_REQUEST);
+ }
break;
case 2:
- info->error = g_error_new (MM_MODEM_ERROR,
- MM_MODEM_ERROR_GENERAL,
- "USSD terminated by network.");
- ussd_state = MM_MODEM_GSM_USSD_STATE_IDLE;
+ error = g_error_new_literal (MM_MODEM_ERROR,
+ MM_MODEM_ERROR_GENERAL,
+ "USSD terminated by network.");
break;
case 4:
- info->error = g_error_new (MM_MODEM_ERROR,
- MM_MODEM_ERROR_GENERAL,
- "Operiation not supported.");
- ussd_state = MM_MODEM_GSM_USSD_STATE_IDLE;
+ error = g_error_new_literal (MM_MODEM_ERROR,
+ MM_MODEM_ERROR_GENERAL,
+ "Operation not supported.");
break;
default:
- info->error = g_error_new (MM_MODEM_ERROR,
- MM_MODEM_ERROR_GENERAL,
- "Unknown USSD reply %d", status);
- ussd_state = MM_MODEM_GSM_USSD_STATE_IDLE;
+ error = g_error_new (MM_MODEM_ERROR,
+ MM_MODEM_ERROR_GENERAL,
+ "Unhandled USSD reply %d", status);
break;
}
- if (info->error)
- goto done;
- /* look for the reply */
- if ((start = strchr (str, '"')) && (end = strrchr (str, '"')) && (start != end))
- reply = g_strndup (start + 1, end - start -1);
+ ussd_update_state (self, ussd_state);
- if (reply) {
- /* look for the reply data coding scheme */
- if ((start = strrchr (end, ',')) != NULL)
- mm_dbg ("USSD data coding scheme %d", atoi (start + 1));
-
- converted = mm_modem_charset_hex_to_utf8 (reply, priv->cur_charset);
- mm_callback_info_set_result (info, converted, g_free);
- parsed = TRUE;
- g_free (reply);
+ if (priv->pending_ussd_info) {
+ if (error)
+ priv->pending_ussd_info->error = g_error_copy (error);
+ mm_callback_info_schedule (priv->pending_ussd_info);
+ priv->pending_ussd_info = NULL;
}
-done:
- if (!parsed && !info->error) {
- info->error = g_error_new (MM_MODEM_ERROR,
- MM_MODEM_ERROR_GENERAL,
- "Could not parse USSD reply '%s'",
- response->str);
+ g_clear_error (&error);
+ g_free (reply);
+}
+
+static void
+ussd_send_done (MMAtSerialPort *port,
+ GString *response,
+ GError *error,
+ gpointer user_data)
+{
+ MMCallbackInfo *info = (MMCallbackInfo *) user_data;
+ MMGenericGsmPrivate *priv;
+
+ /* If the modem has already been removed, return without
+ * scheduling callback */
+ if (mm_callback_info_check_modem_removed (info))
+ return;
+
+ priv = MM_GENERIC_GSM_GET_PRIVATE (info->modem);
+
+ if (error) {
+ /* Some immediate error happened when sending the USSD request */
+ info->error = g_error_copy (error);
+ priv->pending_ussd_info = NULL;
+ mm_callback_info_schedule (info);
+
+ ussd_update_state (MM_GENERIC_GSM (info->modem), MM_MODEM_GSM_USSD_STATE_IDLE);
}
- mm_callback_info_schedule (info);
- if (info->modem)
- ussd_update_state (MM_GENERIC_GSM (info->modem), ussd_state);
+ /* Otherwise if no error wait for the response to show up via the
+ * unsolicited response code.
+ */
}
static void
@@ -4779,6 +4894,8 @@ ussd_send (MMModemGsmUssd *modem,
MMAtSerialPort *port;
gboolean success;
+ g_warn_if_fail (priv->pending_ussd_info == NULL);
+
info = mm_callback_info_string_new (MM_MODEM (modem), callback, user_data);
port = mm_generic_gsm_get_best_at_port (MM_GENERIC_GSM (modem), &info->error);
@@ -4787,6 +4904,9 @@ ussd_send (MMModemGsmUssd *modem,
return;
}
+ /* Cache the callback info since the response is an unsolicited one */
+ priv->pending_ussd_info = info;
+
/* encode to cur_charset */
success = mm_modem_charset_byte_array_append (ussd_command, command, FALSE, priv->cur_charset);
g_warn_if_fail (success == TRUE);
@@ -5737,10 +5857,10 @@ get_property (GObject *object, guint prop_id,
g_value_set_string (value, ussd_state_to_string (priv->ussd_state));
break;
case MM_GENERIC_GSM_PROP_USSD_NETWORK_REQUEST:
- g_value_set_string (value, "");
+ g_value_set_string (value, priv->ussd_network_request);
break;
case MM_GENERIC_GSM_PROP_USSD_NETWORK_NOTIFICATION:
- g_value_set_string (value, "");
+ g_value_set_string (value, priv->ussd_network_notification);
break;
case MM_GENERIC_GSM_PROP_FLOW_CONTROL_CMD:
/* By default, try to set XOFF/XON flow control */
@@ -5769,6 +5889,7 @@ finalize (GObject *object)
MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (object);
mm_generic_gsm_pending_registration_stop (MM_GENERIC_GSM (object));
+ mm_generic_gsm_ussd_cleanup (MM_GENERIC_GSM (object));
if (priv->pin_check_timeout) {
g_source_remove (priv->pin_check_timeout);
diff --git a/src/mm-generic-gsm.h b/src/mm-generic-gsm.h
index 5afab3c8..57b65bf8 100644
--- a/src/mm-generic-gsm.h
+++ b/src/mm-generic-gsm.h
@@ -159,6 +159,8 @@ MMModem *mm_generic_gsm_new (const char *device,
void mm_generic_gsm_pending_registration_stop (MMGenericGsm *modem);
+void mm_generic_gsm_ussd_cleanup (MMGenericGsm *modem);
+
gint mm_generic_gsm_get_cid (MMGenericGsm *modem);
void mm_generic_gsm_set_reg_status (MMGenericGsm *modem,