| |
| /* -*- 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) */ |
| g_free (split_text); |
| 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 { |
| MMBaseModem *modem; |
| MMSmsStorage storage; |
| gboolean need_unlock; |
| gboolean use_pdu_mode; |
| GList *current; |
| gchar *msg_data; |
| } SmsStoreContext; |
| |
| static void |
| sms_store_context_free (SmsStoreContext *ctx) |
| { |
| /* 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_free (ctx->msg_data); |
| g_free (ctx); |
| } |
| |
| static gboolean |
| sms_store_finish (MMBaseSms *self, |
| GAsyncResult *res, |
| GError **error) |
| { |
| return g_task_propagate_boolean (G_TASK (res), error); |
| } |
| |
| static void sms_store_next_part (GTask *task); |
| |
| static void |
| store_msg_data_ready (MMBaseModem *modem, |
| GAsyncResult *res, |
| GTask *task) |
| { |
| 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_task_return_error (task, error); |
| g_object_unref (task); |
| return; |
| } |
| |
| /* Read the new part index from the reply */ |
| rv = sscanf (response, "+CMGW: %d", &idx); |
| if (rv != 1 || idx < 0) { |
| g_task_return_new_error (task, |
| MM_CORE_ERROR, |
| MM_CORE_ERROR_FAILED, |
| "Couldn't read index of already stored part: " |
| "%d fields parsed", |
| rv); |
| g_object_unref (task); |
| return; |
| } |
| |
| ctx = g_task_get_task_data (task); |
| |
| /* 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 (task); |
| } |
| |
| static void |
| store_ready (MMBaseModem *modem, |
| GAsyncResult *res, |
| GTask *task) |
| { |
| SmsStoreContext *ctx; |
| GError *error = NULL; |
| |
| mm_base_modem_at_command_finish (modem, res, &error); |
| if (error) { |
| g_task_return_error (task, error); |
| g_object_unref (task); |
| return; |
| } |
| |
| ctx = g_task_get_task_data (task); |
| |
| /* Send the actual message data. |
| * We send the data as 'raw' data because we do NOT want it to |
| * be treated as an AT command (i.e. we don't want it prefixed |
| * with AT+ and suffixed with <CR><LF>), plus, we want it to be |
| * sent right away (not queued after other AT commands). */ |
| mm_base_modem_at_command_raw (ctx->modem, |
| ctx->msg_data, |
| 10, |
| FALSE, |
| (GAsyncReadyCallback)store_msg_data_ready, |
| task); |
| } |
| |
| static void |
| sms_store_next_part (GTask *task) |
| { |
| SmsStoreContext *ctx; |
| gchar *cmd; |
| GError *error = NULL; |
| |
| ctx = g_task_get_task_data (task); |
| |
| if (!ctx->current) { |
| /* Done we are */ |
| g_task_return_boolean (task, TRUE); |
| g_object_unref (task); |
| return; |
| } |
| |
| g_clear_pointer (&ctx->msg_data, g_free); |
| |
| if (!sms_get_store_or_send_command ((MMSmsPart *)ctx->current->data, |
| ctx->use_pdu_mode, |
| FALSE, |
| &cmd, |
| &ctx->msg_data, |
| &error)) { |
| g_task_return_error (task, error); |
| g_object_unref (task); |
| return; |
| } |
| |
| g_assert (cmd != NULL); |
| g_assert (ctx->msg_data != NULL); |
| |
| mm_base_modem_at_command (ctx->modem, |
| cmd, |
| 10, |
| FALSE, |
| (GAsyncReadyCallback)store_ready, |
| task); |
| g_free (cmd); |
| } |
| |
| static void |
| store_lock_sms_storages_ready (MMBroadbandModem *modem, |
| GAsyncResult *res, |
| GTask *task) |
| { |
| MMBaseSms *self; |
| SmsStoreContext *ctx; |
| GError *error = NULL; |
| |
| if (!mm_broadband_modem_lock_sms_storages_finish (modem, res, &error)) { |
| g_task_return_error (task, error); |
| g_object_unref (task); |
| return; |
| } |
| |
| self = g_task_get_source_object (task); |
| ctx = g_task_get_task_data (task); |
| |
| /* 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 = self->priv->parts; |
| sms_store_next_part (task); |
| } |
| |
| static void |
| sms_store (MMBaseSms *self, |
| MMSmsStorage storage, |
| GAsyncReadyCallback callback, |
| gpointer user_data) |
| { |
| SmsStoreContext *ctx; |
| GTask *task; |
| |
| /* Setup the context */ |
| ctx = g_new0 (SmsStoreContext, 1); |
| 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); |
| |
| task = g_task_new (self, NULL, callback, user_data); |
| g_task_set_task_data (task, ctx, (GDestroyNotify)sms_store_context_free); |
| |
| /* 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, |
| task); |
| } |
| |
| /*****************************************************************************/ |
| /* Send the SMS */ |
| |
| typedef struct { |
| MMBaseModem *modem; |
| gboolean need_unlock; |
| gboolean from_storage; |
| gboolean use_pdu_mode; |
| GList *current; |
| gchar *msg_data; |
| } SmsSendContext; |
| |
| static void |
| sms_send_context_free (SmsSendContext *ctx) |
| { |
| /* 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_free (ctx->msg_data); |
| g_free (ctx); |
| } |
| |
| static gboolean |
| sms_send_finish (MMBaseSms *self, |
| GAsyncResult *res, |
| GError **error) |
| { |
| return g_task_propagate_boolean (G_TASK (res), error); |
| } |
| |
| static void sms_send_next_part (GTask *task); |
| |
| 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, |
| GTask *task) |
| { |
| SmsSendContext *ctx; |
| GError *error = NULL; |
| const gchar *response; |
| gint message_reference; |
| |
| response = mm_base_modem_at_command_finish (modem, res, &error); |
| if (error) { |
| g_task_return_error (task, error); |
| g_object_unref (task); |
| return; |
| } |
| |
| message_reference = read_message_reference_from_reply (response, &error); |
| if (error) { |
| g_task_return_error (task, error); |
| g_object_unref (task); |
| return; |
| } |
| |
| ctx = g_task_get_task_data (task); |
| |
| mm_sms_part_set_message_reference ((MMSmsPart *)ctx->current->data, |
| (guint)message_reference); |
| |
| ctx->current = g_list_next (ctx->current); |
| sms_send_next_part (task); |
| } |
| |
| static void |
| send_generic_ready (MMBaseModem *modem, |
| GAsyncResult *res, |
| GTask *task) |
| { |
| SmsSendContext *ctx; |
| GError *error = NULL; |
| |
| mm_base_modem_at_command_finish (modem, res, &error); |
| if (error) { |
| g_task_return_error (task, error); |
| g_object_unref (task); |
| return; |
| } |
| |
| ctx = g_task_get_task_data (task); |
| |
| /* Send the actual message data. |
| * We send the data as 'raw' data because we do NOT want it to |
| * be treated as an AT command (i.e. we don't want it prefixed |
| * with AT+ and suffixed with <CR><LF>), plus, we want it to be |
| * sent right away (not queued after other AT commands). */ |
| mm_base_modem_at_command_raw (ctx->modem, |
| ctx->msg_data, |
| 60, |
| FALSE, |
| (GAsyncReadyCallback)send_generic_msg_data_ready, |
| task); |
| } |
| |
| static void |
| send_from_storage_ready (MMBaseModem *modem, |
| GAsyncResult *res, |
| GTask *task) |
| { |
| SmsSendContext *ctx; |
| GError *error = NULL; |
| const gchar *response; |
| gint message_reference; |
| |
| ctx = g_task_get_task_data (task); |
| |
| 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_task_return_error (task, error); |
| g_object_unref (task); |
| 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 (task); |
| return; |
| } |
| |
| message_reference = read_message_reference_from_reply (response, &error); |
| if (error) { |
| g_task_return_error (task, error); |
| g_object_unref (task); |
| 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 (task); |
| } |
| |
| static void |
| sms_send_next_part (GTask *task) |
| { |
| SmsSendContext *ctx; |
| GError *error = NULL; |
| gchar *cmd; |
| |
| ctx = g_task_get_task_data (task); |
| |
| if (!ctx->current) { |
| /* Done we are */ |
| g_task_return_boolean (task, TRUE); |
| g_object_unref (task); |
| 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, |
| 60, |
| FALSE, |
| (GAsyncReadyCallback)send_from_storage_ready, |
| task); |
| g_free (cmd); |
| return; |
| } |
| |
| /* Generic send */ |
| |
| g_clear_pointer (&ctx->msg_data, g_free); |
| |
| if (!sms_get_store_or_send_command ((MMSmsPart *)ctx->current->data, |
| ctx->use_pdu_mode, |
| TRUE, |
| &cmd, |
| &ctx->msg_data, |
| &error)) { |
| g_task_return_error (task, error); |
| g_object_unref (task); |
| return; |
| } |
| |
| g_assert (cmd != NULL); |
| g_assert (ctx->msg_data != NULL); |
| mm_base_modem_at_command (ctx->modem, |
| cmd, |
| 60, |
| FALSE, |
| (GAsyncReadyCallback)send_generic_ready, |
| task); |
| g_free (cmd); |
| } |
| |
| static void |
| send_lock_sms_storages_ready (MMBroadbandModem *modem, |
| GAsyncResult *res, |
| GTask *task) |
| { |
| MMBaseSms *self; |
| SmsSendContext *ctx; |
| GError *error = NULL; |
| |
| if (!mm_broadband_modem_lock_sms_storages_finish (modem, res, &error)) { |
| g_task_return_error (task, error); |
| g_object_unref (task); |
| return; |
| } |
| |
| self = g_task_get_source_object (task); |
| ctx = g_task_get_task_data (task); |
| |
| /* 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 = self->priv->parts; |
| sms_send_next_part (task); |
| } |
| |
| static void |
| sms_send (MMBaseSms *self, |
| GAsyncReadyCallback callback, |
| gpointer user_data) |
| { |
| SmsSendContext *ctx; |
| GTask *task; |
| |
| /* Setup the context */ |
| ctx = g_new0 (SmsSendContext, 1); |
| ctx->modem = g_object_ref (self->priv->modem); |
| |
| task = g_task_new (self, NULL, callback, user_data); |
| g_task_set_task_data (task, ctx, (GDestroyNotify)sms_send_context_free); |
| |
| /* 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, |
| task); |
| 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 (task); |
| } |
| |
| /*****************************************************************************/ |
| |
| typedef struct { |
| MMBaseModem *modem; |
| gboolean need_unlock; |
| GList *current; |
| guint n_failed; |
| } SmsDeletePartsContext; |
| |
| static void |
| sms_delete_parts_context_free (SmsDeletePartsContext *ctx) |
| { |
| /* 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_free (ctx); |
| } |
| |
| static gboolean |
| sms_delete_finish (MMBaseSms *self, |
| GAsyncResult *res, |
| GError **error) |
| { |
| return g_task_propagate_boolean (G_TASK (res), error); |
| } |
| |
| static void delete_next_part (GTask *task); |
| |
| static void |
| delete_part_ready (MMBaseModem *modem, |
| GAsyncResult *res, |
| GTask *task) |
| { |
| SmsDeletePartsContext *ctx; |
| GError *error = NULL; |
| |
| ctx = g_task_get_task_data (task); |
| |
| 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 (task); |
| } |
| |
| static void |
| delete_next_part (GTask *task) |
| { |
| SmsDeletePartsContext *ctx; |
| gchar *cmd; |
| |
| ctx = g_task_get_task_data (task); |
| |
| /* 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_task_return_new_error (task, |
| MM_CORE_ERROR, |
| MM_CORE_ERROR_FAILED, |
| "Couldn't delete %u parts from this SMS", |
| ctx->n_failed); |
| else |
| g_task_return_boolean (task, TRUE); |
| |
| g_object_unref (task); |
| 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, |
| task); |
| g_free (cmd); |
| } |
| |
| static void |
| delete_lock_sms_storages_ready (MMBroadbandModem *modem, |
| GAsyncResult *res, |
| GTask *task) |
| { |
| MMBaseSms *self; |
| SmsDeletePartsContext *ctx; |
| GError *error = NULL; |
| |
| if (!mm_broadband_modem_lock_sms_storages_finish (modem, res, &error)) { |
| g_task_return_error (task, error); |
| g_object_unref (task); |
| return; |
| } |
| |
| self = g_task_get_source_object (task); |
| ctx = g_task_get_task_data (task); |
| |
| /* 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 = self->priv->parts; |
| delete_next_part (task); |
| } |
| |
| static void |
| sms_delete (MMBaseSms *self, |
| GAsyncReadyCallback callback, |
| gpointer user_data) |
| { |
| SmsDeletePartsContext *ctx; |
| GTask *task; |
| |
| ctx = g_new0 (SmsDeletePartsContext, 1); |
| ctx->modem = g_object_ref (self->priv->modem); |
| |
| task = g_task_new (self, NULL, callback, user_data); |
| g_task_set_task_data (task, ctx, (GDestroyNotify)sms_delete_parts_context_free); |
| |
| if (mm_base_sms_get_storage (self) == MM_SMS_STORAGE_UNKNOWN) { |
| mm_dbg ("Not removing parts from non-stored SMS"); |
| g_task_return_boolean (task, TRUE); |
| g_object_unref (task); |
| 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, |
| task); |
| } |
| |
| /*****************************************************************************/ |
| |
| 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_task_propagate_boolean (G_TASK (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_task_report_new_error (self, |
| callback, |
| user_data, |
| mm_base_sms_delete, |
| MM_CORE_ERROR, |
| MM_CORE_ERROR_UNSUPPORTED, |
| "Deleting SMS is not supported by this modem"); |
| } |
| |
| /*****************************************************************************/ |
| |
| static void |
| initialize_sms (MMBaseSms *self) |
| { |
| MMSmsPart *part; |
| guint validity_relative; |
| |
| /* Some of the fields of the SMS object may be initialized as soon as we have |
| * one part already available, even if it's not exactly the first one */ |
| g_assert (self->priv->parts); |
| part = (MMSmsPart *)(self->priv->parts->data); |
| |
| /* Prepare for validity tuple */ |
| validity_relative = mm_sms_part_get_validity_relative (part); |
| |
| g_object_set (self, |
| "pdu-type", mm_sms_part_get_pdu_type (part), |
| "smsc", mm_sms_part_get_smsc (part), |
| "class", mm_sms_part_get_class (part), |
| "teleservice-id", mm_sms_part_get_cdma_teleservice_id (part), |
| "service-category", mm_sms_part_get_cdma_service_category (part), |
| "number", mm_sms_part_get_number (part), |
| "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 (part), |
| "discharge-timestamp", mm_sms_part_get_discharge_timestamp (part), |
| "delivery-state", mm_sms_part_get_delivery_state (part), |
| NULL); |
| } |
| |
| static gboolean |
| assemble_sms (MMBaseSms *self, |
| GError **error) |
| { |
| GList *l; |
| guint idx; |
| MMSmsPart **sorted_parts; |
| GString *fulltext; |
| GByteArray *fulldata; |
| |
| 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); |
| |
| /* If we got everything, assemble the text! */ |
| g_object_set (self, |
| "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)), |
| /* 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); |
| |
| /* If this is the first part we take, initialize common SMS fields */ |
| if (g_list_length (self->priv->parts) == 1) |
| initialize_sms (self); |
| |
| /* 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); |
| |
| /* Initialize common SMS fields */ |
| initialize_sms (self); |
| |
| 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]); |
| } |