summaryrefslogtreecommitdiff
path: root/src/mm-base-sms.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/mm-base-sms.c')
-rw-r--r--src/mm-base-sms.c2048
1 files changed, 2048 insertions, 0 deletions
diff --git a/src/mm-base-sms.c b/src/mm-base-sms.c
new file mode 100644
index 00000000..bc38a1c5
--- /dev/null
+++ b/src/mm-base-sms.c
@@ -0,0 +1,2048 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details:
+ *
+ * Copyright (C) 2008 - 2009 Novell, Inc.
+ * Copyright (C) 2009 - 2012 Red Hat, Inc.
+ * Copyright (C) 2012 Google, Inc.
+ */
+
+#include <config.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <ctype.h>
+
+#include <ModemManager.h>
+#define _LIBMM_INSIDE_MM
+#include <libmm-glib.h>
+
+#include "mm-base-sms.h"
+#include "mm-broadband-modem.h"
+#include "mm-iface-modem.h"
+#include "mm-iface-modem-messaging.h"
+#include "mm-sms-part-3gpp.h"
+#include "mm-base-modem-at.h"
+#include "mm-base-modem.h"
+#include "mm-log.h"
+#include "mm-modem-helpers.h"
+
+G_DEFINE_TYPE (MMBaseSms, mm_base_sms, MM_GDBUS_TYPE_SMS_SKELETON)
+
+enum {
+ PROP_0,
+ PROP_PATH,
+ PROP_CONNECTION,
+ PROP_MODEM,
+ PROP_IS_MULTIPART,
+ PROP_MAX_PARTS,
+ PROP_MULTIPART_REFERENCE,
+ PROP_LAST
+};
+
+static GParamSpec *properties[PROP_LAST];
+
+struct _MMBaseSmsPrivate {
+ /* The connection to the system bus */
+ GDBusConnection *connection;
+ /* The modem which owns this SMS */
+ MMBaseModem *modem;
+ /* The path where the SMS object is exported */
+ gchar *path;
+
+ /* Multipart SMS specific stuff */
+ gboolean is_multipart;
+ guint multipart_reference;
+
+ /* List of SMS parts */
+ guint max_parts;
+ GList *parts;
+
+ /* Set to true when all needed parts were received,
+ * parsed and assembled */
+ gboolean is_assembled;
+};
+
+/*****************************************************************************/
+
+static guint
+get_validity_relative (GVariant *tuple)
+{
+ guint type;
+ GVariant *value;
+ guint value_integer = 0;
+
+ if (!tuple)
+ return 0;
+
+ g_variant_get (tuple, "(uv)", &type, &value);
+
+ if (type == MM_SMS_VALIDITY_TYPE_RELATIVE)
+ value_integer = g_variant_get_uint32 (value);
+
+ g_variant_unref (value);
+
+ return value_integer;
+}
+
+static gboolean
+generate_3gpp_submit_pdus (MMBaseSms *self,
+ GError **error)
+{
+ guint i;
+ guint n_parts;
+
+ const gchar *text;
+ GVariant *data_variant;
+ const guint8 *data;
+ gsize data_len = 0;
+
+ MMSmsEncoding encoding;
+ gchar **split_text = NULL;
+ GByteArray **split_data = NULL;
+
+ g_assert (self->priv->parts == NULL);
+
+ text = mm_gdbus_sms_get_text (MM_GDBUS_SMS (self));
+ data_variant = mm_gdbus_sms_get_data (MM_GDBUS_SMS (self));
+ data = (data_variant ?
+ g_variant_get_fixed_array (data_variant,
+ &data_len,
+ sizeof (guchar)) :
+ NULL);
+
+ g_assert (text != NULL || data != NULL);
+ g_assert (!(text != NULL && data != NULL));
+
+ if (text) {
+ split_text = mm_sms_part_3gpp_util_split_text (text, &encoding);
+ if (!split_text) {
+ g_set_error (error,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_INVALID_ARGS,
+ "Cannot generate PDUs: Error processing input text");
+ return FALSE;
+ }
+ n_parts = g_strv_length (split_text);
+ } else if (data) {
+ encoding = MM_SMS_ENCODING_8BIT;
+ split_data = mm_sms_part_3gpp_util_split_data (data, data_len);
+ g_assert (split_data != NULL);
+ /* noop within the for */
+ for (n_parts = 0; split_data[n_parts]; n_parts++);
+ } else
+ g_assert_not_reached ();
+
+ g_assert (split_text != NULL || split_data != NULL);
+ g_assert (!(split_text != NULL && split_data != NULL));
+
+ if (n_parts > 255) {
+ if (split_text)
+ g_strfreev (split_text);
+ else if (split_data) {
+ guint i = 0;
+
+ while (split_data[i])
+ g_byte_array_unref (split_data[i++]);
+ g_free (split_data);
+ }
+
+ g_set_error (error,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_TOO_MANY,
+ "Cannot generate PDUs: Text or Data too long");
+ return FALSE;
+ }
+
+ /* Loop text/data chunks */
+ i = 0;
+ while (1) {
+ MMSmsPart *part;
+ gchar *part_text = NULL;
+ GByteArray *part_data = NULL;
+
+ if (split_text) {
+ if (!split_text[i])
+ break;
+ part_text = split_text[i];
+ mm_dbg (" Processing chunk '%u' of text with '%u' bytes",
+ i, (guint) strlen (part_text));
+ } else if (split_data) {
+ if (!split_data[i])
+ break;
+ part_data = split_data[i];
+ mm_dbg (" Processing chunk '%u' of data with '%u' bytes",
+ i, part_data->len);
+
+ } else
+ g_assert_not_reached ();
+
+ /* Create new part */
+ part = mm_sms_part_new (SMS_PART_INVALID_INDEX, MM_SMS_PDU_TYPE_SUBMIT);
+ mm_sms_part_take_text (part, part_text);
+ mm_sms_part_take_data (part, part_data);
+ mm_sms_part_set_encoding (part, encoding);
+ mm_sms_part_set_number (part, mm_gdbus_sms_get_number (MM_GDBUS_SMS (self)));
+ mm_sms_part_set_smsc (part, mm_gdbus_sms_get_smsc (MM_GDBUS_SMS (self)));
+ mm_sms_part_set_validity_relative (part, get_validity_relative (mm_gdbus_sms_get_validity (MM_GDBUS_SMS (self))));
+ mm_sms_part_set_class (part, mm_gdbus_sms_get_class (MM_GDBUS_SMS (self)));
+ mm_sms_part_set_delivery_report_request (part, mm_gdbus_sms_get_delivery_report_request (MM_GDBUS_SMS (self)));
+
+ if (n_parts > 1) {
+ mm_sms_part_set_concat_reference (part, 0); /* We don't set a concat reference here */
+ mm_sms_part_set_concat_sequence (part, i + 1);
+ mm_sms_part_set_concat_max (part, n_parts);
+
+ mm_dbg ("Created SMS part '%u' for multipart SMS ('%u' parts expected)",
+ i + 1, n_parts);
+ } else {
+ mm_dbg ("Created SMS part for singlepart SMS");
+ }
+
+ /* Add to the list of parts */
+ self->priv->parts = g_list_append (self->priv->parts, part);
+
+ i++;
+ }
+
+ /* Free array (not contents, which were taken for the part) */
+ if (split_text)
+ g_free (split_text);
+ if (split_data)
+ g_free (split_data);
+
+ /* Set additional multipart specific properties */
+ if (n_parts > 1) {
+ self->priv->is_multipart = TRUE;
+ self->priv->max_parts = n_parts;
+ }
+
+ /* No more parts are expected */
+ self->priv->is_assembled = TRUE;
+
+ return TRUE;
+}
+
+static gboolean
+generate_cdma_submit_pdus (MMBaseSms *self,
+ GError **error)
+{
+ const gchar *text;
+ GVariant *data_variant;
+ const guint8 *data;
+ gsize data_len = 0;
+
+ MMSmsPart *part;
+
+ g_assert (self->priv->parts == NULL);
+
+ text = mm_gdbus_sms_get_text (MM_GDBUS_SMS (self));
+ data_variant = mm_gdbus_sms_get_data (MM_GDBUS_SMS (self));
+ data = (data_variant ?
+ g_variant_get_fixed_array (data_variant,
+ &data_len,
+ sizeof (guchar)) :
+ NULL);
+
+ g_assert (text != NULL || data != NULL);
+ g_assert (!(text != NULL && data != NULL));
+
+ /* Create new part */
+ part = mm_sms_part_new (SMS_PART_INVALID_INDEX, MM_SMS_PDU_TYPE_CDMA_SUBMIT);
+ if (text)
+ mm_sms_part_set_text (part, text);
+ else if (data) {
+ GByteArray *part_data;
+
+ part_data = g_byte_array_sized_new (data_len);
+ g_byte_array_append (part_data, data, data_len);
+ mm_sms_part_take_data (part, part_data);
+ } else
+ g_assert_not_reached ();
+ mm_sms_part_set_encoding (part, data ? MM_SMS_ENCODING_8BIT : MM_SMS_ENCODING_UNKNOWN);
+ mm_sms_part_set_number (part, mm_gdbus_sms_get_number (MM_GDBUS_SMS (self)));
+
+ /* If creating a CDMA SMS part but we don't have a Teleservice ID, we default to WMT */
+ if (mm_gdbus_sms_get_teleservice_id (MM_GDBUS_SMS (self)) == MM_SMS_CDMA_TELESERVICE_ID_UNKNOWN) {
+ mm_dbg ("Defaulting to WMT teleservice ID when creating SMS part");
+ mm_sms_part_set_cdma_teleservice_id (part, MM_SMS_CDMA_TELESERVICE_ID_WMT);
+ } else
+ mm_sms_part_set_cdma_teleservice_id (part, mm_gdbus_sms_get_teleservice_id (MM_GDBUS_SMS (self)));
+
+ mm_sms_part_set_cdma_service_category (part, mm_gdbus_sms_get_service_category (MM_GDBUS_SMS (self)));
+
+ mm_dbg ("Created SMS part for CDMA SMS");
+
+ /* Add to the list of parts */
+ self->priv->parts = g_list_append (self->priv->parts, part);
+
+ /* No more parts are expected */
+ self->priv->is_assembled = TRUE;
+
+ return TRUE;
+}
+
+static gboolean
+generate_submit_pdus (MMBaseSms *self,
+ GError **error)
+{
+ MMBaseModem *modem;
+ gboolean is_3gpp;
+
+ /* First; decide which kind of PDU we'll generate, based on the current modem caps */
+
+ g_object_get (self,
+ MM_BASE_SMS_MODEM, &modem,
+ NULL);
+ g_assert (modem != NULL);
+
+ is_3gpp = mm_iface_modem_is_3gpp (MM_IFACE_MODEM (modem));
+ g_object_unref (modem);
+
+ /* On a 3GPP-capable modem, create always a 3GPP SMS (even if the modem is 3GPP+3GPP2) */
+ if (is_3gpp)
+ return generate_3gpp_submit_pdus (self, error);
+
+ /* Otherwise, create a 3GPP2 SMS */
+ return generate_cdma_submit_pdus (self, error);
+}
+
+/*****************************************************************************/
+/* Store SMS (DBus call handling) */
+
+typedef struct {
+ MMBaseSms *self;
+ MMBaseModem *modem;
+ GDBusMethodInvocation *invocation;
+ MMSmsStorage storage;
+} HandleStoreContext;
+
+static void
+handle_store_context_free (HandleStoreContext *ctx)
+{
+ g_object_unref (ctx->invocation);
+ g_object_unref (ctx->modem);
+ g_object_unref (ctx->self);
+ g_free (ctx);
+}
+
+static void
+handle_store_ready (MMBaseSms *self,
+ GAsyncResult *res,
+ HandleStoreContext *ctx)
+{
+ GError *error = NULL;
+
+ if (!MM_BASE_SMS_GET_CLASS (self)->store_finish (self, res, &error)) {
+ /* On error, clear up the parts we generated */
+ g_list_free_full (self->priv->parts, (GDestroyNotify)mm_sms_part_free);
+ self->priv->parts = NULL;
+ g_dbus_method_invocation_take_error (ctx->invocation, error);
+ } else {
+ mm_gdbus_sms_set_storage (MM_GDBUS_SMS (ctx->self), ctx->storage);
+
+ /* Transition from Unknown->Stored for SMS which were created by the user */
+ if (mm_gdbus_sms_get_state (MM_GDBUS_SMS (ctx->self)) == MM_SMS_STATE_UNKNOWN)
+ mm_gdbus_sms_set_state (MM_GDBUS_SMS (ctx->self), MM_SMS_STATE_STORED);
+
+ mm_gdbus_sms_complete_store (MM_GDBUS_SMS (ctx->self), ctx->invocation);
+ }
+
+ handle_store_context_free (ctx);
+}
+
+static gboolean
+prepare_sms_to_be_stored (MMBaseSms *self,
+ GError **error)
+{
+ GList *l;
+ guint8 reference;
+
+ g_assert (self->priv->parts == NULL);
+
+ /* Look for a valid multipart reference to use. When storing, we need to
+ * check whether we have already stored multipart SMS with the same
+ * reference and destination number */
+ reference = (mm_iface_modem_messaging_get_local_multipart_reference (
+ MM_IFACE_MODEM_MESSAGING (self->priv->modem),
+ mm_gdbus_sms_get_number (MM_GDBUS_SMS (self)),
+ error));
+ if (!reference ||
+ !generate_submit_pdus (self, error)) {
+ g_prefix_error (error, "Cannot prepare SMS to be stored: ");
+ return FALSE;
+ }
+
+ /* If the message is a multipart message, we need to set a proper
+ * multipart reference. When sending a message which wasn't stored
+ * yet, we can just get a random multipart reference. */
+ if (self->priv->is_multipart) {
+ self->priv->multipart_reference = reference;
+ for (l = self->priv->parts; l; l = g_list_next (l)) {
+ mm_sms_part_set_concat_reference ((MMSmsPart *)l->data,
+ self->priv->multipart_reference);
+ }
+ }
+
+ return TRUE;
+}
+
+static void
+handle_store_auth_ready (MMBaseModem *modem,
+ GAsyncResult *res,
+ HandleStoreContext *ctx)
+{
+ GError *error = NULL;
+
+ if (!mm_base_modem_authorize_finish (modem, res, &error)) {
+ g_dbus_method_invocation_take_error (ctx->invocation, error);
+ handle_store_context_free (ctx);
+ return;
+ }
+
+ /* First of all, check if we already have the SMS stored. */
+ if (mm_base_sms_get_storage (ctx->self) != MM_SMS_STORAGE_UNKNOWN) {
+ /* Check if SMS stored in some other storage */
+ if (mm_base_sms_get_storage (ctx->self) == ctx->storage)
+ /* Good, same storage */
+ mm_gdbus_sms_complete_store (MM_GDBUS_SMS (ctx->self), ctx->invocation);
+ else
+ g_dbus_method_invocation_return_error (
+ ctx->invocation,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "SMS is already stored in storage '%s', cannot store it in storage '%s'",
+ mm_sms_storage_get_string (mm_base_sms_get_storage (ctx->self)),
+ mm_sms_storage_get_string (ctx->storage));
+ handle_store_context_free (ctx);
+ return;
+ }
+
+ /* Check if the requested storage is allowed for storing */
+ if (!mm_iface_modem_messaging_is_storage_supported_for_storing (MM_IFACE_MODEM_MESSAGING (ctx->modem),
+ ctx->storage,
+ &error)) {
+ g_dbus_method_invocation_take_error (ctx->invocation, error);
+ handle_store_context_free (ctx);
+ return;
+ }
+
+ /* Prepare the SMS to be stored, creating the PDU list if required */
+ if (!prepare_sms_to_be_stored (ctx->self, &error)) {
+ g_dbus_method_invocation_take_error (ctx->invocation, error);
+ handle_store_context_free (ctx);
+ return;
+ }
+
+ /* If not stored, check if we do support doing it */
+ if (!MM_BASE_SMS_GET_CLASS (ctx->self)->store ||
+ !MM_BASE_SMS_GET_CLASS (ctx->self)->store_finish) {
+ g_dbus_method_invocation_return_error (ctx->invocation,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_UNSUPPORTED,
+ "Storing SMS is not supported by this modem");
+ handle_store_context_free (ctx);
+ return;
+ }
+
+ MM_BASE_SMS_GET_CLASS (ctx->self)->store (ctx->self,
+ ctx->storage,
+ (GAsyncReadyCallback)handle_store_ready,
+ ctx);
+}
+
+static gboolean
+handle_store (MMBaseSms *self,
+ GDBusMethodInvocation *invocation,
+ guint32 storage)
+{
+ HandleStoreContext *ctx;
+
+ ctx = g_new0 (HandleStoreContext, 1);
+ ctx->self = g_object_ref (self);
+ ctx->invocation = g_object_ref (invocation);
+ g_object_get (self,
+ MM_BASE_SMS_MODEM, &ctx->modem,
+ NULL);
+ ctx->storage = (MMSmsStorage)storage;
+
+ if (ctx->storage == MM_SMS_STORAGE_UNKNOWN) {
+ /* We'll set now the proper storage, taken from the default mem2 one */
+ g_object_get (self->priv->modem,
+ MM_IFACE_MODEM_MESSAGING_SMS_DEFAULT_STORAGE, &ctx->storage,
+ NULL);
+ g_assert (ctx->storage != MM_SMS_STORAGE_UNKNOWN);
+ }
+
+ mm_base_modem_authorize (ctx->modem,
+ invocation,
+ MM_AUTHORIZATION_MESSAGING,
+ (GAsyncReadyCallback)handle_store_auth_ready,
+ ctx);
+ return TRUE;
+}
+
+/*****************************************************************************/
+/* Send SMS (DBus call handling) */
+
+typedef struct {
+ MMBaseSms *self;
+ MMBaseModem *modem;
+ GDBusMethodInvocation *invocation;
+} HandleSendContext;
+
+static void
+handle_send_context_free (HandleSendContext *ctx)
+{
+ g_object_unref (ctx->invocation);
+ g_object_unref (ctx->modem);
+ g_object_unref (ctx->self);
+ g_free (ctx);
+}
+
+static void
+handle_send_ready (MMBaseSms *self,
+ GAsyncResult *res,
+ HandleSendContext *ctx)
+{
+ GError *error = NULL;
+
+ if (!MM_BASE_SMS_GET_CLASS (self)->send_finish (self, res, &error)) {
+ /* On error, clear up the parts we generated */
+ g_list_free_full (self->priv->parts, (GDestroyNotify)mm_sms_part_free);
+ self->priv->parts = NULL;
+ g_dbus_method_invocation_take_error (ctx->invocation, error);
+ } else {
+ /* Transition from Unknown->Sent or Stored->Sent */
+ if (mm_gdbus_sms_get_state (MM_GDBUS_SMS (ctx->self)) == MM_SMS_STATE_UNKNOWN ||
+ mm_gdbus_sms_get_state (MM_GDBUS_SMS (ctx->self)) == MM_SMS_STATE_STORED) {
+ GList *l;
+
+ /* Update state */
+ mm_gdbus_sms_set_state (MM_GDBUS_SMS (ctx->self), MM_SMS_STATE_SENT);
+ /* Grab last message reference */
+ l = g_list_last (mm_base_sms_get_parts (ctx->self));
+ mm_gdbus_sms_set_message_reference (MM_GDBUS_SMS (ctx->self),
+ mm_sms_part_get_message_reference ((MMSmsPart *)l->data));
+ }
+ mm_gdbus_sms_complete_send (MM_GDBUS_SMS (ctx->self), ctx->invocation);
+ }
+
+ handle_send_context_free (ctx);
+}
+
+static gboolean
+prepare_sms_to_be_sent (MMBaseSms *self,
+ GError **error)
+{
+ GList *l;
+
+ if (self->priv->parts)
+ return TRUE;
+
+ if (!generate_submit_pdus (self, error)) {
+ g_prefix_error (error, "Cannot prepare SMS to be sent: ");
+ return FALSE;
+ }
+
+ /* If the message is a multipart message, we need to set a proper
+ * multipart reference. When sending a message which wasn't stored
+ * yet, we can just get a random multipart reference. */
+ if (self->priv->is_multipart) {
+ self->priv->multipart_reference = g_random_int_range (1,255);
+ for (l = self->priv->parts; l; l = g_list_next (l)) {
+ mm_sms_part_set_concat_reference ((MMSmsPart *)l->data,
+ self->priv->multipart_reference);
+ }
+ }
+
+ return TRUE;
+}
+
+static void
+handle_send_auth_ready (MMBaseModem *modem,
+ GAsyncResult *res,
+ HandleSendContext *ctx)
+{
+ MMSmsState state;
+ GError *error = NULL;
+
+ if (!mm_base_modem_authorize_finish (modem, res, &error)) {
+ g_dbus_method_invocation_take_error (ctx->invocation, error);
+ handle_send_context_free (ctx);
+ return;
+ }
+
+ /* We can only send SMS created by the user */
+ state = mm_gdbus_sms_get_state (MM_GDBUS_SMS (ctx->self));
+ if (state == MM_SMS_STATE_RECEIVED ||
+ state == MM_SMS_STATE_RECEIVING) {
+ g_dbus_method_invocation_return_error (ctx->invocation,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "This SMS was received, cannot send it");
+ handle_send_context_free (ctx);
+ return;
+ }
+
+ /* Don't allow sending the same SMS multiple times, we would lose the message reference */
+ if (state == MM_SMS_STATE_SENT) {
+ g_dbus_method_invocation_return_error (ctx->invocation,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "This SMS was already sent, cannot send it again");
+ handle_send_context_free (ctx);
+ return;
+ }
+
+ /* Prepare the SMS to be sent, creating the PDU list if required */
+ if (!prepare_sms_to_be_sent (ctx->self, &error)) {
+ g_dbus_method_invocation_take_error (ctx->invocation, error);
+ handle_send_context_free (ctx);
+ return;
+ }
+
+ /* Check if we do support doing it */
+ if (!MM_BASE_SMS_GET_CLASS (ctx->self)->send ||
+ !MM_BASE_SMS_GET_CLASS (ctx->self)->send_finish) {
+ g_dbus_method_invocation_return_error (ctx->invocation,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_UNSUPPORTED,
+ "Sending SMS is not supported by this modem");
+ handle_send_context_free (ctx);
+ return;
+ }
+
+ MM_BASE_SMS_GET_CLASS (ctx->self)->send (ctx->self,
+ (GAsyncReadyCallback)handle_send_ready,
+ ctx);
+}
+
+static gboolean
+handle_send (MMBaseSms *self,
+ GDBusMethodInvocation *invocation)
+{
+ HandleSendContext *ctx;
+
+ ctx = g_new0 (HandleSendContext, 1);
+ ctx->self = g_object_ref (self);
+ ctx->invocation = g_object_ref (invocation);
+ g_object_get (self,
+ MM_BASE_SMS_MODEM, &ctx->modem,
+ NULL);
+
+ mm_base_modem_authorize (ctx->modem,
+ invocation,
+ MM_AUTHORIZATION_MESSAGING,
+ (GAsyncReadyCallback)handle_send_auth_ready,
+ ctx);
+ return TRUE;
+}
+
+/*****************************************************************************/
+
+void
+mm_base_sms_export (MMBaseSms *self)
+{
+ static guint id = 0;
+ gchar *path;
+
+ path = g_strdup_printf (MM_DBUS_SMS_PREFIX "/%d", id++);
+ g_object_set (self,
+ MM_BASE_SMS_PATH, path,
+ NULL);
+ g_free (path);
+}
+
+void
+mm_base_sms_unexport (MMBaseSms *self)
+{
+ g_object_set (self,
+ MM_BASE_SMS_PATH, NULL,
+ NULL);
+}
+
+/*****************************************************************************/
+
+static void
+sms_dbus_export (MMBaseSms *self)
+{
+ GError *error = NULL;
+
+ /* Handle method invocations */
+ g_signal_connect (self,
+ "handle-store",
+ G_CALLBACK (handle_store),
+ NULL);
+ g_signal_connect (self,
+ "handle-send",
+ G_CALLBACK (handle_send),
+ NULL);
+
+ if (!g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (self),
+ self->priv->connection,
+ self->priv->path,
+ &error)) {
+ mm_warn ("couldn't export SMS at '%s': '%s'",
+ self->priv->path,
+ error->message);
+ g_error_free (error);
+ }
+}
+
+static void
+sms_dbus_unexport (MMBaseSms *self)
+{
+ /* Only unexport if currently exported */
+ if (g_dbus_interface_skeleton_get_object_path (G_DBUS_INTERFACE_SKELETON (self)))
+ g_dbus_interface_skeleton_unexport (G_DBUS_INTERFACE_SKELETON (self));
+}
+
+/*****************************************************************************/
+
+const gchar *
+mm_base_sms_get_path (MMBaseSms *self)
+{
+ return self->priv->path;
+}
+
+MMSmsStorage
+mm_base_sms_get_storage (MMBaseSms *self)
+{
+ return mm_gdbus_sms_get_storage (MM_GDBUS_SMS (self));
+}
+
+gboolean
+mm_base_sms_is_multipart (MMBaseSms *self)
+{
+ return self->priv->is_multipart;
+}
+
+guint
+mm_base_sms_get_multipart_reference (MMBaseSms *self)
+{
+ g_return_val_if_fail (self->priv->is_multipart, 0);
+
+ return self->priv->multipart_reference;
+}
+
+gboolean
+mm_base_sms_multipart_is_complete (MMBaseSms *self)
+{
+ return (g_list_length (self->priv->parts) == self->priv->max_parts);
+}
+
+gboolean
+mm_base_sms_multipart_is_assembled (MMBaseSms *self)
+{
+ return self->priv->is_assembled;
+}
+
+/*****************************************************************************/
+
+static guint
+cmp_sms_part_index (MMSmsPart *part,
+ gpointer user_data)
+{
+ return (GPOINTER_TO_UINT (user_data) - mm_sms_part_get_index (part));
+}
+
+gboolean
+mm_base_sms_has_part_index (MMBaseSms *self,
+ guint index)
+{
+ return !!g_list_find_custom (self->priv->parts,
+ GUINT_TO_POINTER (index),
+ (GCompareFunc)cmp_sms_part_index);
+}
+
+GList *
+mm_base_sms_get_parts (MMBaseSms *self)
+{
+ return self->priv->parts;
+}
+
+/*****************************************************************************/
+
+static gboolean
+sms_get_store_or_send_command (MMSmsPart *part,
+ gboolean text_or_pdu, /* TRUE for PDU */
+ gboolean store_or_send, /* TRUE for send */
+ gchar **out_cmd,
+ gchar **out_msg_data,
+ GError **error)
+{
+ g_assert (out_cmd != NULL);
+ g_assert (out_msg_data != NULL);
+
+ if (!text_or_pdu) {
+ /* Text mode */
+ *out_cmd = g_strdup_printf ("+CMG%c=\"%s\"",
+ store_or_send ? 'S' : 'W',
+ mm_sms_part_get_number (part));
+ *out_msg_data = g_strdup_printf ("%s\x1a", mm_sms_part_get_text (part));
+ } else {
+ guint8 *pdu;
+ guint pdulen = 0;
+ guint msgstart = 0;
+ gchar *hex;
+
+ /* AT+CMGW=<length>[, <stat>]<CR> PDU can be entered. <CTRL-Z>/<ESC> */
+
+ pdu = mm_sms_part_3gpp_get_submit_pdu (part, &pdulen, &msgstart, error);
+ if (!pdu)
+ /* 'error' should already be set */
+ return FALSE;
+
+ /* Convert PDU to hex */
+ hex = mm_utils_bin2hexstr (pdu, pdulen);
+ g_free (pdu);
+
+ if (!hex) {
+ g_set_error (error,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Not enough memory to send SMS PDU");
+ return FALSE;
+ }
+
+ /* CMGW/S length is the size of the PDU without SMSC information */
+ *out_cmd = g_strdup_printf ("+CMG%c=%d",
+ store_or_send ? 'S' : 'W',
+ pdulen - msgstart);
+ *out_msg_data = g_strdup_printf ("%s\x1a", hex);
+ g_free (hex);
+ }
+
+ return TRUE;
+}
+
+/*****************************************************************************/
+/* Store the SMS */
+
+typedef struct {
+ MMBaseSms *self;
+ MMBaseModem *modem;
+ GSimpleAsyncResult *result;
+ MMSmsStorage storage;
+ gboolean need_unlock;
+ gboolean use_pdu_mode;
+ GList *current;
+ gchar *msg_data;
+} SmsStoreContext;
+
+static void
+sms_store_context_complete_and_free (SmsStoreContext *ctx)
+{
+ g_simple_async_result_complete (ctx->result);
+ g_object_unref (ctx->result);
+ /* Unlock mem2 storage if we had the lock */
+ if (ctx->need_unlock)
+ mm_broadband_modem_unlock_sms_storages (MM_BROADBAND_MODEM (ctx->modem), FALSE, TRUE);
+ g_object_unref (ctx->modem);
+ g_object_unref (ctx->self);
+ g_free (ctx->msg_data);
+ g_free (ctx);
+}
+
+static gboolean
+sms_store_finish (MMBaseSms *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return !g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error);
+}
+
+static void sms_store_next_part (SmsStoreContext *ctx);
+
+static void
+store_msg_data_ready (MMBaseModem *modem,
+ GAsyncResult *res,
+ SmsStoreContext *ctx)
+{
+ const gchar *response;
+ GError *error = NULL;
+ gint rv;
+ gint idx;
+
+ response = mm_base_modem_at_command_finish (modem, res, &error);
+ if (error) {
+ g_simple_async_result_take_error (ctx->result, error);
+ sms_store_context_complete_and_free (ctx);
+ return;
+ }
+
+ /* Read the new part index from the reply */
+ rv = sscanf (response, "+CMGW: %d", &idx);
+ if (rv != 1 || idx < 0) {
+ g_simple_async_result_set_error (ctx->result,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Couldn't read index of already stored part: "
+ "%d fields parsed",
+ rv);
+ sms_store_context_complete_and_free (ctx);
+ return;
+ }
+
+ /* Set the index in the part we hold */
+ mm_sms_part_set_index ((MMSmsPart *)ctx->current->data, (guint)idx);
+
+ ctx->current = g_list_next (ctx->current);
+ sms_store_next_part (ctx);
+}
+
+static void
+store_ready (MMBaseModem *modem,
+ GAsyncResult *res,
+ SmsStoreContext *ctx)
+{
+ const gchar *response;
+ GError *error = NULL;
+
+ response = mm_base_modem_at_command_finish (modem, res, &error);
+ if (error) {
+ g_simple_async_result_take_error (ctx->result, error);
+ sms_store_context_complete_and_free (ctx);
+ return;
+ }
+
+ /* Send the actual message data */
+ mm_base_modem_at_command_raw (ctx->modem,
+ ctx->msg_data,
+ 10,
+ FALSE,
+ (GAsyncReadyCallback)store_msg_data_ready,
+ ctx);
+}
+
+static void
+sms_store_next_part (SmsStoreContext *ctx)
+{
+ gchar *cmd;
+ GError *error = NULL;
+
+ if (!ctx->current) {
+ /* Done we are */
+ g_simple_async_result_set_op_res_gboolean (ctx->result, TRUE);
+ sms_store_context_complete_and_free (ctx);
+ return;
+ }
+
+ if (ctx->msg_data) {
+ g_free (ctx->msg_data);
+ ctx->msg_data = NULL;
+ }
+
+ if (!sms_get_store_or_send_command ((MMSmsPart *)ctx->current->data,
+ ctx->use_pdu_mode,
+ FALSE,
+ &cmd,
+ &ctx->msg_data,
+ &error)) {
+ g_simple_async_result_take_error (ctx->result, error);
+ sms_store_context_complete_and_free (ctx);
+ return;
+ }
+
+ g_assert (cmd != NULL);
+ g_assert (ctx->msg_data != NULL);
+
+ mm_base_modem_at_command (ctx->modem,
+ cmd,
+ 10,
+ FALSE,
+ (GAsyncReadyCallback)store_ready,
+ ctx);
+ g_free (cmd);
+}
+
+static void
+store_lock_sms_storages_ready (MMBroadbandModem *modem,
+ GAsyncResult *res,
+ SmsStoreContext *ctx)
+{
+ GError *error = NULL;
+
+ if (!mm_broadband_modem_lock_sms_storages_finish (modem, res, &error)) {
+ g_simple_async_result_take_error (ctx->result, error);
+ sms_store_context_complete_and_free (ctx);
+ return;
+ }
+
+ /* We are now locked. Whatever result we have here, we need to make sure
+ * we unlock the storages before finishing. */
+ ctx->need_unlock = TRUE;
+
+ /* Go on to store the parts */
+ ctx->current = ctx->self->priv->parts;
+ sms_store_next_part (ctx);
+}
+
+static void
+sms_store (MMBaseSms *self,
+ MMSmsStorage storage,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ SmsStoreContext *ctx;
+
+ /* Setup the context */
+ ctx = g_new0 (SmsStoreContext, 1);
+ ctx->result = g_simple_async_result_new (G_OBJECT (self),
+ callback,
+ user_data,
+ sms_store);
+ ctx->self = g_object_ref (self);
+ ctx->modem = g_object_ref (self->priv->modem);
+ ctx->storage = storage;
+
+ /* Different ways to do it if on PDU or text mode */
+ g_object_get (self->priv->modem,
+ MM_IFACE_MODEM_MESSAGING_SMS_PDU_MODE, &ctx->use_pdu_mode,
+ NULL);
+
+ /* First, lock storage to use */
+ g_assert (MM_IS_BROADBAND_MODEM (self->priv->modem));
+ mm_broadband_modem_lock_sms_storages (
+ MM_BROADBAND_MODEM (self->priv->modem),
+ MM_SMS_STORAGE_UNKNOWN, /* none required for mem1 */
+ ctx->storage,
+ (GAsyncReadyCallback)store_lock_sms_storages_ready,
+ ctx);
+}
+
+/*****************************************************************************/
+/* Send the SMS */
+
+typedef struct {
+ MMBaseSms *self;
+ MMBaseModem *modem;
+ GSimpleAsyncResult *result;
+ gboolean need_unlock;
+ gboolean from_storage;
+ gboolean use_pdu_mode;
+ GList *current;
+ gchar *msg_data;
+} SmsSendContext;
+
+static void
+sms_send_context_complete_and_free (SmsSendContext *ctx)
+{
+ g_simple_async_result_complete_in_idle (ctx->result);
+ g_object_unref (ctx->result);
+ /* Unlock mem2 storage if we had the lock */
+ if (ctx->need_unlock)
+ mm_broadband_modem_unlock_sms_storages (MM_BROADBAND_MODEM (ctx->modem), FALSE, TRUE);
+ g_object_unref (ctx->modem);
+ g_object_unref (ctx->self);
+ g_free (ctx->msg_data);
+ g_free (ctx);
+}
+
+static gboolean
+sms_send_finish (MMBaseSms *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return !g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error);
+}
+
+static void sms_send_next_part (SmsSendContext *ctx);
+
+static gint
+read_message_reference_from_reply (const gchar *response,
+ GError **error)
+{
+ gint rv = 0;
+ gint idx = -1;
+
+ if (strstr (response, "+CMGS"))
+ rv = sscanf (strstr (response, "+CMGS"), "+CMGS: %d", &idx);
+ else if (strstr (response, "+CMSS"))
+ rv = sscanf (strstr (response, "+CMSS"), "+CMSS: %d", &idx);
+
+ if (rv != 1 || idx < 0) {
+ g_set_error (error,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Couldn't read message reference: "
+ "%d fields parsed from response '%s'",
+ rv, response);
+ return -1;
+ }
+
+ return idx;
+}
+
+static void
+send_generic_msg_data_ready (MMBaseModem *modem,
+ GAsyncResult *res,
+ SmsSendContext *ctx)
+{
+ GError *error = NULL;
+ const gchar *response;
+ gint message_reference;
+
+ response = mm_base_modem_at_command_finish (modem, res, &error);
+ if (error) {
+ g_simple_async_result_take_error (ctx->result, error);
+ sms_send_context_complete_and_free (ctx);
+ return;
+ }
+
+ message_reference = read_message_reference_from_reply (response, &error);
+ if (error) {
+ g_simple_async_result_take_error (ctx->result, error);
+ sms_send_context_complete_and_free (ctx);
+ return;
+ }
+
+ mm_sms_part_set_message_reference ((MMSmsPart *)ctx->current->data,
+ (guint)message_reference);
+
+ ctx->current = g_list_next (ctx->current);
+ sms_send_next_part (ctx);
+}
+
+static void
+send_generic_ready (MMBaseModem *modem,
+ GAsyncResult *res,
+ SmsSendContext *ctx)
+{
+ GError *error = NULL;
+
+ mm_base_modem_at_command_finish (modem, res, &error);
+ if (error) {
+ g_simple_async_result_take_error (ctx->result, error);
+ sms_send_context_complete_and_free (ctx);
+ return;
+ }
+
+ /* Send the actual message data */
+ mm_base_modem_at_command_raw (ctx->modem,
+ ctx->msg_data,
+ 10,
+ FALSE,
+ (GAsyncReadyCallback)send_generic_msg_data_ready,
+ ctx);
+}
+
+static void
+send_from_storage_ready (MMBaseModem *modem,
+ GAsyncResult *res,
+ SmsSendContext *ctx)
+{
+ GError *error = NULL;
+ const gchar *response;
+ gint message_reference;
+
+ response = mm_base_modem_at_command_finish (modem, res, &error);
+ if (error) {
+ if (g_error_matches (error, MM_SERIAL_ERROR, MM_SERIAL_ERROR_RESPONSE_TIMEOUT)) {
+ g_simple_async_result_take_error (ctx->result, error);
+ sms_send_context_complete_and_free (ctx);
+ return;
+ }
+
+ mm_dbg ("Couldn't send SMS from storage: '%s'; trying generic send...",
+ error->message);
+ g_error_free (error);
+
+ ctx->from_storage = FALSE;
+ sms_send_next_part (ctx);
+ return;
+ }
+
+ message_reference = read_message_reference_from_reply (response, &error);
+ if (error) {
+ g_simple_async_result_take_error (ctx->result, error);
+ sms_send_context_complete_and_free (ctx);
+ return;
+ }
+
+ mm_sms_part_set_message_reference ((MMSmsPart *)ctx->current->data,
+ (guint)message_reference);
+
+ ctx->current = g_list_next (ctx->current);
+ sms_send_next_part (ctx);
+}
+
+static void
+sms_send_next_part (SmsSendContext *ctx)
+{
+ GError *error = NULL;
+ gchar *cmd;
+
+ if (!ctx->current) {
+ /* Done we are */
+ g_simple_async_result_set_op_res_gboolean (ctx->result, TRUE);
+ sms_send_context_complete_and_free (ctx);
+ return;
+ }
+
+ /* Send from storage */
+ if (ctx->from_storage) {
+ cmd = g_strdup_printf ("+CMSS=%d",
+ mm_sms_part_get_index ((MMSmsPart *)ctx->current->data));
+ mm_base_modem_at_command (ctx->modem,
+ cmd,
+ 30,
+ FALSE,
+ (GAsyncReadyCallback)send_from_storage_ready,
+ ctx);
+ g_free (cmd);
+ return;
+ }
+
+ /* Generic send */
+
+ if (ctx->msg_data) {
+ g_free (ctx->msg_data);
+ ctx->msg_data = NULL;
+ }
+
+ if (!sms_get_store_or_send_command ((MMSmsPart *)ctx->current->data,
+ ctx->use_pdu_mode,
+ TRUE,
+ &cmd,
+ &ctx->msg_data,
+ &error)) {
+ g_simple_async_result_take_error (ctx->result, error);
+ sms_send_context_complete_and_free (ctx);
+ return;
+ }
+
+ g_assert (cmd != NULL);
+ g_assert (ctx->msg_data != NULL);
+ mm_base_modem_at_command (ctx->modem,
+ cmd,
+ 30,
+ FALSE,
+ (GAsyncReadyCallback)send_generic_ready,
+ ctx);
+ g_free (cmd);
+}
+
+static void
+send_lock_sms_storages_ready (MMBroadbandModem *modem,
+ GAsyncResult *res,
+ SmsSendContext *ctx)
+{
+ GError *error = NULL;
+
+ if (!mm_broadband_modem_lock_sms_storages_finish (modem, res, &error)) {
+ g_simple_async_result_take_error (ctx->result, error);
+ sms_send_context_complete_and_free (ctx);
+ return;
+ }
+
+ /* We are now locked. Whatever result we have here, we need to make sure
+ * we unlock the storages before finishing. */
+ ctx->need_unlock = TRUE;
+
+ /* Go on to send the parts */
+ ctx->current = ctx->self->priv->parts;
+ sms_send_next_part (ctx);
+}
+
+static void
+sms_send (MMBaseSms *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ SmsSendContext *ctx;
+
+ /* Setup the context */
+ ctx = g_new0 (SmsSendContext, 1);
+ ctx->result = g_simple_async_result_new (G_OBJECT (self),
+ callback,
+ user_data,
+ sms_send);
+ ctx->self = g_object_ref (self);
+ ctx->modem = g_object_ref (self->priv->modem);
+
+ /* If the SMS is STORED, try to send from storage */
+ ctx->from_storage = (mm_base_sms_get_storage (self) != MM_SMS_STORAGE_UNKNOWN);
+ if (ctx->from_storage) {
+ /* When sending from storage, first lock storage to use */
+ g_assert (MM_IS_BROADBAND_MODEM (self->priv->modem));
+ mm_broadband_modem_lock_sms_storages (
+ MM_BROADBAND_MODEM (self->priv->modem),
+ MM_SMS_STORAGE_UNKNOWN, /* none required for mem1 */
+ mm_base_sms_get_storage (self),
+ (GAsyncReadyCallback)send_lock_sms_storages_ready,
+ ctx);
+ return;
+ }
+
+ /* Different ways to do it if on PDU or text mode */
+ g_object_get (self->priv->modem,
+ MM_IFACE_MODEM_MESSAGING_SMS_PDU_MODE, &ctx->use_pdu_mode,
+ NULL);
+ ctx->current = self->priv->parts;
+ sms_send_next_part (ctx);
+}
+
+/*****************************************************************************/
+
+typedef struct {
+ MMBaseSms *self;
+ MMBaseModem *modem;
+ GSimpleAsyncResult *result;
+ gboolean need_unlock;
+ GList *current;
+ guint n_failed;
+} SmsDeletePartsContext;
+
+static void
+sms_delete_parts_context_complete_and_free (SmsDeletePartsContext *ctx)
+{
+ g_simple_async_result_complete_in_idle (ctx->result);
+ g_object_unref (ctx->result);
+ /* Unlock mem1 storage if we had the lock */
+ if (ctx->need_unlock)
+ mm_broadband_modem_unlock_sms_storages (MM_BROADBAND_MODEM (ctx->modem), TRUE, FALSE);
+ g_object_unref (ctx->modem);
+ g_object_unref (ctx->self);
+ g_free (ctx);
+}
+
+static gboolean
+sms_delete_finish (MMBaseSms *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return !g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error);
+}
+
+static void delete_next_part (SmsDeletePartsContext *ctx);
+
+static void
+delete_part_ready (MMBaseModem *modem,
+ GAsyncResult *res,
+ SmsDeletePartsContext *ctx)
+{
+ GError *error = NULL;
+
+ mm_base_modem_at_command_finish (modem, res, &error);
+ if (error) {
+ ctx->n_failed++;
+ mm_dbg ("Couldn't delete SMS part with index %u: '%s'",
+ mm_sms_part_get_index ((MMSmsPart *)ctx->current->data),
+ error->message);
+ g_error_free (error);
+ }
+
+ /* We reset the index, as there is no longer that part */
+ mm_sms_part_set_index ((MMSmsPart *)ctx->current->data, SMS_PART_INVALID_INDEX);
+
+ ctx->current = g_list_next (ctx->current);
+ delete_next_part (ctx);
+}
+
+static void
+delete_next_part (SmsDeletePartsContext *ctx)
+{
+ gchar *cmd;
+
+ /* Skip non-stored parts */
+ while (ctx->current &&
+ mm_sms_part_get_index ((MMSmsPart *)ctx->current->data) == SMS_PART_INVALID_INDEX)
+ ctx->current = g_list_next (ctx->current);
+
+ /* If all removed, we're done */
+ if (!ctx->current) {
+ if (ctx->n_failed > 0)
+ g_simple_async_result_set_error (ctx->result,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Couldn't delete %u parts from this SMS",
+ ctx->n_failed);
+ else
+ g_simple_async_result_set_op_res_gboolean (ctx->result, TRUE);
+
+ sms_delete_parts_context_complete_and_free (ctx);
+ return;
+ }
+
+ cmd = g_strdup_printf ("+CMGD=%d",
+ mm_sms_part_get_index ((MMSmsPart *)ctx->current->data));
+ mm_base_modem_at_command (ctx->modem,
+ cmd,
+ 10,
+ FALSE,
+ (GAsyncReadyCallback)delete_part_ready,
+ ctx);
+ g_free (cmd);
+}
+
+static void
+delete_lock_sms_storages_ready (MMBroadbandModem *modem,
+ GAsyncResult *res,
+ SmsDeletePartsContext *ctx)
+{
+ GError *error = NULL;
+
+ if (!mm_broadband_modem_lock_sms_storages_finish (modem, res, &error)) {
+ g_simple_async_result_take_error (ctx->result, error);
+ sms_delete_parts_context_complete_and_free (ctx);
+ return;
+ }
+
+ /* We are now locked. Whatever result we have here, we need to make sure
+ * we unlock the storages before finishing. */
+ ctx->need_unlock = TRUE;
+
+ /* Go on deleting parts */
+ ctx->current = ctx->self->priv->parts;
+ delete_next_part (ctx);
+}
+
+static void
+sms_delete (MMBaseSms *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ SmsDeletePartsContext *ctx;
+
+ ctx = g_new0 (SmsDeletePartsContext, 1);
+ ctx->result = g_simple_async_result_new (G_OBJECT (self),
+ callback,
+ user_data,
+ sms_delete);
+ ctx->self = g_object_ref (self);
+ ctx->modem = g_object_ref (self->priv->modem);
+
+ if (mm_base_sms_get_storage (self) == MM_SMS_STORAGE_UNKNOWN) {
+ mm_dbg ("Not removing parts from non-stored SMS");
+ g_simple_async_result_set_op_res_gboolean (ctx->result, TRUE);
+ sms_delete_parts_context_complete_and_free (ctx);
+ return;
+ }
+
+ /* Select specific storage to delete from */
+ mm_broadband_modem_lock_sms_storages (
+ MM_BROADBAND_MODEM (self->priv->modem),
+ mm_base_sms_get_storage (self),
+ MM_SMS_STORAGE_UNKNOWN, /* none required for mem2 */
+ (GAsyncReadyCallback)delete_lock_sms_storages_ready,
+ ctx);
+}
+
+/*****************************************************************************/
+
+gboolean
+mm_base_sms_delete_finish (MMBaseSms *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ if (MM_BASE_SMS_GET_CLASS (self)->delete_finish) {
+ gboolean deleted;
+
+ deleted = MM_BASE_SMS_GET_CLASS (self)->delete_finish (self, res, error);
+ if (deleted)
+ /* We do change the state of this SMS back to UNKNOWN, as it is no
+ * longer stored in the device */
+ mm_gdbus_sms_set_state (MM_GDBUS_SMS (self), MM_SMS_STATE_UNKNOWN);
+
+ return deleted;
+ }
+
+ return !g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error);
+}
+
+void
+mm_base_sms_delete (MMBaseSms *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ if (MM_BASE_SMS_GET_CLASS (self)->delete &&
+ MM_BASE_SMS_GET_CLASS (self)->delete_finish) {
+ MM_BASE_SMS_GET_CLASS (self)->delete (self, callback, user_data);
+ return;
+ }
+
+ g_simple_async_report_error_in_idle (G_OBJECT (self),
+ callback,
+ user_data,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_UNSUPPORTED,
+ "Deleting SMS is not supported by this modem");
+}
+
+/*****************************************************************************/
+
+static gboolean
+assemble_sms (MMBaseSms *self,
+ GError **error)
+{
+ GList *l;
+ guint idx;
+ MMSmsPart **sorted_parts;
+ GString *fulltext;
+ GByteArray *fulldata;
+ guint validity_relative;
+
+ sorted_parts = g_new0 (MMSmsPart *, self->priv->max_parts);
+
+ /* Note that sequence in multipart messages start with '1', while singlepart
+ * messages have '0' as sequence. */
+
+ if (self->priv->max_parts == 1) {
+ if (g_list_length (self->priv->parts) != 1) {
+ g_set_error (error,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Single part message with multiple parts (%u) found",
+ g_list_length (self->priv->parts));
+ g_free (sorted_parts);
+ return FALSE;
+ }
+
+ sorted_parts[0] = (MMSmsPart *)self->priv->parts->data;
+ } else {
+ /* Check if we have duplicate parts */
+ for (l = self->priv->parts; l; l = g_list_next (l)) {
+ idx = mm_sms_part_get_concat_sequence ((MMSmsPart *)l->data);
+
+ if (idx < 1 || idx > self->priv->max_parts) {
+ mm_warn ("Invalid part index (%u) found, ignoring", idx);
+ continue;
+ }
+
+ if (sorted_parts[idx - 1]) {
+ mm_warn ("Duplicate part index (%u) found, ignoring", idx);
+ continue;
+ }
+
+ /* Add the part to the proper index */
+ sorted_parts[idx - 1] = (MMSmsPart *)l->data;
+ }
+ }
+
+ fulltext = g_string_new ("");
+ fulldata = g_byte_array_sized_new (160 * self->priv->max_parts);
+
+ /* Assemble text and data from all parts. Now 'idx' is the index of the
+ * array, so for multipart messages the real index of the part is 'idx + 1'
+ */
+ for (idx = 0; idx < self->priv->max_parts; idx++) {
+ const gchar *parttext;
+ const GByteArray *partdata;
+
+ if (!sorted_parts[idx]) {
+ g_set_error (error,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Cannot assemble SMS, missing part at index (%u)",
+ self->priv->max_parts == 1 ? idx : idx + 1);
+ g_string_free (fulltext, TRUE);
+ g_byte_array_free (fulldata, TRUE);
+ g_free (sorted_parts);
+ return FALSE;
+ }
+
+ /* When the user creates the SMS, it will have either 'text' or 'data',
+ * not both. Also status report PDUs may not have neither text nor data. */
+ parttext = mm_sms_part_get_text (sorted_parts[idx]);
+ partdata = mm_sms_part_get_data (sorted_parts[idx]);
+
+ if (!parttext && !partdata &&
+ mm_sms_part_get_pdu_type (sorted_parts[idx]) != MM_SMS_PDU_TYPE_STATUS_REPORT) {
+ g_set_error (error,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Cannot assemble SMS, part at index (%u) has neither text nor data",
+ self->priv->max_parts == 1 ? idx : idx + 1);
+ g_string_free (fulltext, TRUE);
+ g_byte_array_free (fulldata, TRUE);
+ g_free (sorted_parts);
+ return FALSE;
+ }
+
+ if (parttext)
+ g_string_append (fulltext, parttext);
+ if (partdata)
+ g_byte_array_append (fulldata, partdata->data, partdata->len);
+ }
+
+ /* If we got all parts, we also have the first one always */
+ g_assert (sorted_parts[0] != NULL);
+
+ /* Prepare for validity tuple */
+ validity_relative = mm_sms_part_get_validity_relative (sorted_parts[0]);
+
+ /* If we got everything, assemble the text! */
+ g_object_set (self,
+ "pdu-type", mm_sms_part_get_pdu_type (sorted_parts[0]),
+ "text", fulltext->str,
+ "data", g_variant_new_from_data (G_VARIANT_TYPE ("ay"),
+ fulldata->data,
+ fulldata->len * sizeof (guint8),
+ TRUE,
+ (GDestroyNotify) g_byte_array_unref,
+ g_byte_array_ref (fulldata)),
+ "smsc", mm_sms_part_get_smsc (sorted_parts[0]),
+ "class", mm_sms_part_get_class (sorted_parts[0]),
+ "teleservice-id", mm_sms_part_get_cdma_teleservice_id (sorted_parts[0]),
+ "service-category", mm_sms_part_get_cdma_service_category (sorted_parts[0]),
+ "number", mm_sms_part_get_number (sorted_parts[0]),
+ "validity", (validity_relative ?
+ g_variant_new ("(uv)", MM_SMS_VALIDITY_TYPE_RELATIVE, g_variant_new_uint32 (validity_relative)) :
+ g_variant_new ("(uv)", MM_SMS_VALIDITY_TYPE_UNKNOWN, g_variant_new_boolean (FALSE))),
+ "timestamp", mm_sms_part_get_timestamp (sorted_parts[0]),
+ "discharge-timestamp", mm_sms_part_get_discharge_timestamp (sorted_parts[0]),
+ "delivery-state", mm_sms_part_get_delivery_state (sorted_parts[0]),
+ /* delivery report request and message reference taken always from the last part */
+ "message-reference", mm_sms_part_get_message_reference (sorted_parts[self->priv->max_parts - 1]),
+ "delivery-report-request", mm_sms_part_get_delivery_report_request (sorted_parts[self->priv->max_parts - 1]),
+ NULL);
+
+ g_string_free (fulltext, TRUE);
+ g_byte_array_unref (fulldata);
+ g_free (sorted_parts);
+
+ self->priv->is_assembled = TRUE;
+
+ return TRUE;
+}
+
+/*****************************************************************************/
+
+static guint
+cmp_sms_part_sequence (MMSmsPart *a,
+ MMSmsPart *b)
+{
+ return (mm_sms_part_get_concat_sequence (a) - mm_sms_part_get_concat_sequence (b));
+}
+
+gboolean
+mm_base_sms_multipart_take_part (MMBaseSms *self,
+ MMSmsPart *part,
+ GError **error)
+{
+ if (!self->priv->is_multipart) {
+ g_set_error (error,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "This SMS is not a multipart message");
+ return FALSE;
+ }
+
+ if (g_list_length (self->priv->parts) >= self->priv->max_parts) {
+ g_set_error (error,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Already took %u parts, cannot take more",
+ g_list_length (self->priv->parts));
+ return FALSE;
+ }
+
+ if (g_list_find_custom (self->priv->parts,
+ part,
+ (GCompareFunc)cmp_sms_part_sequence)) {
+ g_set_error (error,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Cannot take part, sequence %u already taken",
+ mm_sms_part_get_concat_sequence (part));
+ return FALSE;
+ }
+
+ if (mm_sms_part_get_concat_sequence (part) > self->priv->max_parts) {
+ g_set_error (error,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Cannot take part with sequence %u, maximum is %u",
+ mm_sms_part_get_concat_sequence (part),
+ self->priv->max_parts);
+ return FALSE;
+ }
+
+ /* Insert sorted by concat sequence */
+ self->priv->parts = g_list_insert_sorted (self->priv->parts,
+ part,
+ (GCompareFunc)cmp_sms_part_sequence);
+
+ /* We only populate contents when the multipart SMS is complete */
+ if (mm_base_sms_multipart_is_complete (self)) {
+ GError *inner_error = NULL;
+
+ if (!assemble_sms (self, &inner_error)) {
+ /* We DO NOT propagate the error. The part was properly taken
+ * so ownership passed to the MMBaseSms object. */
+ mm_warn ("Couldn't assemble SMS: '%s'",
+ inner_error->message);
+ g_error_free (inner_error);
+ } else {
+ /* Completed AND assembled
+ * Change state RECEIVING->RECEIVED, and signal completeness */
+ if (mm_gdbus_sms_get_state (MM_GDBUS_SMS (self)) == MM_SMS_STATE_RECEIVING)
+ mm_gdbus_sms_set_state (MM_GDBUS_SMS (self), MM_SMS_STATE_RECEIVED);
+ }
+ }
+
+ return TRUE;
+}
+
+MMBaseSms *
+mm_base_sms_new (MMBaseModem *modem)
+{
+ return MM_BASE_SMS (g_object_new (MM_TYPE_BASE_SMS,
+ MM_BASE_SMS_MODEM, modem,
+ NULL));
+}
+
+MMBaseSms *
+mm_base_sms_singlepart_new (MMBaseModem *modem,
+ MMSmsState state,
+ MMSmsStorage storage,
+ MMSmsPart *part,
+ GError **error)
+{
+ MMBaseSms *self;
+
+ g_assert (MM_IS_IFACE_MODEM_MESSAGING (modem));
+
+ /* Create an SMS object as defined by the interface */
+ self = mm_iface_modem_messaging_create_sms (MM_IFACE_MODEM_MESSAGING (modem));
+ g_object_set (self,
+ "state", state,
+ "storage", storage,
+ NULL);
+
+ /* Keep the single part in the list */
+ self->priv->parts = g_list_prepend (self->priv->parts, part);
+
+ if (!assemble_sms (self, error)) {
+ /* Note: we need to remove the part from the list, as we really didn't
+ * take it, and therefore the caller is responsible for freeing it. */
+ self->priv->parts = g_list_remove (self->priv->parts, part);
+ g_clear_object (&self);
+ } else
+ /* Only export once properly created */
+ mm_base_sms_export (self);
+
+ return self;
+}
+
+MMBaseSms *
+mm_base_sms_multipart_new (MMBaseModem *modem,
+ MMSmsState state,
+ MMSmsStorage storage,
+ guint reference,
+ guint max_parts,
+ MMSmsPart *first_part,
+ GError **error)
+{
+ MMBaseSms *self;
+
+ g_assert (MM_IS_IFACE_MODEM_MESSAGING (modem));
+
+ /* If this is the first part of a RECEIVED SMS, we overwrite the state
+ * as RECEIVING, to indicate that it is not completed yet. */
+ if (state == MM_SMS_STATE_RECEIVED)
+ state = MM_SMS_STATE_RECEIVING;
+
+ /* Create an SMS object as defined by the interface */
+ self = mm_iface_modem_messaging_create_sms (MM_IFACE_MODEM_MESSAGING (modem));
+ g_object_set (self,
+ MM_BASE_SMS_IS_MULTIPART, TRUE,
+ MM_BASE_SMS_MAX_PARTS, max_parts,
+ MM_BASE_SMS_MULTIPART_REFERENCE, reference,
+ "state", state,
+ "storage", storage,
+ "validity", g_variant_new ("(uv)",
+ MM_SMS_VALIDITY_TYPE_UNKNOWN,
+ g_variant_new_boolean (FALSE)),
+ NULL);
+
+ if (!mm_base_sms_multipart_take_part (self, first_part, error))
+ g_clear_object (&self);
+
+ /* We do export uncomplete multipart messages, in order to be able to
+ * request removal of all parts of those multipart SMS that will never
+ * get completed.
+ * Only the STATE of the SMS object will be valid in the exported DBus
+ * interface.*/
+ if (self)
+ mm_base_sms_export (self);
+
+ return self;
+}
+
+MMBaseSms *
+mm_base_sms_new_from_properties (MMBaseModem *modem,
+ MMSmsProperties *properties,
+ GError **error)
+{
+ MMBaseSms *self;
+ const gchar *text;
+ GByteArray *data;
+
+ g_assert (MM_IS_IFACE_MODEM_MESSAGING (modem));
+
+ text = mm_sms_properties_get_text (properties);
+ data = mm_sms_properties_peek_data_bytearray (properties);
+
+ /* Don't create SMS from properties if either (text|data) or number is missing */
+ if (!mm_sms_properties_get_number (properties) ||
+ (!text && !data)) {
+ g_set_error (error,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_INVALID_ARGS,
+ "Cannot create SMS: mandatory parameter '%s' is missing",
+ (!mm_sms_properties_get_number (properties)?
+ "number" : "text' or 'data"));
+ return NULL;
+ }
+
+ /* Don't create SMS from properties if both text and data are given */
+ if (text && data) {
+ g_set_error (error,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_INVALID_ARGS,
+ "Cannot create SMS: both 'text' and 'data' given");
+ return NULL;
+ }
+
+ /* Create an SMS object as defined by the interface */
+ self = mm_iface_modem_messaging_create_sms (MM_IFACE_MODEM_MESSAGING (modem));
+ g_object_set (self,
+ "state", MM_SMS_STATE_UNKNOWN,
+ "storage", MM_SMS_STORAGE_UNKNOWN,
+ "number", mm_sms_properties_get_number (properties),
+ "pdu-type", (mm_sms_properties_get_teleservice_id (properties) == MM_SMS_CDMA_TELESERVICE_ID_UNKNOWN ?
+ MM_SMS_PDU_TYPE_SUBMIT :
+ MM_SMS_PDU_TYPE_CDMA_SUBMIT),
+ "text", text,
+ "data", (data ?
+ g_variant_new_from_data (G_VARIANT_TYPE ("ay"),
+ data->data,
+ data->len * sizeof (guint8),
+ TRUE,
+ (GDestroyNotify) g_byte_array_unref,
+ g_byte_array_ref (data)) :
+ NULL),
+ "smsc", mm_sms_properties_get_smsc (properties),
+ "class", mm_sms_properties_get_class (properties),
+ "teleservice-id", mm_sms_properties_get_teleservice_id (properties),
+ "service-category", mm_sms_properties_get_service_category (properties),
+ "delivery-report-request", mm_sms_properties_get_delivery_report_request (properties),
+ "delivery-state", MM_SMS_DELIVERY_STATE_UNKNOWN,
+ "validity", (mm_sms_properties_get_validity_type (properties) == MM_SMS_VALIDITY_TYPE_RELATIVE ?
+ g_variant_new ("(uv)", MM_SMS_VALIDITY_TYPE_RELATIVE, g_variant_new_uint32 (mm_sms_properties_get_validity_relative (properties))) :
+ g_variant_new ("(uv)", MM_SMS_VALIDITY_TYPE_UNKNOWN, g_variant_new_boolean (FALSE))),
+ NULL);
+
+ /* Only export once properly created */
+ mm_base_sms_export (self);
+
+ return self;
+}
+
+/*****************************************************************************/
+
+static void
+set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ MMBaseSms *self = MM_BASE_SMS (object);
+
+ switch (prop_id) {
+ case PROP_PATH:
+ g_free (self->priv->path);
+ self->priv->path = g_value_dup_string (value);
+
+ /* Export when we get a DBus connection AND we have a path */
+ if (!self->priv->path)
+ sms_dbus_unexport (self);
+ else if (self->priv->connection)
+ sms_dbus_export (self);
+ break;
+ case PROP_CONNECTION:
+ g_clear_object (&self->priv->connection);
+ self->priv->connection = g_value_dup_object (value);
+
+ /* Export when we get a DBus connection AND we have a path */
+ if (!self->priv->connection)
+ sms_dbus_unexport (self);
+ else if (self->priv->path)
+ sms_dbus_export (self);
+ break;
+ case PROP_MODEM:
+ g_clear_object (&self->priv->modem);
+ self->priv->modem = g_value_dup_object (value);
+ if (self->priv->modem) {
+ /* Bind the modem's connection (which is set when it is exported,
+ * and unset when unexported) to the SMS's connection */
+ g_object_bind_property (self->priv->modem, MM_BASE_MODEM_CONNECTION,
+ self, MM_BASE_SMS_CONNECTION,
+ G_BINDING_DEFAULT | G_BINDING_SYNC_CREATE);
+ }
+ break;
+ case PROP_IS_MULTIPART:
+ self->priv->is_multipart = g_value_get_boolean (value);
+ break;
+ case PROP_MAX_PARTS:
+ self->priv->max_parts = g_value_get_uint (value);
+ break;
+ case PROP_MULTIPART_REFERENCE:
+ self->priv->multipart_reference = g_value_get_uint (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ MMBaseSms *self = MM_BASE_SMS (object);
+
+ switch (prop_id) {
+ case PROP_PATH:
+ g_value_set_string (value, self->priv->path);
+ break;
+ case PROP_CONNECTION:
+ g_value_set_object (value, self->priv->connection);
+ break;
+ case PROP_MODEM:
+ g_value_set_object (value, self->priv->modem);
+ break;
+ case PROP_IS_MULTIPART:
+ g_value_set_boolean (value, self->priv->is_multipart);
+ break;
+ case PROP_MAX_PARTS:
+ g_value_set_uint (value, self->priv->max_parts);
+ break;
+ case PROP_MULTIPART_REFERENCE:
+ g_value_set_uint (value, self->priv->multipart_reference);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+mm_base_sms_init (MMBaseSms *self)
+{
+ /* Initialize private data */
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, MM_TYPE_BASE_SMS, MMBaseSmsPrivate);
+ /* Defaults */
+ self->priv->max_parts = 1;
+}
+
+static void
+finalize (GObject *object)
+{
+ MMBaseSms *self = MM_BASE_SMS (object);
+
+ g_list_free_full (self->priv->parts, (GDestroyNotify)mm_sms_part_free);
+ g_free (self->priv->path);
+
+ G_OBJECT_CLASS (mm_base_sms_parent_class)->finalize (object);
+}
+
+static void
+dispose (GObject *object)
+{
+ MMBaseSms *self = MM_BASE_SMS (object);
+
+ if (self->priv->connection) {
+ /* If we arrived here with a valid connection, make sure we unexport
+ * the object */
+ sms_dbus_unexport (self);
+ g_clear_object (&self->priv->connection);
+ }
+
+ g_clear_object (&self->priv->modem);
+
+ G_OBJECT_CLASS (mm_base_sms_parent_class)->dispose (object);
+}
+
+static void
+mm_base_sms_class_init (MMBaseSmsClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ g_type_class_add_private (object_class, sizeof (MMBaseSmsPrivate));
+
+ /* Virtual methods */
+ object_class->get_property = get_property;
+ object_class->set_property = set_property;
+ object_class->finalize = finalize;
+ object_class->dispose = dispose;
+
+ klass->store = sms_store;
+ klass->store_finish = sms_store_finish;
+ klass->send = sms_send;
+ klass->send_finish = sms_send_finish;
+ klass->delete = sms_delete;
+ klass->delete_finish = sms_delete_finish;
+
+ properties[PROP_CONNECTION] =
+ g_param_spec_object (MM_BASE_SMS_CONNECTION,
+ "Connection",
+ "GDBus connection to the system bus.",
+ G_TYPE_DBUS_CONNECTION,
+ G_PARAM_READWRITE);
+ g_object_class_install_property (object_class, PROP_CONNECTION, properties[PROP_CONNECTION]);
+
+ properties[PROP_PATH] =
+ g_param_spec_string (MM_BASE_SMS_PATH,
+ "Path",
+ "DBus path of the SMS",
+ NULL,
+ G_PARAM_READWRITE);
+ g_object_class_install_property (object_class, PROP_PATH, properties[PROP_PATH]);
+
+ properties[PROP_MODEM] =
+ g_param_spec_object (MM_BASE_SMS_MODEM,
+ "Modem",
+ "The Modem which owns this SMS",
+ MM_TYPE_BASE_MODEM,
+ G_PARAM_READWRITE);
+ g_object_class_install_property (object_class, PROP_MODEM, properties[PROP_MODEM]);
+
+ properties[PROP_IS_MULTIPART] =
+ g_param_spec_boolean (MM_BASE_SMS_IS_MULTIPART,
+ "Is multipart",
+ "Flag specifying if the SMS is multipart",
+ FALSE,
+ G_PARAM_READWRITE);
+ g_object_class_install_property (object_class, PROP_IS_MULTIPART, properties[PROP_IS_MULTIPART]);
+
+ properties[PROP_MAX_PARTS] =
+ g_param_spec_uint (MM_BASE_SMS_MAX_PARTS,
+ "Max parts",
+ "Maximum number of parts composing this SMS",
+ 1,255, 1,
+ G_PARAM_READWRITE);
+ g_object_class_install_property (object_class, PROP_MAX_PARTS, properties[PROP_MAX_PARTS]);
+
+ properties[PROP_MULTIPART_REFERENCE] =
+ g_param_spec_uint (MM_BASE_SMS_MULTIPART_REFERENCE,
+ "Multipart reference",
+ "Common reference for all parts in the multipart SMS",
+ 0, G_MAXUINT, 0,
+ G_PARAM_READWRITE);
+ g_object_class_install_property (object_class, PROP_MULTIPART_REFERENCE, properties[PROP_MULTIPART_REFERENCE]);
+}