/* -*- 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,
10,
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,
30,
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,
30,
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]);
}