summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAleksander Morgado <aleksander@aleksander.es>2018-08-07 00:35:14 +0200
committerAleksander Morgado <aleksander@aleksander.es>2018-08-12 13:53:07 +0200
commitc2900fe5832f85b56fe089d03d713a77897ae518 (patch)
tree64db2da21a57e17dc2e152e7030b9e7af26dd12f
parent8f095f7d6c8ff9731c4944ca0d4048003eb80f36 (diff)
downloadModemManager-aleksander/mbim-ussd.tar.gz
broadband-modem-mbim: implement 3GPP USSD supportaleksander/mbim-ussd
-rw-r--r--src/mm-broadband-modem-mbim.c622
1 files changed, 617 insertions, 5 deletions
diff --git a/src/mm-broadband-modem-mbim.c b/src/mm-broadband-modem-mbim.c
index 40ede015e..894e544aa 100644
--- a/src/mm-broadband-modem-mbim.c
+++ b/src/mm-broadband-modem-mbim.c
@@ -35,6 +35,7 @@
#include "mm-bearer-list.h"
#include "mm-iface-modem.h"
#include "mm-iface-modem-3gpp.h"
+#include "mm-iface-modem-3gpp-ussd.h"
#include "mm-iface-modem-location.h"
#include "mm-iface-modem-messaging.h"
#include "mm-iface-modem-signal.h"
@@ -46,6 +47,7 @@
static void iface_modem_init (MMIfaceModem *iface);
static void iface_modem_3gpp_init (MMIfaceModem3gpp *iface);
+static void iface_modem_3gpp_ussd_init (MMIfaceModem3gppUssd *iface);
static void iface_modem_location_init (MMIfaceModemLocation *iface);
static void iface_modem_messaging_init (MMIfaceModemMessaging *iface);
static void iface_modem_signal_init (MMIfaceModemSignal *iface);
@@ -55,6 +57,7 @@ static MMIfaceModemSignal *iface_modem_signal_parent;
G_DEFINE_TYPE_EXTENDED (MMBroadbandModemMbim, mm_broadband_modem_mbim, MM_TYPE_BROADBAND_MODEM, 0,
G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM, iface_modem_init)
G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_3GPP, iface_modem_3gpp_init)
+ G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_3GPP_USSD, iface_modem_3gpp_ussd_init)
G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_LOCATION, iface_modem_location_init)
G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_MESSAGING, iface_modem_messaging_init)
G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_SIGNAL, iface_modem_signal_init))
@@ -68,6 +71,7 @@ typedef enum {
PROCESS_NOTIFICATION_FLAG_SUBSCRIBER_INFO = 1 << 4,
PROCESS_NOTIFICATION_FLAG_PACKET_SERVICE = 1 << 5,
PROCESS_NOTIFICATION_FLAG_PCO = 1 << 6,
+ PROCESS_NOTIFICATION_FLAG_USSD = 1 << 7,
} ProcessNotificationFlag;
struct _MMBroadbandModemMbimPrivate {
@@ -95,6 +99,9 @@ struct _MMBroadbandModemMbimPrivate {
gchar *current_operator_id;
gchar *current_operator_name;
+ /* USSD helpers */
+ GTask *pending_ussd_action;
+
/* Access technology updates */
MbimDataClass available_data_classes;
MbimDataClass highest_available_data_class;
@@ -2556,6 +2563,25 @@ basic_connect_extensions_notification (MMBroadbandModemMbim *self,
}
static void
+process_ussd_notification (MMBroadbandModemMbim *self,
+ MbimMessage *notification);
+
+static void
+ussd_notification (MMBroadbandModemMbim *self,
+ MbimMessage *notification)
+{
+ if (mbim_message_indicate_status_get_cid (notification) != MBIM_CID_USSD) {
+ mm_warn ("unexpected USSD notification (cid %u)", mbim_message_indicate_status_get_cid (notification));
+ return;
+ }
+
+ if (!(self->priv->setup_flags & PROCESS_NOTIFICATION_FLAG_USSD))
+ return;
+
+ process_ussd_notification (self, notification);
+}
+
+static void
device_notification_cb (MbimDevice *device,
MbimMessage *notification,
MMBroadbandModemMbim *self)
@@ -2578,6 +2604,9 @@ device_notification_cb (MbimDevice *device,
case MBIM_SERVICE_SMS:
sms_notification (self, notification);
break;
+ case MBIM_SERVICE_USSD:
+ ussd_notification (self, notification);
+ break;
default:
/* Ignore */
break;
@@ -2592,14 +2621,15 @@ common_setup_cleanup_unsolicited_events_sync (MMBroadbandModemMbim *self,
if (!device)
return;
- mm_dbg ("Supported notifications: signal (%s), registration (%s), sms (%s), connect (%s), subscriber (%s), packet (%s), pco (%s)",
+ mm_dbg ("Supported notifications: signal (%s), registration (%s), sms (%s), connect (%s), subscriber (%s), packet (%s), pco (%s), ussd (%s)",
self->priv->setup_flags & PROCESS_NOTIFICATION_FLAG_SIGNAL_QUALITY ? "yes" : "no",
self->priv->setup_flags & PROCESS_NOTIFICATION_FLAG_REGISTRATION_UPDATES ? "yes" : "no",
self->priv->setup_flags & PROCESS_NOTIFICATION_FLAG_SMS_READ ? "yes" : "no",
self->priv->setup_flags & PROCESS_NOTIFICATION_FLAG_CONNECT ? "yes" : "no",
self->priv->setup_flags & PROCESS_NOTIFICATION_FLAG_SUBSCRIBER_INFO ? "yes" : "no",
self->priv->setup_flags & PROCESS_NOTIFICATION_FLAG_PACKET_SERVICE ? "yes" : "no",
- self->priv->setup_flags & PROCESS_NOTIFICATION_FLAG_PCO ? "yes" : "no");
+ self->priv->setup_flags & PROCESS_NOTIFICATION_FLAG_PCO ? "yes" : "no",
+ self->priv->setup_flags & PROCESS_NOTIFICATION_FLAG_USSD ? "yes" : "no");
if (setup) {
/* Don't re-enable it if already there */
@@ -2767,16 +2797,17 @@ common_enable_disable_unsolicited_events (MMBroadbandModemMbim *self,
if (!peek_device (self, &device, callback, user_data))
return;
- mm_dbg ("Enabled notifications: signal (%s), registration (%s), sms (%s), connect (%s), subscriber (%s), packet (%s), pco (%s)",
+ mm_dbg ("Enabled notifications: signal (%s), registration (%s), sms (%s), connect (%s), subscriber (%s), packet (%s), pco (%s), ussd (%s)",
self->priv->enable_flags & PROCESS_NOTIFICATION_FLAG_SIGNAL_QUALITY ? "yes" : "no",
self->priv->enable_flags & PROCESS_NOTIFICATION_FLAG_REGISTRATION_UPDATES ? "yes" : "no",
self->priv->enable_flags & PROCESS_NOTIFICATION_FLAG_SMS_READ ? "yes" : "no",
self->priv->enable_flags & PROCESS_NOTIFICATION_FLAG_CONNECT ? "yes" : "no",
self->priv->enable_flags & PROCESS_NOTIFICATION_FLAG_SUBSCRIBER_INFO ? "yes" : "no",
self->priv->enable_flags & PROCESS_NOTIFICATION_FLAG_PACKET_SERVICE ? "yes" : "no",
- self->priv->enable_flags & PROCESS_NOTIFICATION_FLAG_PCO ? "yes" : "no");
+ self->priv->enable_flags & PROCESS_NOTIFICATION_FLAG_PCO ? "yes" : "no",
+ self->priv->enable_flags & PROCESS_NOTIFICATION_FLAG_USSD ? "yes" : "no");
- entries = g_new0 (MbimEventEntry *, 4);
+ entries = g_new0 (MbimEventEntry *, 5);
/* Basic connect service */
if (self->priv->enable_flags & PROCESS_NOTIFICATION_FLAG_SIGNAL_QUALITY ||
@@ -2822,6 +2853,16 @@ common_enable_disable_unsolicited_events (MMBroadbandModemMbim *self,
n_entries++;
}
+ /* USSD service */
+ if (self->priv->enable_flags & PROCESS_NOTIFICATION_FLAG_USSD) {
+ entries[n_entries] = g_new (MbimEventEntry, 1);
+ memcpy (&(entries[n_entries]->device_service_id), MBIM_UUID_USSD, sizeof (MbimUuid));
+ entries[n_entries]->cids_count = 1;
+ entries[n_entries]->cids = g_new0 (guint32, 1);
+ entries[n_entries]->cids[0] = MBIM_CID_USSD;
+ n_entries++;
+ }
+
task = g_task_new (self, NULL, callback, user_data);
request = (mbim_message_device_service_subscribe_list_set_new (
@@ -3585,6 +3626,551 @@ modem_signal_load_values (MMIfaceModemSignal *self,
}
/*****************************************************************************/
+/* Check if USSD supported (3GPP/USSD interface) */
+
+static gboolean
+modem_3gpp_ussd_check_support_finish (MMIfaceModem3gppUssd *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+modem_3gpp_ussd_check_support (MMIfaceModem3gppUssd *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+
+ task = g_task_new (self, NULL, callback, user_data);
+ g_task_return_boolean (task, MM_BROADBAND_MODEM_MBIM (self)->priv->is_ussd_supported);
+ g_object_unref (task);
+}
+
+/*****************************************************************************/
+/* USSD encoding/deconding helpers
+ *
+ * Note: we don't care about subclassing the ussd_encode/decode methods in the
+ * interface, as we're going to use this methods just here.
+ */
+
+static GByteArray *
+ussd_encode (const gchar *command,
+ guint32 *scheme,
+ GError **error)
+{
+ GByteArray *array;
+
+ if (mm_charset_can_convert_to (command, MM_MODEM_CHARSET_GSM)) {
+ guint8 *gsm;
+ guint8 *packed;
+ guint32 len = 0;
+ guint32 packed_len = 0;
+
+ *scheme = MM_MODEM_GSM_USSD_SCHEME_7BIT;
+ gsm = mm_charset_utf8_to_unpacked_gsm (command, &len);
+ if (!gsm) {
+ g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED,
+ "Failed to encode USSD command in GSM7 charset");
+ return NULL;
+ }
+ packed = mm_charset_gsm_pack (gsm, len, 0, &packed_len);
+ g_free (gsm);
+
+ array = g_byte_array_new_take (packed, packed_len);
+ } else {
+ *scheme = MM_MODEM_GSM_USSD_SCHEME_UCS2;
+ array = g_byte_array_sized_new (strlen (command) * 2);
+ if (!mm_modem_charset_byte_array_append (array, command, FALSE, MM_MODEM_CHARSET_UCS2)) {
+ g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED,
+ "Failed to encode USSD command in UCS2 charset");
+ g_byte_array_unref (array);
+ return NULL;
+ }
+ }
+
+ if (array->len > 160) {
+ g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_INVALID_ARGS,
+ "Failed to encode USSD command: encoded data too long (%u > 160)", array->len);
+ g_byte_array_unref (array);
+ return NULL;
+ }
+
+ return array;
+}
+
+static gchar *
+ussd_decode (guint32 scheme,
+ GByteArray *data,
+ GError **error)
+{
+ gchar *decoded = NULL;
+
+ if (scheme == MM_MODEM_GSM_USSD_SCHEME_7BIT) {
+ guint8 *unpacked;
+ guint32 unpacked_len;
+
+ unpacked = mm_charset_gsm_unpack ((const guint8 *)data->data, (data->len * 8) / 7, 0, &unpacked_len);
+ decoded = (gchar *) mm_charset_gsm_unpacked_to_utf8 (unpacked, unpacked_len);
+ if (!decoded)
+ g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED,
+ "Error decoding USSD command in 0x%04x scheme (GSM7 charset)",
+ scheme);
+ } else if (scheme == MM_MODEM_GSM_USSD_SCHEME_UCS2) {
+ decoded = mm_modem_charset_byte_array_to_utf8 (data, MM_MODEM_CHARSET_UCS2);
+ if (!decoded)
+ g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED,
+ "Error decoding USSD command in 0x%04x scheme (UCS2 charset)",
+ scheme);
+ } else
+ g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED,
+ "Failed to decode USSD command in unsupported 0x%04x scheme", scheme);
+
+ return decoded;
+}
+
+/*****************************************************************************/
+/* USSD notifications */
+
+static void
+process_ussd_message (MMBroadbandModemMbim *self,
+ MbimUssdResponse ussd_response,
+ MbimUssdSessionState ussd_session_state,
+ guint32 scheme,
+ guint32 data_size,
+ const guint8 *data)
+{
+ GTask *task = NULL;
+ MMModem3gppUssdSessionState ussd_state = MM_MODEM_3GPP_USSD_SESSION_STATE_IDLE;
+ GByteArray *bytearray = NULL;
+ gchar *converted = NULL;
+ GError *error = NULL;
+
+ /* Steal task and balance out received reference */
+ if (self->priv->pending_ussd_action) {
+ task = self->priv->pending_ussd_action;
+ self->priv->pending_ussd_action = NULL;
+ }
+
+ if (data_size)
+ bytearray = g_byte_array_append (g_byte_array_new (), data, data_size);
+
+ switch (ussd_response) {
+ case MBIM_USSD_RESPONSE_NO_ACTION_REQUIRED:
+ /* no further action required */
+ converted = ussd_decode (scheme, bytearray, &error);
+ if (!converted)
+ break;
+
+ /* Response to the user's request? */
+ if (task)
+ break;
+
+ /* Network-initiated USSD-Notify */
+ mm_iface_modem_3gpp_ussd_update_network_notification (MM_IFACE_MODEM_3GPP_USSD (self), converted);
+ g_clear_pointer (&converted, g_free);
+ break;
+
+ case MBIM_USSD_RESPONSE_ACTION_REQUIRED:
+ /* further action required */
+ ussd_state = MM_MODEM_3GPP_USSD_SESSION_STATE_USER_RESPONSE;
+
+ converted = ussd_decode (scheme, bytearray, &error);
+ if (!converted)
+ break;
+ /* Response to the user's request? */
+ if (task)
+ break;
+
+ /* Network-initiated USSD-Request */
+ mm_iface_modem_3gpp_ussd_update_network_request (MM_IFACE_MODEM_3GPP_USSD (self), converted);
+ g_clear_pointer (&converted, g_free);
+ break;
+
+ case MBIM_USSD_RESPONSE_TERMINATED_BY_NETWORK:
+ error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_CANCELLED, "USSD terminated by network");
+ break;
+
+ case MBIM_USSD_RESPONSE_OTHER_LOCAL_CLIENT:
+ error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_CANCELLED, "Another ongoing USSD operation is in progress");
+ break;
+
+ case MBIM_USSD_RESPONSE_OPERATION_NOT_SUPPORTED:
+ error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_CANCELLED, "Operation not supported");
+ break;
+
+ case MBIM_USSD_RESPONSE_NETWORK_TIMEOUT:
+ error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_CANCELLED, "Network timeout");
+ break;
+
+ default:
+ error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_CANCELLED, "Unknown USSD response (%u)", ussd_response);
+ break;
+ }
+
+ mm_iface_modem_3gpp_ussd_update_state (MM_IFACE_MODEM_3GPP_USSD (self), ussd_state);
+
+ if (bytearray)
+ g_byte_array_unref (bytearray);
+
+ /* Complete the pending action */
+ if (task) {
+ if (error)
+ g_task_return_error (task, error);
+ else if (converted)
+ g_task_return_pointer (task, converted, g_free);
+ else
+ g_assert_not_reached ();
+ return;
+ }
+
+ /* If no pending task, just report the error */
+ if (error) {
+ mm_dbg ("Network reported USSD message: %s", error->message);
+ g_error_free (error);
+ }
+
+ g_assert (!converted);
+}
+
+static void
+process_ussd_notification (MMBroadbandModemMbim *self,
+ MbimMessage *notification)
+{
+ MbimUssdResponse ussd_response;
+ MbimUssdSessionState ussd_session_state;
+ guint32 scheme;
+ guint32 data_size;
+ const guint8 *data;
+
+ if (mbim_message_ussd_notification_parse (notification,
+ &ussd_response,
+ &ussd_session_state,
+ &scheme,
+ &data_size,
+ &data,
+ NULL)) {
+ mm_dbg ("Received USSD indication: %s, session state: %s, scheme: 0x%x, data size: %u bytes",
+ mbim_ussd_response_get_string (ussd_response),
+ mbim_ussd_session_state_get_string (ussd_session_state),
+ scheme,
+ data_size);
+ process_ussd_message (self, ussd_response, ussd_session_state, scheme, data_size, data);
+ }
+}
+
+/*****************************************************************************/
+/* Setup/Cleanup unsolicited result codes (3GPP/USSD interface) */
+
+static gboolean
+modem_3gpp_ussd_setup_cleanup_unsolicited_events_finish (MMIfaceModem3gppUssd *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+common_setup_flag_ussd_ready (MMBroadbandModemMbim *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error = NULL;
+
+ if (!common_setup_cleanup_unsolicited_events_finish (self, res, &error)) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+static void
+common_setup_cleanup_unsolicited_ussd_events (MMBroadbandModemMbim *self,
+ gboolean setup,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+
+ task = g_task_new (self, NULL, callback, user_data);
+ g_task_set_task_data (task, GINT_TO_POINTER (setup), NULL);
+
+ if (setup)
+ self->priv->setup_flags |= PROCESS_NOTIFICATION_FLAG_USSD;
+ else
+ self->priv->setup_flags &= ~PROCESS_NOTIFICATION_FLAG_USSD;
+ common_setup_cleanup_unsolicited_events (self, setup, (GAsyncReadyCallback)common_setup_flag_ussd_ready, task);
+}
+
+static void
+modem_3gpp_ussd_cleanup_unsolicited_events (MMIfaceModem3gppUssd *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ common_setup_cleanup_unsolicited_ussd_events (MM_BROADBAND_MODEM_MBIM (self), FALSE, callback, user_data);
+}
+
+static void
+modem_3gpp_ussd_setup_unsolicited_events (MMIfaceModem3gppUssd *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ common_setup_cleanup_unsolicited_ussd_events (MM_BROADBAND_MODEM_MBIM (self), TRUE, callback, user_data);
+}
+
+/*****************************************************************************/
+/* Enable/Disable URCs (3GPP/USSD interface) */
+
+static gboolean
+modem_3gpp_ussd_enable_disable_unsolicited_events_finish (MMIfaceModem3gppUssd *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return common_enable_disable_unsolicited_events_finish (MM_BROADBAND_MODEM_MBIM (self), res, error);
+}
+
+static void
+modem_3gpp_ussd_disable_unsolicited_events (MMIfaceModem3gppUssd *_self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ MMBroadbandModemMbim *self = MM_BROADBAND_MODEM_MBIM (_self);
+
+ self->priv->enable_flags &= ~PROCESS_NOTIFICATION_FLAG_USSD;
+ common_enable_disable_unsolicited_events (self, callback, user_data);
+}
+
+static void
+modem_3gpp_ussd_enable_unsolicited_events (MMIfaceModem3gppUssd *_self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ MMBroadbandModemMbim *self = MM_BROADBAND_MODEM_MBIM (_self);
+
+ self->priv->enable_flags |= PROCESS_NOTIFICATION_FLAG_USSD;
+ common_enable_disable_unsolicited_events (self, callback, user_data);
+}
+
+/*****************************************************************************/
+/* Send command (3GPP/USSD interface) */
+
+static gchar *
+modem_3gpp_ussd_send_finish (MMIfaceModem3gppUssd *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_pointer (G_TASK (res), error);
+}
+
+static void
+ussd_send_ready (MbimDevice *device,
+ GAsyncResult *res,
+ MMBroadbandModemMbim *self)
+{
+ MbimMessage *response;
+ GError *error = NULL;
+ MbimUssdResponse ussd_response;
+ MbimUssdSessionState ussd_session_state;
+ guint32 scheme;
+ guint32 data_size;
+ const guint8 *data;
+
+ /* Note: if there is a cached task, it is ALWAYS completed here */
+
+ response = mbim_device_command_finish (device, res, &error);
+ if (response &&
+ mbim_message_response_get_result (response, MBIM_MESSAGE_TYPE_COMMAND_DONE, &error) &&
+ mbim_message_ussd_response_parse (response,
+ &ussd_response,
+ &ussd_session_state,
+ &scheme,
+ &data_size,
+ &data,
+ &error)) {
+ mm_dbg ("Received USSD response: %s, session state: %s, scheme: 0x%x, data size: %u bytes",
+ mbim_ussd_response_get_string (ussd_response),
+ mbim_ussd_session_state_get_string (ussd_session_state),
+ scheme,
+ data_size);
+ process_ussd_message (self, ussd_response, ussd_session_state, scheme, data_size, data);
+ } else {
+ /* Report error in the cached task, if any */
+ if (self->priv->pending_ussd_action) {
+ GTask *task;
+
+ task = self->priv->pending_ussd_action;
+ self->priv->pending_ussd_action = NULL;
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ } else {
+ mm_dbg ("Failed to parse USSD response: %s", error->message);
+ g_clear_error (&error);
+ }
+ }
+
+ if (response)
+ mbim_message_unref (response);
+
+ /* Balance out received reference */
+ g_object_unref (self);
+}
+
+static void
+modem_3gpp_ussd_send (MMIfaceModem3gppUssd *_self,
+ const gchar *command,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ MMBroadbandModemMbim *self;
+ MbimDevice *device;
+ GTask *task;
+ MbimUssdAction action;
+ MbimMessage *message;
+ GByteArray *encoded;
+ guint32 scheme = 0;
+ GError *error = NULL;
+
+ self = MM_BROADBAND_MODEM_MBIM (_self);
+ if (!peek_device (self, &device, callback, user_data))
+ return;
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ /* Fail if there is an ongoing operation already */
+ if (self->priv->pending_ussd_action) {
+ g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_IN_PROGRESS,
+ "there is already an ongoing USSD operation");
+ g_object_unref (task);
+ return;
+ }
+
+ switch (mm_iface_modem_3gpp_ussd_get_state (MM_IFACE_MODEM_3GPP_USSD (self))) {
+ case MM_MODEM_3GPP_USSD_SESSION_STATE_IDLE:
+ action = MBIM_USSD_ACTION_INITIATE;
+ break;
+ case MM_MODEM_3GPP_USSD_SESSION_STATE_USER_RESPONSE:
+ action = MBIM_USSD_ACTION_CONTINUE;
+ break;
+ default:
+ g_assert_not_reached ();
+ return;
+ }
+
+ encoded = ussd_encode (command, &scheme, &error);
+ if (!encoded) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ message = mbim_message_ussd_set_new (action, scheme, encoded->len, encoded->data, &error);
+ if (!message) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ /* Cache the action, as it may be completed via URCs */
+ self->priv->pending_ussd_action = task;
+ mm_iface_modem_3gpp_ussd_update_state (_self, MM_MODEM_3GPP_USSD_SESSION_STATE_ACTIVE);
+
+ mbim_device_command (device,
+ message,
+ 100,
+ NULL,
+ (GAsyncReadyCallback)ussd_send_ready,
+ g_object_ref (self)); /* Full reference! */
+ mbim_message_unref (message);
+}
+
+/*****************************************************************************/
+/* Cancel USSD (3GPP/USSD interface) */
+
+static gboolean
+modem_3gpp_ussd_cancel_finish (MMIfaceModem3gppUssd *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+ussd_cancel_ready (MbimDevice *device,
+ GAsyncResult *res,
+ GTask *task)
+{
+ MMBroadbandModemMbim *self;
+ MbimMessage *response;
+ GError *error = NULL;
+
+ self = g_task_get_source_object (task);
+
+ response = mbim_device_command_finish (device, res, &error);
+ if (response)
+ mbim_message_response_get_result (response, MBIM_MESSAGE_TYPE_COMMAND_DONE, &error);
+
+ /* Complete the pending action, regardless of the operation result */
+ if (self->priv->pending_ussd_action) {
+ GTask *task;
+
+ task = self->priv->pending_ussd_action;
+ self->priv->pending_ussd_action = NULL;
+
+ g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_CANCELLED,
+ "USSD session was cancelled");
+ g_object_unref (task);
+ }
+
+ mm_iface_modem_3gpp_ussd_update_state (MM_IFACE_MODEM_3GPP_USSD (self),
+ MM_MODEM_3GPP_USSD_SESSION_STATE_IDLE);
+
+ if (error)
+ g_task_return_error (task, error);
+ else
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+
+ if (response)
+ mbim_message_unref (response);
+}
+
+static void
+modem_3gpp_ussd_cancel (MMIfaceModem3gppUssd *_self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ MMBroadbandModemMbim *self;
+ MbimDevice *device;
+ GTask *task;
+ MbimMessage *message;
+ GError *error = NULL;
+
+ self = MM_BROADBAND_MODEM_MBIM (_self);
+ if (!peek_device (self, &device, callback, user_data))
+ return;
+
+ task = g_task_new (self, NULL, callback, user_data);
+
+ message = mbim_message_ussd_set_new (MBIM_USSD_ACTION_CANCEL, 0, 0, NULL, &error);
+ if (!message) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+ mbim_device_command (device,
+ message,
+ 10,
+ NULL,
+ (GAsyncReadyCallback)ussd_cancel_ready,
+ task);
+ mbim_message_unref (message);
+}
+
+/*****************************************************************************/
/* Check support (Messaging interface) */
static gboolean
@@ -3990,6 +4576,32 @@ iface_modem_3gpp_init (MMIfaceModem3gpp *iface)
}
static void
+iface_modem_3gpp_ussd_init (MMIfaceModem3gppUssd *iface)
+{
+ /* Initialization steps */
+ iface->check_support = modem_3gpp_ussd_check_support;
+ iface->check_support_finish = modem_3gpp_ussd_check_support_finish;
+
+ /* Enabling steps */
+ iface->setup_unsolicited_events = modem_3gpp_ussd_setup_unsolicited_events;
+ iface->setup_unsolicited_events_finish = modem_3gpp_ussd_setup_cleanup_unsolicited_events_finish;
+ iface->enable_unsolicited_events = modem_3gpp_ussd_enable_unsolicited_events;
+ iface->enable_unsolicited_events_finish = modem_3gpp_ussd_enable_disable_unsolicited_events_finish;
+
+ /* Disabling steps */
+ iface->cleanup_unsolicited_events_finish = modem_3gpp_ussd_setup_cleanup_unsolicited_events_finish;
+ iface->cleanup_unsolicited_events = modem_3gpp_ussd_cleanup_unsolicited_events;
+ iface->disable_unsolicited_events = modem_3gpp_ussd_disable_unsolicited_events;
+ iface->disable_unsolicited_events_finish = modem_3gpp_ussd_enable_disable_unsolicited_events_finish;
+
+ /* Additional actions */
+ iface->send = modem_3gpp_ussd_send;
+ iface->send_finish = modem_3gpp_ussd_send_finish;
+ iface->cancel = modem_3gpp_ussd_cancel;
+ iface->cancel_finish = modem_3gpp_ussd_cancel_finish;
+}
+
+static void
iface_modem_location_init (MMIfaceModemLocation *iface)
{
iface->load_capabilities = NULL;