diff options
author | Dan Williams <dan@ioncontrol.co> | 2025-04-18 08:57:49 -0500 |
---|---|---|
committer | Dan Williams <dan@ioncontrol.co> | 2025-05-08 20:24:37 -0500 |
commit | 9830e3955a3e45ff82c1c76bcba3b53432eaa51e (patch) | |
tree | 6141fb7aeee5422c56acd7733341a85e4d0c0ed2 /src/mm-sms-at.c | |
parent | 6d0e4daf877e0600966bbf9fb34dfba14b2ccb54 (diff) |
base-sms,sms-at: split AT-specific SMS code into MMSmsAt
Simplify MMBaseSms (making it easier to use from testcases) by
splitting the AT-specific code into MMSmsAt rather than keeping
it in the base class.
Signed-off-by: Dan Williams <dan@ioncontrol.co>
Diffstat (limited to 'src/mm-sms-at.c')
-rw-r--r-- | src/mm-sms-at.c | 835 |
1 files changed, 835 insertions, 0 deletions
diff --git a/src/mm-sms-at.c b/src/mm-sms-at.c new file mode 100644 index 00000000..3fc7c155 --- /dev/null +++ b/src/mm-sms-at.c @@ -0,0 +1,835 @@ +/* -*- 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. + * Copyright (C) 2025 Dan Williams <dan@ioncontrol.co> + */ + +#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-sms-at.h" +#include "mm-iface-modem-messaging.h" +#include "mm-sms-part-3gpp.h" +#include "mm-base-modem-at.h" +#include "mm-log-object.h" + +G_DEFINE_TYPE (MMSmsAt, mm_sms_at, MM_TYPE_BASE_SMS) + +struct _MMSmsAtPrivate { + MMBaseModem *modem; +}; + +/*****************************************************************************/ + +static gboolean +sms_get_store_or_send_command (MMSmsAt *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 { + g_autofree gchar *hex = NULL; + g_autofree guint8 *pdu = NULL; + guint pdulen = 0; + guint msgstart = 0; + + /* 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); + 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); + } + + return TRUE; +} + +/*****************************************************************************/ +/* Store the SMS */ + +typedef struct { + MMBaseModem *modem; + MMIfacePortAt *port; + 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_iface_modem_messaging_unlock_storages (MM_IFACE_MODEM_MESSAGING (ctx->modem), + FALSE, + TRUE); + } + g_object_unref (ctx->port); + g_object_unref (ctx->modem); + g_free (ctx->msg_data); + g_slice_free (SmsStoreContext, 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_full_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_full_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_full (ctx->modem, + ctx->port, + ctx->msg_data, + 10, + FALSE, + TRUE, /* raw */ + NULL, + (GAsyncReadyCallback)store_msg_data_ready, + task); +} + +static void +sms_store_next_part (GTask *task) +{ + MMSmsAt *self; + SmsStoreContext *ctx; + GError *error = NULL; + g_autofree gchar *cmd = 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_full (ctx->modem, + ctx->port, + cmd, + 10, + FALSE, + FALSE, /* raw */ + NULL, + (GAsyncReadyCallback)store_ready, + task); +} + +static void +store_lock_sms_storages_ready (MMIfaceModemMessaging *messaging, + GAsyncResult *res, + GTask *task) +{ + MMSmsAt *self; + SmsStoreContext *ctx; + GError *error = NULL; + + if (!mm_iface_modem_messaging_lock_storages_finish (messaging, 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 = mm_base_sms_get_parts (MM_BASE_SMS (self)); + sms_store_next_part (task); +} + +static void +sms_store (MMBaseSms *sms, + MMSmsStorage storage, + GAsyncReadyCallback callback, + gpointer user_data) +{ + MMSmsAt *self = MM_SMS_AT (sms); + SmsStoreContext *ctx; + GTask *task; + MMIfacePortAt *port; + GError *error = NULL; + + task = g_task_new (self, NULL, callback, user_data); + + /* Select port for the operation */ + port = mm_base_modem_peek_best_at_port (self->priv->modem, &error); + if (!port) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + /* Setup the context */ + ctx = g_slice_new0 (SmsStoreContext); + ctx->modem = g_object_ref (self->priv->modem); + ctx->port = g_object_ref (port); + ctx->storage = storage; + + /* Different ways to do it if on PDU or text mode */ + g_assert (MM_IS_IFACE_MODEM_MESSAGING (self->priv->modem)); + g_object_get (self->priv->modem, + MM_IFACE_MODEM_MESSAGING_SMS_PDU_MODE, &ctx->use_pdu_mode, + NULL); + g_task_set_task_data (task, ctx, (GDestroyNotify)sms_store_context_free); + + /* First, lock storage to use */ + mm_iface_modem_messaging_lock_storages ( + MM_IFACE_MODEM_MESSAGING (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; + MMIfacePortAt *port; + 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_iface_modem_messaging_unlock_storages (MM_IFACE_MODEM_MESSAGING (ctx->modem), + FALSE, + TRUE); + } + g_object_unref (ctx->port); + g_object_unref (ctx->modem); + g_free (ctx->msg_data); + g_slice_free (SmsSendContext, 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_full_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_full_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_full (ctx->modem, + ctx->port, + ctx->msg_data, + MM_BASE_SMS_DEFAULT_SEND_TIMEOUT, + FALSE, + TRUE, /* raw */ + NULL, + (GAsyncReadyCallback)send_generic_msg_data_ready, + task); +} + +static void +send_from_storage_ready (MMBaseModem *modem, + GAsyncResult *res, + GTask *task) +{ + MMSmsAt *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_full_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) +{ + MMSmsAt *self; + SmsSendContext *ctx; + GError *error = NULL; + g_autofree gchar *cmd = 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; + } + + /* 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_full (ctx->modem, + ctx->port, + cmd, + MM_BASE_SMS_DEFAULT_SEND_TIMEOUT, + FALSE, + FALSE, + NULL, + (GAsyncReadyCallback)send_from_storage_ready, + task); + 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_full (ctx->modem, + ctx->port, + cmd, + 10, + FALSE, + FALSE, /* raw */ + NULL, + (GAsyncReadyCallback)send_generic_ready, + task); +} + +static void +send_lock_sms_storages_ready (MMIfaceModemMessaging *messaging, + GAsyncResult *res, + GTask *task) +{ + MMSmsAt *self; + SmsSendContext *ctx; + GError *error = NULL; + + if (!mm_iface_modem_messaging_lock_storages_finish (messaging, 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 = mm_base_sms_get_parts (MM_BASE_SMS (self)); + sms_send_next_part (task); +} + +static void +sms_send (MMBaseSms *sms, + GAsyncReadyCallback callback, + gpointer user_data) +{ + MMSmsAt *self = MM_SMS_AT (sms); + SmsSendContext *ctx; + GTask *task; + MMIfacePortAt *port; + GError *error = NULL; + + task = g_task_new (self, NULL, callback, user_data); + + /* Select port for the operation */ + port = mm_base_modem_peek_best_at_port (self->priv->modem, &error); + if (!port) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + /* Setup the context */ + ctx = g_slice_new0 (SmsSendContext); + ctx->modem = g_object_ref (self->priv->modem); + ctx->port = g_object_ref (port); + 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 (MM_BASE_SMS (self)) != MM_SMS_STORAGE_UNKNOWN); + if (ctx->from_storage) { + /* When sending from storage, first lock storage to use */ + g_assert (MM_IS_IFACE_MODEM_MESSAGING (self->priv->modem)); + mm_iface_modem_messaging_lock_storages ( + MM_IFACE_MODEM_MESSAGING (self->priv->modem), + MM_SMS_STORAGE_UNKNOWN, /* none required for mem1 */ + mm_base_sms_get_storage (MM_BASE_SMS (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 = mm_base_sms_get_parts (MM_BASE_SMS (self)); + 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_iface_modem_messaging_unlock_storages (MM_IFACE_MODEM_MESSAGING (ctx->modem), + TRUE, + FALSE); + } + g_object_unref (ctx->modem); + g_slice_free (SmsDeletePartsContext, 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) +{ + MMSmsAt *self; + SmsDeletePartsContext *ctx; + g_autoptr(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); + } + + /* 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; + g_autofree gchar *cmd = NULL; + + 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); +} + +static void +delete_lock_sms_storages_ready (MMIfaceModemMessaging *messaging, + GAsyncResult *res, + GTask *task) +{ + MMSmsAt *self; + SmsDeletePartsContext *ctx; + GError *error = NULL; + + if (!mm_iface_modem_messaging_lock_storages_finish (messaging, 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 = mm_base_sms_get_parts (MM_BASE_SMS (self)); + delete_next_part (task); +} + +static void +sms_delete (MMBaseSms *sms, + GAsyncReadyCallback callback, + gpointer user_data) +{ + MMSmsAt *self = MM_SMS_AT (sms); + SmsDeletePartsContext *ctx; + GTask *task; + + ctx = g_slice_new0 (SmsDeletePartsContext); + 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 (MM_BASE_SMS (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_iface_modem_messaging_lock_storages ( + MM_IFACE_MODEM_MESSAGING (self->priv->modem), + mm_base_sms_get_storage (MM_BASE_SMS (self)), + MM_SMS_STORAGE_UNKNOWN, /* none required for mem2 */ + (GAsyncReadyCallback)delete_lock_sms_storages_ready, + task); +} + +/*****************************************************************************/ + +MMBaseSms * +mm_sms_at_new (MMBaseModem *modem, gboolean is_3gpp) +{ + MMBaseSms *sms; + + sms = MM_BASE_SMS (g_object_new (MM_TYPE_SMS_AT, + MM_BASE_SMS_MODEM, modem, + MM_BASE_SMS_IS_3GPP, is_3gpp, + NULL)); + MM_SMS_AT (sms)->priv->modem = g_object_ref (modem); + return sms; +} + +static void +mm_sms_at_init (MMSmsAt *self) +{ + self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, MM_TYPE_SMS_AT, MMSmsAtPrivate); +} + +static void +dispose (GObject *object) +{ + MMSmsAt *self = MM_SMS_AT (object); + + g_clear_object (&self->priv->modem); + + G_OBJECT_CLASS (mm_sms_at_parent_class)->dispose (object); +} + +static void +mm_sms_at_class_init (MMSmsAtClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + MMBaseSmsClass *base_sms_class = MM_BASE_SMS_CLASS (klass); + + g_type_class_add_private (object_class, sizeof (MMSmsAtPrivate)); + + object_class->dispose = dispose; + + base_sms_class->store = sms_store; + base_sms_class->store_finish = sms_store_finish; + base_sms_class->send = sms_send; + base_sms_class->send_finish = sms_send_finish; + base_sms_class->delete = sms_delete; + base_sms_class->delete_finish = sms_delete_finish; +} |