Files
ModemManager/src/mm-base-sms.c
T
Aleksander Morgado 1f9a04268f core: ensure all errors returned in DBus operations are normalized
We want to ensure that all errors reported via DBus operations are
normalized to MM-specific errors.

We don't want to return QMI or MBIM specific errors, as those are
protocol specific and we don't want DBus clients to need to rely on
knowing which is the protocol in use by the device.
2023-10-27 11:34:26 +02:00

2142 lines
70 KiB
C

/* -*- 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-object.h"
#include "mm-modem-helpers.h"
#include "mm-error-helpers.h"
static void log_object_iface_init (MMLogObjectInterface *iface);
G_DEFINE_TYPE_EXTENDED (MMBaseSms, mm_base_sms, MM_GDBUS_TYPE_SMS_SKELETON, 0,
G_IMPLEMENT_INTERFACE (MM_TYPE_LOG_OBJECT, log_object_iface_init))
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;
guint dbus_id;
/* 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;
MMModemCharset charset;
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_charset_util_split_text (text, &charset, self);
if (!split_text) {
g_set_error (error,
MM_CORE_ERROR,
MM_CORE_ERROR_INVALID_ARGS,
"Cannot generate PDUs: Error processing input text");
return FALSE;
}
encoding = (charset == MM_MODEM_CHARSET_GSM) ? MM_SMS_ENCODING_GSM7 : MM_SMS_ENCODING_UCS2;
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) {
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_obj_dbg (self, " 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_obj_dbg (self, " 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_obj_dbg (self, "created SMS part '%u' for multipart SMS ('%u' parts expected)", i + 1, n_parts);
} else {
mm_obj_dbg (self, "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_obj_dbg (self, "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_obj_dbg (self, "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_slice_free (HandleStoreContext, 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)) {
mm_obj_warn (self, "failed storing SMS message: %s", error->message);
mm_dbus_method_invocation_take_error (ctx->invocation, error);
handle_store_context_free (ctx);
return;
}
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_obj_info (self, "stored SMS message");
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)
{
/* Create parts if we did not create them already before (e.g. when
* sending) */
if (!self->priv->parts && !generate_submit_pdus (self, error)) {
g_prefix_error (error, "Cannot create submit PDUs: ");
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 chose a random multipart reference, but that doesn't work
* when storing locally, as we could collide with the references used
* in other existing messages. */
if (self->priv->is_multipart) {
GList *l;
guint8 reference;
/* 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) {
g_prefix_error (error, "Cannot get local multipart reference: ");
return FALSE;
}
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)) {
mm_dbus_method_invocation_take_error (ctx->invocation, error);
handle_store_context_free (ctx);
return;
}
mm_obj_info (ctx->self, "processing user request to store SMS message in storage '%s'...",
mm_sms_storage_get_string (ctx->storage));
/* 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_obj_info (ctx->self, "SMS message already stored");
mm_gdbus_sms_complete_store (MM_GDBUS_SMS (ctx->self), ctx->invocation);
} else {
error = g_error_new (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));
mm_obj_warn (ctx->self, "failed storing SMS message: %s", error->message);
mm_dbus_method_invocation_take_error (ctx->invocation, error);
}
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)) {
mm_obj_warn (ctx->self, "failed storing SMS message: %s", error->message);
mm_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)) {
mm_obj_warn (ctx->self, "failed preparing SMS message to be stored: %s", error->message);
mm_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) {
mm_obj_warn (ctx->self, "failed storing SMS message: unsupported");
mm_dbus_method_invocation_return_error_literal (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_slice_new0 (HandleStoreContext);
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_slice_free (HandleSendContext, 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)) {
mm_obj_warn (self, "failed sending SMS message: %s", error->message);
mm_dbus_method_invocation_take_error (ctx->invocation, error);
handle_send_context_free (ctx);
return;
}
/* 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_obj_info (self, "sent SMS message");
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 we created the parts when storing, we're fine already */
if (self->priv->parts)
return TRUE;
if (!generate_submit_pdus (self, error)) {
g_prefix_error (error, "Cannot create submit PDUs: ");
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)) {
mm_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) {
mm_dbus_method_invocation_return_error_literal (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) {
mm_dbus_method_invocation_return_error_literal (ctx->invocation, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
"This SMS was already sent, cannot send it again");
handle_send_context_free (ctx);
return;
}
mm_obj_info (ctx->self, "processing user request to send SMS message...");
/* Prepare the SMS to be sent, creating the PDU list if required */
if (!prepare_sms_to_be_sent (ctx->self, &error)) {
mm_obj_warn (ctx->self, "failed preparing SMS message to be sent: %s", error->message);
mm_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) {
mm_obj_warn (ctx->self, "failed sending SMS message: unsupported");
mm_dbus_method_invocation_return_error_literal (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_slice_new0 (HandleSendContext);
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)
{
gchar *path;
path = g_strdup_printf (MM_DBUS_SMS_PREFIX "/%d", self->priv->dbus_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_obj_warn (self, "couldn't export SMS: %s", 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 (MMBaseSms *self,
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, self, 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)
{
MMBaseSms *self;
SmsStoreContext *ctx;
gchar *cmd;
GError *error = NULL;
self = g_task_get_source_object (task);
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 (self,
(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,
MM_BASE_SMS_DEFAULT_SEND_TIMEOUT,
FALSE,
(GAsyncReadyCallback)send_generic_msg_data_ready,
task);
}
static void
send_from_storage_ready (MMBaseModem *modem,
GAsyncResult *res,
GTask *task)
{
MMBaseSms *self;
SmsSendContext *ctx;
GError *error = NULL;
const gchar *response;
gint message_reference;
self = g_task_get_source_object (task);
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_obj_dbg (self, "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)
{
MMBaseSms *self;
SmsSendContext *ctx;
GError *error = NULL;
gchar *cmd;
self = g_task_get_source_object (task);
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,
MM_BASE_SMS_DEFAULT_SEND_TIMEOUT,
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 (self,
(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);
/* no network involved in this initial AT command, so lower timeout */
mm_base_modem_at_command (ctx->modem,
cmd,
10,
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)
{
MMBaseSms *self;
SmsDeletePartsContext *ctx;
GError *error = NULL;
self = g_task_get_source_object (task);
ctx = g_task_get_task_data (task);
mm_base_modem_at_command_finish (modem, res, &error);
if (error) {
ctx->n_failed++;
mm_obj_dbg (self, "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_obj_dbg (self, "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_obj_warn (self, "invalid part index (%u) found, ignoring", idx);
continue;
}
if (sorted_parts[idx - 1]) {
mm_obj_warn (self, "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_obj_warn (self, "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 *props,
GError **error)
{
MMBaseSms *self;
const gchar *text;
GByteArray *data;
g_assert (MM_IS_IFACE_MODEM_MESSAGING (modem));
text = mm_sms_properties_get_text (props);
data = mm_sms_properties_peek_data_bytearray (props);
/* Don't create SMS from properties if either (text|data) or number is missing */
if (!mm_sms_properties_get_number (props) ||
(!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 (props)?
"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 (props),
"pdu-type", (mm_sms_properties_get_teleservice_id (props) == 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 (props),
"class", mm_sms_properties_get_class (props),
"teleservice-id", mm_sms_properties_get_teleservice_id (props),
"service-category", mm_sms_properties_get_service_category (props),
"delivery-report-request", mm_sms_properties_get_delivery_report_request (props),
"delivery-state", MM_SMS_DELIVERY_STATE_UNKNOWN,
"validity", (mm_sms_properties_get_validity_type (props) == MM_SMS_VALIDITY_TYPE_RELATIVE ?
g_variant_new ("(uv)", MM_SMS_VALIDITY_TYPE_RELATIVE, g_variant_new_uint32 (mm_sms_properties_get_validity_relative (props))) :
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 gchar *
log_object_build_id (MMLogObject *_self)
{
MMBaseSms *self;
self = MM_BASE_SMS (_self);
return g_strdup_printf ("sms%u", self->priv->dbus_id);
}
/*****************************************************************************/
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) {
/* Set owner ID */
mm_log_object_set_owner_id (MM_LOG_OBJECT (self), mm_log_object_get_id (MM_LOG_OBJECT (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)
{
static guint id = 0;
/* Initialize private data */
self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, MM_TYPE_BASE_SMS, MMBaseSmsPrivate);
/* Defaults */
self->priv->max_parts = 1;
/* Each SMS is given a unique id to build its own DBus path */
self->priv->dbus_id = id++;
}
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
log_object_iface_init (MMLogObjectInterface *iface)
{
iface->build_id = log_object_build_id;
}
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]);
}