diff options
author | Guido Günther <agx@sigxcpu.org> | 2023-12-05 20:05:17 +0100 |
---|---|---|
committer | Aleksander Morgado <aleksander@aleksander.es> | 2024-12-01 21:41:10 +0000 |
commit | 62f7b76e8ea8f682048840741d1177e6c93a1c80 (patch) | |
tree | 37c6a4f9b96b942daf0fa1f85c6c322134a20469 /src | |
parent | eedf78d6622d09862d9e91a54358f2b56344cc22 (diff) |
cbm: Add CellBroadcast interface
This adds support for the Cell Broadcast interface allowing to
receive, list, read and delete Cell Broadcast messages via the
org.freedesktop.ModemManager1.Modem.CellBroadcast and
org.freedesktop.ModemManager1.Cbm interfaces.
Signed-off-by: Guido Günther <agx@sigxcpu.org>
Diffstat (limited to 'src')
-rw-r--r-- | src/meson.build | 3 | ||||
-rw-r--r-- | src/mm-auth-provider.h | 1 | ||||
-rw-r--r-- | src/mm-base-cbm.c | 571 | ||||
-rw-r--r-- | src/mm-base-cbm.h | 81 | ||||
-rw-r--r-- | src/mm-cbm-list.c | 402 | ||||
-rw-r--r-- | src/mm-cbm-list.h | 86 | ||||
-rw-r--r-- | src/mm-iface-modem-cell-broadcast.c | 654 | ||||
-rw-r--r-- | src/mm-iface-modem-cell-broadcast.h | 120 |
8 files changed, 1918 insertions, 0 deletions
diff --git a/src/meson.build b/src/meson.build index 01c5e12c..f88d905a 100644 --- a/src/meson.build +++ b/src/meson.build @@ -259,6 +259,7 @@ sources = files( 'mm-auth-provider.c', 'mm-base-bearer.c', 'mm-base-call.c', + 'mm-base-cbm.c', 'mm-base-manager.c', 'mm-base-modem-at.c', 'mm-base-modem.c', @@ -268,6 +269,7 @@ sources = files( 'mm-broadband-bearer.c', 'mm-broadband-modem.c', 'mm-call-list.c', + 'mm-cbm-list.c', 'mm-context.c', 'mm-device.c', 'mm-dispatcher.c', @@ -280,6 +282,7 @@ sources = files( 'mm-iface-modem-3gpp-ussd.c', 'mm-iface-modem.c', 'mm-iface-modem-cdma.c', + 'mm-iface-modem-cell-broadcast.c', 'mm-iface-modem-firmware.c', 'mm-iface-modem-location.c', 'mm-iface-modem-messaging.c', diff --git a/src/mm-auth-provider.h b/src/mm-auth-provider.h index c33e42ce..6065c806 100644 --- a/src/mm-auth-provider.h +++ b/src/mm-auth-provider.h @@ -37,6 +37,7 @@ #define MM_AUTHORIZATION_LOCATION "org.freedesktop.ModemManager1.Location" #define MM_AUTHORIZATION_TIME "org.freedesktop.ModemManager1.Time" #define MM_AUTHORIZATION_FIRMWARE "org.freedesktop.ModemManager1.Firmware" +#define MM_AUTHORIZATION_CELL_BROADCAST "org.freedesktop.ModemManager1.CellBroadcast" typedef struct _MMAuthProvider MMAuthProvider; typedef struct _MMAuthProviderClass MMAuthProviderClass; diff --git a/src/mm-base-cbm.c b/src/mm-base-cbm.c new file mode 100644 index 00000000..d35141be --- /dev/null +++ b/src/mm-base-cbm.c @@ -0,0 +1,571 @@ +/* -*- 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) 2024 Guido Günther <agx@sigxcpu.org> + */ + +#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-cbm.h" +#include "mm-broadband-modem.h" +#include "mm-iface-modem.h" +#include "mm-iface-modem-cell-broadcast.h" +#include "mm-cbm-part.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 (MMBaseCbm, mm_base_cbm, MM_GDBUS_TYPE_CBM_SKELETON, 0, + G_IMPLEMENT_INTERFACE (MM_TYPE_LOG_OBJECT, log_object_iface_init)) + +enum { + PROP_0, + PROP_PATH, + PROP_CONNECTION, + PROP_MODEM, + PROP_MAX_PARTS, + PROP_SERIAL, + PROP_LAST +}; + +static GParamSpec *properties[PROP_LAST]; + +struct _MMBaseCbmPrivate { + /* The connection to the system bus */ + GDBusConnection *connection; + guint dbus_id; + + /* The modem which owns this CBM */ + MMBaseModem *modem; + /* The path where the CBM object is exported */ + gchar *path; + + /* List of CBM parts */ + guint max_parts; + GList *parts; + + /* channel and serial identify the CBM */ + guint16 serial; + + /* Set to true when all needed parts were received, + * parsed and assembled */ + gboolean is_assembled; +}; + +void +mm_base_cbm_export (MMBaseCbm *self) +{ + g_autofree gchar *path = NULL; + + path = g_strdup_printf (MM_DBUS_CBM_PREFIX "/%d", self->priv->dbus_id); + g_object_set (self, + MM_BASE_CBM_PATH, path, + NULL); +} + +void +mm_base_cbm_unexport (MMBaseCbm *self) +{ + g_object_set (self, + MM_BASE_CBM_PATH, NULL, + NULL); +} + +/*****************************************************************************/ + +static void +cbm_dbus_export (MMBaseCbm *self) +{ + g_autoptr (GError) error = 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 CBM: %s", error->message); + } +} + +static void +cbm_dbus_unexport (MMBaseCbm *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_cbm_get_path (MMBaseCbm *self) +{ + return self->priv->path; +} + +gboolean +mm_base_cbm_is_complete (MMBaseCbm *self) +{ + return (g_list_length (self->priv->parts) == self->priv->max_parts); +} + +/*****************************************************************************/ + +static guint +cmp_cbm_part_has_num (MMCbmPart *part, + gpointer user_data) +{ + return (GPOINTER_TO_UINT (user_data) - mm_cbm_part_get_part_num (part)); +} + +gboolean +mm_base_cbm_has_part_num (MMBaseCbm *self, + guint part_num) +{ + return !!g_list_find_custom (self->priv->parts, + GUINT_TO_POINTER (part_num), + (GCompareFunc)cmp_cbm_part_has_num); +} + +GList * +mm_base_cbm_get_parts (MMBaseCbm *self) +{ + return self->priv->parts; +} + +/*****************************************************************************/ + +static void +initialize_cbm (MMBaseCbm *self) +{ + MMCbmPart *part; + guint16 serial; + + /* Some of the fields of the CBM 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 = (MMCbmPart *)(self->priv->parts->data); + + serial = mm_cbm_part_get_serial (part); + g_object_set (self, + MM_BASE_CBM_MAX_PARTS, mm_cbm_part_get_num_parts (part), + MM_BASE_CBM_SERIAL, serial, + "channel", mm_cbm_part_get_channel (part), + "update", CBM_SERIAL_MESSAGE_CODE_UPDATE (serial), + "message-code", CBM_SERIAL_MESSAGE_CODE (serial), + NULL); +} + +static gboolean +assemble_cbm (MMBaseCbm *self, + GError **error) +{ + GList *l; + guint part_num, idx; + g_autofree MMCbmPart **sorted_parts = NULL; + g_autoptr (GString) fulltext = NULL; + + sorted_parts = g_new0 (MMCbmPart *, self->priv->max_parts); + + /* Check if we have duplicate parts */ + for (l = self->priv->parts; l; l = g_list_next (l)) { + part_num = mm_cbm_part_get_part_num ((MMCbmPart *)l->data); + + /* part_num starts at 1, array starts at 0 */ + idx = part_num - 1; + if (part_num < 1 || part_num > self->priv->max_parts) { + mm_obj_warn (self, "invalid part part nu (%u) found, ignoring", part_num); + continue; + } + + if (sorted_parts[idx]) { + mm_obj_warn (self, "duplicate part index (%u) found, ignoring", part_num); + continue; + } + + /* Add the part to the proper array position */ + sorted_parts[idx] = (MMCbmPart *)l->data; + } + + fulltext = g_string_new (""); + + /* Assemble text and data from all parts. */ + for (idx = 0; idx < self->priv->max_parts; idx++) { + const gchar *parttext; + + if (!sorted_parts[idx]) { + g_set_error (error, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Cannot assemble CBM, missing part at index (%u)", + idx); + return FALSE; + } + + parttext = mm_cbm_part_get_text (sorted_parts[idx]); + if (!parttext) { + g_set_error (error, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Cannot assemble CBM, part at index (%u) has no text", + idx); + return FALSE; + } + + if (parttext) + g_string_append (fulltext, parttext); + } + + /* 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, + NULL); + + self->priv->is_assembled = TRUE; + + return TRUE; +} + +/*****************************************************************************/ + +static guint +cmp_cbm_part_num (MMCbmPart *a, + MMCbmPart *b) +{ + return (mm_cbm_part_get_part_num (a) - mm_cbm_part_get_part_num (b)); +} + +gboolean +mm_base_cbm_take_part (MMBaseCbm *self, + MMCbmPart *part, + GError **error) +{ + 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_cbm_part_num)) { + g_set_error (error, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Cannot take part, part %u already taken", + mm_cbm_part_get_part_num (part)); + return FALSE; + } + + if (mm_cbm_part_get_part_num (part) > self->priv->max_parts) { + g_set_error (error, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Cannot take part with number %u, maximum is %u", + mm_cbm_part_get_part_num (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_cbm_part_num); + + /* If this is the first part we take, initialize common CBM fields */ + if (g_list_length (self->priv->parts) == 1) + initialize_cbm (self); + + /* We only populate contents when the multipart CBM is complete */ + if (mm_base_cbm_is_complete (self)) { + g_autoptr (GError) inner_error = NULL; + + if (!assemble_cbm (self, &inner_error)) { + /* We DO NOT propagate the error. The part was properly taken + * so ownership passed to the MMBaseCbm object. */ + mm_obj_warn (self, "couldn't assemble CBM: %s", inner_error->message); + } else { + /* Completed AND assembled + * Change state RECEIVING->RECEIVED, and signal completeness */ + if (mm_gdbus_cbm_get_state (MM_GDBUS_CBM (self)) == MM_CBM_STATE_RECEIVING) + mm_gdbus_cbm_set_state (MM_GDBUS_CBM (self), MM_CBM_STATE_RECEIVED); + } + } + + return TRUE; +} + +MMBaseCbm * +mm_base_cbm_new (MMBaseModem *modem) +{ + return MM_BASE_CBM (g_object_new (MM_TYPE_BASE_CBM, + MM_BASE_CBM_MODEM, modem, + NULL)); +} + +MMBaseCbm * +mm_base_cbm_new_with_part (MMBaseModem *modem, + MMCbmState state, + guint max_parts, + MMCbmPart *first_part, + GError **error) +{ + MMBaseCbm *self; + + g_assert (MM_IS_IFACE_MODEM_CELL_BROADCAST (modem)); + + if (state == MM_CBM_STATE_RECEIVED) + state = MM_CBM_STATE_RECEIVING; + + /* Create a CBM object as defined by the interface */ + self = mm_iface_modem_cell_broadcast_create_cbm (MM_IFACE_MODEM_CELL_BROADCAST (modem)); + g_object_set (self, + MM_BASE_CBM_MAX_PARTS, max_parts, + "state", state, + NULL); + + if (!mm_base_cbm_take_part (self, first_part, error)) + g_clear_object (&self); + + /* We do export uncomplete multipart messages, so clients can make use of it + * Only the STATE of the CBM object will be valid in the exported DBus + * interface intially.*/ + if (self) + mm_base_cbm_export (self); + + return self; +} + +/*****************************************************************************/ + +static gchar * +log_object_build_id (MMLogObject *_self) +{ + MMBaseCbm *self; + + self = MM_BASE_CBM (_self); + return g_strdup_printf ("cbm%u", self->priv->dbus_id); +} + +/*****************************************************************************/ + +static void +set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + MMBaseCbm *self = MM_BASE_CBM (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) + cbm_dbus_unexport (self); + else if (self->priv->connection) + cbm_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) + cbm_dbus_unexport (self); + else if (self->priv->path) + cbm_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 CBM's connection */ + g_object_bind_property (self->priv->modem, MM_BASE_MODEM_CONNECTION, + self, MM_BASE_CBM_CONNECTION, + G_BINDING_DEFAULT | G_BINDING_SYNC_CREATE); + } + break; + case PROP_MAX_PARTS: + self->priv->max_parts = g_value_get_uint (value); + break; + case PROP_SERIAL: + self->priv->serial = 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) +{ + MMBaseCbm *self = MM_BASE_CBM (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_MAX_PARTS: + g_value_set_uint (value, self->priv->max_parts); + break; + case PROP_SERIAL: + g_value_set_uint (value, self->priv->serial); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +mm_base_cbm_init (MMBaseCbm *self) +{ + static guint id = 0; + + /* Initialize private data */ + self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, MM_TYPE_BASE_CBM, MMBaseCbmPrivate); + self->priv->max_parts = 1; + + /* Each CBM is given a unique id to build its own DBus path */ + self->priv->dbus_id = id++; +} + +static void +finalize (GObject *object) +{ + MMBaseCbm *self = MM_BASE_CBM (object); + + g_list_free_full (self->priv->parts, (GDestroyNotify)mm_cbm_part_free); + g_free (self->priv->path); + + G_OBJECT_CLASS (mm_base_cbm_parent_class)->finalize (object); +} + +static void +dispose (GObject *object) +{ + MMBaseCbm *self = MM_BASE_CBM (object); + + if (self->priv->connection) { + /* If we arrived here with a valid connection, make sure we unexport + * the object */ + cbm_dbus_unexport (self); + g_clear_object (&self->priv->connection); + } + + g_clear_object (&self->priv->modem); + + G_OBJECT_CLASS (mm_base_cbm_parent_class)->dispose (object); +} + +static void +log_object_iface_init (MMLogObjectInterface *iface) +{ + iface->build_id = log_object_build_id; +} + +static void +mm_base_cbm_class_init (MMBaseCbmClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + g_type_class_add_private (object_class, sizeof (MMBaseCbmPrivate)); + + /* Virtual methods */ + object_class->get_property = get_property; + object_class->set_property = set_property; + object_class->finalize = finalize; + object_class->dispose = dispose; + + properties[PROP_CONNECTION] = + g_param_spec_object (MM_BASE_CBM_CONNECTION, + "Connection", + "GDBus connection to the system bus.", + G_TYPE_DBUS_CONNECTION, + G_PARAM_READWRITE); + + properties[PROP_PATH] = + g_param_spec_string (MM_BASE_CBM_PATH, + "Path", + "DBus path of the CBM", + NULL, + G_PARAM_READWRITE); + + properties[PROP_MODEM] = + g_param_spec_object (MM_BASE_CBM_MODEM, + "Modem", + "The Modem which owns this CBM", + MM_TYPE_BASE_MODEM, + G_PARAM_READWRITE); + + properties[PROP_MAX_PARTS] = + g_param_spec_uint (MM_BASE_CBM_MAX_PARTS, + "Max parts", + "Maximum number of parts composing this CBM", + 1, 255, 1, + G_PARAM_READWRITE); + + properties[PROP_SERIAL] = + g_param_spec_uint (MM_BASE_CBM_SERIAL, + "Serial", + "The serial of this CBM", + 0, G_MAXUINT16, 0, + G_PARAM_READWRITE); + + g_object_class_install_properties (object_class, PROP_LAST, properties); +} + +guint16 +mm_base_cbm_get_serial (MMBaseCbm *self) +{ + return self->priv->serial; +} + +guint16 +mm_base_cbm_get_channel (MMBaseCbm *self) +{ + return mm_gdbus_cbm_get_channel (MM_GDBUS_CBM (self)); +} diff --git a/src/mm-base-cbm.h b/src/mm-base-cbm.h new file mode 100644 index 00000000..538cd9d1 --- /dev/null +++ b/src/mm-base-cbm.h @@ -0,0 +1,81 @@ +/* -*- 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) 2024 Guido Günther <agx@sigxcpu.org> + */ + +#ifndef MM_BASE_CBM_H +#define MM_BASE_CBM_H + +#include <glib.h> +#include <glib-object.h> + +#define _LIBMM_INSIDE_MM +#include <libmm-glib.h> + +#include "mm-cbm-part.h" +#include "mm-base-modem.h" + +/*****************************************************************************/ + +#define MM_TYPE_BASE_CBM (mm_base_cbm_get_type ()) +#define MM_BASE_CBM(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_BASE_CBM, MMBaseCbm)) +#define MM_BASE_CBM_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_BASE_CBM, MMBaseCbmClass)) +#define MM_IS_BASE_CBM(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_BASE_CBM)) +#define MM_IS_BASE_CBM_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_BASE_CBM)) +#define MM_BASE_CBM_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_BASE_CBM, MMBaseCbmClass)) + +typedef struct _MMBaseCbm MMBaseCbm; +typedef struct _MMBaseCbmClass MMBaseCbmClass; +typedef struct _MMBaseCbmPrivate MMBaseCbmPrivate; + +#define MM_BASE_CBM_PATH "cbm-path" +#define MM_BASE_CBM_CONNECTION "cbm-connection" +#define MM_BASE_CBM_MODEM "cbm-modem" +#define MM_BASE_CBM_MAX_PARTS "cbm-max-parts" +#define MM_BASE_CBM_SERIAL "cbm-serial" + +struct _MMBaseCbm { + MmGdbusCbmSkeleton parent; + MMBaseCbmPrivate *priv; +}; + +struct _MMBaseCbmClass { + MmGdbusCbmSkeletonClass parent; +}; + +GType mm_base_cbm_get_type (void); +G_DEFINE_AUTOPTR_CLEANUP_FUNC (MMBaseCbm, g_object_unref) + +MMBaseCbm *mm_base_cbm_new (MMBaseModem *modem); +gboolean mm_base_cbm_take_part (MMBaseCbm *self, + MMCbmPart *part, + GError **error); +MMBaseCbm *mm_base_cbm_new_with_part (MMBaseModem *modem, + MMCbmState state, + guint max_parts, + MMCbmPart *first_part, + GError **error); + +void mm_base_cbm_export (MMBaseCbm *self); +void mm_base_cbm_unexport (MMBaseCbm *self); +const gchar *mm_base_cbm_get_path (MMBaseCbm *self); + +gboolean mm_base_cbm_has_part_num (MMBaseCbm *self, + guint part_num); +GList *mm_base_cbm_get_parts (MMBaseCbm *self); +guint16 mm_base_cbm_get_serial (MMBaseCbm *self); +guint16 mm_base_cbm_get_channel (MMBaseCbm *self); + +gboolean mm_base_cbm_is_complete (MMBaseCbm *self); + +#endif /* MM_BASE_CBM_H */ diff --git a/src/mm-cbm-list.c b/src/mm-cbm-list.c new file mode 100644 index 00000000..b86246a6 --- /dev/null +++ b/src/mm-cbm-list.c @@ -0,0 +1,402 @@ +/* -*- 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) 2024 Guido Günther <agx@sigxcpu.org> + * + * based on mm-sms-list.c which is + * + * 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-iface-modem-messaging.h" +#include "mm-cbm-list.h" +#include "mm-base-cbm.h" +#include "mm-log-object.h" + +static void log_object_iface_init (MMLogObjectInterface *iface); + +G_DEFINE_TYPE_EXTENDED (MMCbmList, mm_cbm_list, G_TYPE_OBJECT, 0, + G_IMPLEMENT_INTERFACE (MM_TYPE_LOG_OBJECT, log_object_iface_init)) + +enum { + PROP_0, + PROP_MODEM, + PROP_LAST +}; +static GParamSpec *properties[PROP_LAST]; + +enum { + SIGNAL_ADDED, + SIGNAL_DELETED, + SIGNAL_LAST +}; +static guint signals[SIGNAL_LAST]; + +struct _MMCbmListPrivate { + /* The owner modem */ + MMBaseModem *modem; + /* List of cbm objects */ + GList *list; +}; + +/*****************************************************************************/ + +guint +mm_cbm_list_get_count (MMCbmList *self) +{ + return g_list_length (self->priv->list); +} + +GStrv +mm_cbm_list_get_paths (MMCbmList *self) +{ + GStrv path_list = NULL; + GList *l; + guint i; + + path_list = g_new0 (gchar *, 1 + g_list_length (self->priv->list)); + for (i = 0, l = self->priv->list; l; l = g_list_next (l)) { + const gchar *path; + + /* Don't try to add NULL paths (not yet exported CBM objects) */ + path = mm_base_cbm_get_path (MM_BASE_CBM (l->data)); + if (path) + path_list[i++] = g_strdup (path); + } + + return path_list; +} + +/*****************************************************************************/ + +gboolean +mm_cbm_list_delete_cbm_finish (MMCbmList *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static guint +cmp_cbm_by_path (MMBaseCbm *cbm, + const gchar *path) +{ + return g_strcmp0 (mm_base_cbm_get_path (cbm), path); +} + +void +mm_cbm_list_delete_cbm (MMCbmList *self, + const gchar *cbm_path, + GAsyncReadyCallback callback, + gpointer user_data) +{ + g_autoptr (GTask) task = NULL; + MMBaseCbm *cbm; + GList *l; + + l = g_list_find_custom (self->priv->list, + (gpointer)cbm_path, + (GCompareFunc)cmp_cbm_by_path); + if (!l) { + g_task_report_new_error (self, + callback, + user_data, + mm_cbm_list_delete_cbm, + MM_CORE_ERROR, + MM_CORE_ERROR_NOT_FOUND, + "No CBM found with path '%s'", + cbm_path); + return; + } + cbm = MM_BASE_CBM (l->data); + + /* Although this could be done sync we use a task to provide the + * async API pattern like other operations */ + task = g_task_new (self, NULL, callback, user_data); + + self->priv->list = g_list_delete_link (self->priv->list, l); + + mm_base_cbm_unexport (cbm); + g_object_unref (cbm); + + g_signal_emit (self, signals[SIGNAL_DELETED], 0, cbm_path); + g_task_return_boolean (task, TRUE); +} + + +/*****************************************************************************/ + +void +mm_cbm_list_add_cbm (MMCbmList *self, + MMBaseCbm *cbm) +{ + self->priv->list = g_list_prepend (self->priv->list, g_object_ref (cbm)); + g_signal_emit (self, signals[SIGNAL_ADDED], 0, + mm_base_cbm_get_path (cbm), + FALSE); +} + +/*****************************************************************************/ + +typedef struct { + guint16 serial; + guint16 channel; +} PartIdAndSerial; + +static guint +cmp_cbm_by_serial_and_id (MMBaseCbm *cbm, + PartIdAndSerial *ctx) +{ + return !(mm_base_cbm_get_serial (cbm) == ctx->serial && + mm_base_cbm_get_channel (cbm) == ctx->channel); +} + +static gboolean +take_part (MMCbmList *self, + MMCbmPart *part, + MMCbmState state, + GError **error) +{ + GList *l; + MMBaseCbm *cbm; + PartIdAndSerial cmp; + + cmp = (PartIdAndSerial){ + .serial = mm_cbm_part_get_serial (part), + .channel = mm_cbm_part_get_channel (part), + }; + l = g_list_find_custom (self->priv->list, + &cmp, + (GCompareFunc)cmp_cbm_by_serial_and_id); + if (l) { + /* Try to take the part */ + mm_obj_dbg (self, "found existing multipart CBM object with serial '%u' and id '%u': adding new part", + cmp.serial, cmp.channel); + return mm_base_cbm_take_part (MM_BASE_CBM (l->data), part, error); + } + + /* Create new cbm */ + cbm = mm_base_cbm_new_with_part (self->priv->modem, + state, + mm_cbm_part_get_num_parts (part), + part, + error); + if (!cbm) + return FALSE; + + mm_obj_dbg (self, "creating new multipart CBM object: need to receive %u parts with serial '%u' and id '%u'", + mm_cbm_part_get_num_parts (part), cmp.serial, cmp.channel); + + self->priv->list = g_list_prepend (self->priv->list, cbm); + g_signal_emit (self, signals[SIGNAL_ADDED], 0, + mm_base_cbm_get_path (cbm), + (state == MM_CBM_STATE_RECEIVED || + state == MM_CBM_STATE_RECEIVING)); + + return TRUE; +} + +typedef struct { + guint16 serial; + guint16 channel; + guint8 part_num; +} PartIdSerialAndNum; + +static guint +cmp_cbm_by_serial_id_and_part_num (MMBaseCbm *cbm, + PartIdSerialAndNum *ctx) +{ + return !(mm_base_cbm_get_serial (cbm) == ctx->serial && + mm_base_cbm_get_channel (cbm) == ctx->channel && + mm_base_cbm_has_part_num (cbm, ctx->part_num)); +} + +gboolean +mm_cbm_list_has_part (MMCbmList *self, + guint16 serial, + guint16 channel, + guint8 part_num) +{ + PartIdSerialAndNum ctx = { + .channel = channel, + .serial = serial, + .part_num = part_num + }; + + return !!g_list_find_custom (self->priv->list, + &ctx, + (GCompareFunc)cmp_cbm_by_serial_id_and_part_num); +} + +gboolean +mm_cbm_list_take_part (MMCbmList *self, + MMCbmPart *part, + MMCbmState state, + GError **error) +{ + /* Ensure we don't have already taken a part with the same index */ + if (mm_cbm_list_has_part (self, + mm_cbm_part_get_serial (part), + mm_cbm_part_get_channel (part), + mm_cbm_part_get_part_num (part))) { + g_set_error (error, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "A part %u with serial %u and id %u and was already taken", + mm_cbm_part_get_part_num (part), + mm_cbm_part_get_serial (part), + mm_cbm_part_get_channel (part)); + return FALSE; + } + + return take_part (self, part, state, error); +} + +/*****************************************************************************/ + +static gchar * +log_object_build_id (MMLogObject *_self) +{ + return g_strdup ("cbm-list"); +} + +/*****************************************************************************/ + +MMCbmList * +mm_cbm_list_new (MMBaseModem *modem) +{ + /* Create the object */ + return g_object_new (MM_TYPE_CBM_LIST, + MM_CBM_LIST_MODEM, modem, + NULL); +} + +static void +set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + MMCbmList *self = MM_CBM_LIST (object); + + switch (prop_id) { + 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))); + } + 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) +{ + MMCbmList *self = MM_CBM_LIST (object); + + switch (prop_id) { + case PROP_MODEM: + g_value_set_object (value, self->priv->modem); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +mm_cbm_list_init (MMCbmList *self) +{ + /* Initialize private data */ + self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, + MM_TYPE_CBM_LIST, + MMCbmListPrivate); +} + +static void +dispose (GObject *object) +{ + MMCbmList *self = MM_CBM_LIST (object); + + g_clear_object (&self->priv->modem); + g_list_free_full (self->priv->list, g_object_unref); + self->priv->list = NULL; + + G_OBJECT_CLASS (mm_cbm_list_parent_class)->dispose (object); +} + +static void +log_object_iface_init (MMLogObjectInterface *iface) +{ + iface->build_id = log_object_build_id; +} + +static void +mm_cbm_list_class_init (MMCbmListClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + g_type_class_add_private (object_class, sizeof (MMCbmListPrivate)); + + /* Virtual methods */ + object_class->get_property = get_property; + object_class->set_property = set_property; + object_class->dispose = dispose; + + /* Properties */ + properties[PROP_MODEM] = + g_param_spec_object (MM_CBM_LIST_MODEM, + "Modem", + "The Modem which owns this CBM list", + MM_TYPE_BASE_MODEM, + G_PARAM_READWRITE); + g_object_class_install_property (object_class, PROP_MODEM, properties[PROP_MODEM]); + + /* Signals */ + signals[SIGNAL_ADDED] = + g_signal_new (MM_CBM_ADDED, + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (MMCbmListClass, cbm_added), + NULL, NULL, + g_cclosure_marshal_generic, + G_TYPE_NONE, 2, G_TYPE_STRING, G_TYPE_BOOLEAN); + + signals[SIGNAL_DELETED] = + g_signal_new (MM_CBM_DELETED, + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (MMCbmListClass, cbm_deleted), + NULL, NULL, + g_cclosure_marshal_generic, + G_TYPE_NONE, 1, G_TYPE_STRING); +} diff --git a/src/mm-cbm-list.h b/src/mm-cbm-list.h new file mode 100644 index 00000000..79a48878 --- /dev/null +++ b/src/mm-cbm-list.h @@ -0,0 +1,86 @@ +/* -*- 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) 2024 Guido Günther <agx@sigxcpu.org> + */ + +#ifndef MM_CBM_LIST_H +#define MM_CBM_LIST_H + +#include <glib.h> +#include <glib-object.h> + +#include "mm-base-modem.h" +#include "mm-base-cbm.h" +#include "mm-cbm-part.h" + +#define MM_TYPE_CBM_LIST (mm_cbm_list_get_type ()) +#define MM_CBM_LIST(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_CBM_LIST, MMCbmList)) +#define MM_CBM_LIST_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_CBM_LIST, MMCbmListClass)) +#define MM_IS_CBM_LIST(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_CBM_LIST)) +#define MM_IS_CBM_LIST_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_CBM_LIST)) +#define MM_CBM_LIST_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_CBM_LIST, MMCbmListClass)) + +typedef struct _MMCbmList MMCbmList; +typedef struct _MMCbmListClass MMCbmListClass; +typedef struct _MMCbmListPrivate MMCbmListPrivate; + +#define MM_CBM_LIST_MODEM "cbm-list-modem" + +#define MM_CBM_ADDED "cbm-added" +#define MM_CBM_DELETED "cbm-deleted" + +struct _MMCbmList { + GObject parent; + MMCbmListPrivate *priv; +}; + +struct _MMCbmListClass { + GObjectClass parent; + + /* Signals */ + void (* cbm_added) (MMCbmList *self, + const gchar *cbm_path); + void (* cbm_deleted) (MMCbmList *self, + const gchar *sms_path); +}; + +GType mm_cbm_list_get_type (void); +G_DEFINE_AUTOPTR_CLEANUP_FUNC (MMCbmList, g_object_unref) + +MMCbmList *mm_cbm_list_new (MMBaseModem *modem); + +GStrv mm_cbm_list_get_paths (MMCbmList *self); +guint mm_cbm_list_get_count (MMCbmList *self); + +gboolean mm_cbm_list_has_part (MMCbmList *self, + guint16 serial, + guint16 message_id, + guint8 part_num); + +gboolean mm_cbm_list_take_part (MMCbmList *self, + MMCbmPart *part, + MMCbmState state, + GError **error); + +void mm_cbm_list_add_cbm (MMCbmList *self, + MMBaseCbm *cbm); + +void mm_cbm_list_delete_cbm (MMCbmList *self, + const gchar *cbm_path, + GAsyncReadyCallback callback, + gpointer user_data); +gboolean mm_cbm_list_delete_cbm_finish (MMCbmList *self, + GAsyncResult *res, + GError **error); + +#endif /* MM_CBM_LIST_H */ diff --git a/src/mm-iface-modem-cell-broadcast.c b/src/mm-iface-modem-cell-broadcast.c new file mode 100644 index 00000000..8e5af67c --- /dev/null +++ b/src/mm-iface-modem-cell-broadcast.c @@ -0,0 +1,654 @@ +/* -*- 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) 2024 Guido Günther <agx@sigxcpu.org>n + */ + +#include <ModemManager.h> +#define _LIBMM_INSIDE_MM +#include <libmm-glib.h> + +#include "mm-iface-modem.h" +#include "mm-iface-modem-cell-broadcast.h" +#include "mm-cbm-list.h" +#include "mm-log-object.h" +#include "mm-error-helpers.h" +#include "mm-modem-helpers.h" + +#define SUPPORT_CHECKED_TAG "cell-broadcast-support-checked-tag" +#define SUPPORTED_TAG "cell-broadcast-supported-tag" + +static GQuark support_checked_quark; +static GQuark supported_quark; + +G_DEFINE_INTERFACE (MMIfaceModemCellBroadcast, mm_iface_modem_cell_broadcast, MM_TYPE_IFACE_MODEM) + +/*****************************************************************************/ + +void +mm_iface_modem_cell_broadcast_bind_simple_status (MMIfaceModemCellBroadcast *self, + MMSimpleStatus *status) +{ +} + +/*****************************************************************************/ + +typedef struct { + MmGdbusModemCellBroadcast *skeleton; + GDBusMethodInvocation *invocation; + MMIfaceModemCellBroadcast *self; + gchar *path; +} HandleDeleteContext; + +static void +handle_delete_context_free (HandleDeleteContext *ctx) +{ + g_object_unref (ctx->skeleton); + g_object_unref (ctx->invocation); + g_object_unref (ctx->self); + g_free (ctx->path); + g_slice_free (HandleDeleteContext, ctx); +} + +static void +handle_delete_ready (MMCbmList *list, + GAsyncResult *res, + HandleDeleteContext *ctx) +{ + GError *error = NULL; + + if (!mm_cbm_list_delete_cbm_finish (list, res, &error)) { + mm_obj_warn (ctx->self, "failed deleting CBM message '%s': %s", ctx->path, error->message); + mm_dbus_method_invocation_take_error (ctx->invocation, error); + } else { + mm_obj_info (ctx->self, "deleted CBM message '%s'", ctx->path); + mm_gdbus_modem_cell_broadcast_complete_delete (ctx->skeleton, ctx->invocation); + } + handle_delete_context_free (ctx); +} + +static void +handle_delete_auth_ready (MMBaseModem *self, + GAsyncResult *res, + HandleDeleteContext *ctx) +{ + g_autoptr(MMCbmList) list = NULL; + GError *error = NULL; + + if (!mm_base_modem_authorize_finish (self, res, &error)) { + mm_dbus_method_invocation_take_error (ctx->invocation, error); + handle_delete_context_free (ctx); + return; + } + + /* We do allow deleting CBMs while enabling or disabling, it doesn't + * interfere with the state transition logic to do so. The main reason to allow + * this is that during modem enabling we're emitting "Added" signals before we + * reach the enabled state, and so users listening to the signal may want to + * delete the CBM message as soon as it's read. */ + if (mm_iface_modem_abort_invocation_if_state_not_reached (MM_IFACE_MODEM (self), + ctx->invocation, + MM_MODEM_STATE_DISABLING)) { + handle_delete_context_free (ctx); + return; + } + + g_object_get (self, + MM_IFACE_MODEM_CELL_BROADCAST_CBM_LIST, &list, + NULL); + if (!list) { + mm_dbus_method_invocation_return_error_literal (ctx->invocation, MM_CORE_ERROR, MM_CORE_ERROR_WRONG_STATE, + "Cannot delete CBM: missing CBM list"); + handle_delete_context_free (ctx); + return; + } + + mm_obj_info (self, "processing user request to delete CBM message '%s'...", ctx->path); + mm_cbm_list_delete_cbm (list, + ctx->path, + (GAsyncReadyCallback)handle_delete_ready, + ctx); +} + +static gboolean +handle_delete (MmGdbusModemCellBroadcast *skeleton, + GDBusMethodInvocation *invocation, + const gchar *path, + MMIfaceModemCellBroadcast *self) +{ + HandleDeleteContext *ctx; + + ctx = g_slice_new0 (HandleDeleteContext); + ctx->skeleton = g_object_ref (skeleton); + ctx->invocation = g_object_ref (invocation); + ctx->self = g_object_ref (self); + ctx->path = g_strdup (path); + + mm_base_modem_authorize (MM_BASE_MODEM (self), + invocation, + MM_AUTHORIZATION_CELL_BROADCAST, + (GAsyncReadyCallback)handle_delete_auth_ready, + ctx); + return TRUE; +} + +/*****************************************************************************/ + +static gboolean +handle_list (MmGdbusModemCellBroadcast *skeleton, + GDBusMethodInvocation *invocation, + MMIfaceModemCellBroadcast *self) +{ + g_auto(GStrv) paths = NULL; + g_autoptr(MMCbmList) list = NULL; + + if (mm_iface_modem_abort_invocation_if_state_not_reached (MM_IFACE_MODEM (self), + invocation, + MM_MODEM_STATE_ENABLED)) + return TRUE; + + g_object_get (self, + MM_IFACE_MODEM_CELL_BROADCAST_CBM_LIST, &list, + NULL); + if (!list) { + mm_dbus_method_invocation_return_error_literal (invocation, MM_CORE_ERROR, MM_CORE_ERROR_WRONG_STATE, + "Cannot list CBM: missing CBM list"); + return TRUE; + } + + mm_obj_info (self, "processing user request to list CBM messages..."); + paths = mm_cbm_list_get_paths (list); + mm_gdbus_modem_cell_broadcast_complete_list (skeleton, + invocation, + (const gchar *const *)paths); + mm_obj_info (self, "reported %u CBM messages available", paths ? g_strv_length (paths) : 0); + return TRUE; +} + +/*****************************************************************************/ + +MMBaseCbm * +mm_iface_modem_cell_broadcast_create_cbm (MMIfaceModemCellBroadcast *self) +{ + g_assert (MM_IFACE_MODEM_CELL_BROADCAST_GET_IFACE (self)->create_cbm != NULL); + + return MM_IFACE_MODEM_CELL_BROADCAST_GET_IFACE (self)->create_cbm (self); +} + +/*****************************************************************************/ + +typedef struct _InitializationContext InitializationContext; +static void interface_initialization_step (GTask *task); + +typedef enum { + INITIALIZATION_STEP_FIRST, + INITIALIZATION_STEP_CHECK_SUPPORT, + INITIALIZATION_STEP_FAIL_IF_UNSUPPORTED, + INITIALIZATION_STEP_LAST +} InitializationStep; + +struct _InitializationContext { + MmGdbusModemCellBroadcast *skeleton; + InitializationStep step; +}; + +static void +initialization_context_free (InitializationContext *ctx) +{ + g_object_unref (ctx->skeleton); + g_free (ctx); +} + +gboolean +mm_iface_modem_cell_broadcast_initialize_finish (MMIfaceModemCellBroadcast *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +check_support_ready (MMIfaceModemCellBroadcast *self, + GAsyncResult *res, + GTask *task) +{ + InitializationContext *ctx; + GError *error = NULL; + + if (!MM_IFACE_MODEM_CELL_BROADCAST_GET_IFACE (self)->check_support_finish (self, + res, + &error)) { + if (error) { + /* This error shouldn't be treated as critical */ + mm_obj_dbg (self, "cell broadcast support check failed: %s", error->message); + g_error_free (error); + } + } else { + /* CellBroadcast is supported! */ + g_object_set_qdata (G_OBJECT (self), + supported_quark, + GUINT_TO_POINTER (TRUE)); + } + + /* Go on to next step */ + ctx = g_task_get_task_data (task); + ctx->step++; + interface_initialization_step (task); +} + +static void +interface_initialization_step (GTask *task) +{ + MMIfaceModemCellBroadcast *self; + InitializationContext *ctx; + + /* Don't run new steps if we're cancelled */ + if (g_task_return_error_if_cancelled (task)) { + g_object_unref (task); + return; + } + + self = g_task_get_source_object (task); + ctx = g_task_get_task_data (task); + + switch (ctx->step) { + case INITIALIZATION_STEP_FIRST: + /* Setup quarks if we didn't do it before */ + if (G_UNLIKELY (!support_checked_quark)) + support_checked_quark = (g_quark_from_static_string ( + SUPPORT_CHECKED_TAG)); + if (G_UNLIKELY (!supported_quark)) + supported_quark = (g_quark_from_static_string ( + SUPPORTED_TAG)); + + ctx->step++; + /* fall through */ + + case INITIALIZATION_STEP_CHECK_SUPPORT: + if (!GPOINTER_TO_UINT (g_object_get_qdata (G_OBJECT (self), + support_checked_quark))) { + /* Set the checked flag so that we don't run it again */ + g_object_set_qdata (G_OBJECT (self), + support_checked_quark, + GUINT_TO_POINTER (TRUE)); + /* Initially, assume we don't support it */ + g_object_set_qdata (G_OBJECT (self), + supported_quark, + GUINT_TO_POINTER (FALSE)); + + if (MM_IFACE_MODEM_CELL_BROADCAST_GET_IFACE (self)->check_support && + MM_IFACE_MODEM_CELL_BROADCAST_GET_IFACE (self)->check_support_finish) { + MM_IFACE_MODEM_CELL_BROADCAST_GET_IFACE (self)->check_support ( + self, + (GAsyncReadyCallback)check_support_ready, + task); + return; + } + + /* If there is no implementation to check support, assume we DON'T + * support it. */ + } + ctx->step++; + /* fall through */ + + case INITIALIZATION_STEP_FAIL_IF_UNSUPPORTED: + if (!GPOINTER_TO_UINT (g_object_get_qdata (G_OBJECT (self), + supported_quark))) { + g_task_return_new_error (task, + MM_CORE_ERROR, + MM_CORE_ERROR_UNSUPPORTED, + "CellBroadcast not supported"); + g_object_unref (task); + return; + } + ctx->step++; + /* fall through */ + + case INITIALIZATION_STEP_LAST: + /* We are done without errors! */ + g_signal_connect (ctx->skeleton, + "handle-delete", + G_CALLBACK (handle_delete), + self); + g_signal_connect (ctx->skeleton, + "handle-list", + G_CALLBACK (handle_list), + self); + + /* Finally, export the new interface */ + mm_gdbus_object_skeleton_set_modem_cell_broadcast (MM_GDBUS_OBJECT_SKELETON (self), + MM_GDBUS_MODEM_CELL_BROADCAST (ctx->skeleton)); + + g_task_return_boolean (task, TRUE); + g_object_unref (task); + return; + + default: + break; + } + + g_assert_not_reached (); +} + +void +mm_iface_modem_cell_broadcast_initialize (MMIfaceModemCellBroadcast *self, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + InitializationContext *ctx; + MmGdbusModemCellBroadcast *skeleton = NULL; + GTask *task; + + /* Did we already create it? */ + g_object_get (self, + MM_IFACE_MODEM_CELL_BROADCAST_DBUS_SKELETON, &skeleton, + NULL); + if (!skeleton) { + skeleton = mm_gdbus_modem_cell_broadcast_skeleton_new (); + g_object_set (self, + MM_IFACE_MODEM_CELL_BROADCAST_DBUS_SKELETON, skeleton, + NULL); + } + + /* Perform async initialization here */ + + ctx = g_new0 (InitializationContext, 1); + ctx->step = INITIALIZATION_STEP_FIRST; + ctx->skeleton = skeleton; + + task = g_task_new (self, cancellable, callback, user_data); + g_task_set_task_data (task, ctx, (GDestroyNotify)initialization_context_free); + + interface_initialization_step (task); +} + +void +mm_iface_modem_cell_broadcast_shutdown (MMIfaceModemCellBroadcast *self) +{ + /* Unexport DBus interface and remove the skeleton */ + mm_gdbus_object_skeleton_set_modem_cell_broadcast (MM_GDBUS_OBJECT_SKELETON (self), NULL); + g_object_set (self, + MM_IFACE_MODEM_CELL_BROADCAST_DBUS_SKELETON, NULL, + NULL); +} + +/*****************************************************************************/ + +static void +update_message_list (MmGdbusModemCellBroadcast *skeleton, + MMCbmList *list) +{ + gchar **paths; + + paths = mm_cbm_list_get_paths (list); + mm_gdbus_modem_cell_broadcast_set_cell_broadcasts (skeleton, (const gchar *const *)paths); + g_strfreev (paths); + + g_dbus_interface_skeleton_flush (G_DBUS_INTERFACE_SKELETON (skeleton)); +} + +static void +cbm_added (MMCbmList *list, + const gchar *cbm_path, + gboolean received, + MmGdbusModemCellBroadcast *skeleton) +{ + update_message_list (skeleton, list); + mm_gdbus_modem_cell_broadcast_emit_added (skeleton, cbm_path); +} + +static void +cbm_deleted (MMCbmList *list, + const gchar *cbm_path, + MmGdbusModemCellBroadcast *skeleton) +{ + update_message_list (skeleton, list); + mm_gdbus_modem_cell_broadcast_emit_deleted (skeleton, cbm_path); +} + +/*****************************************************************************/ + +typedef struct _EnablingContext EnablingContext; +static void interface_enabling_step (GTask *task); + +typedef enum { + ENABLING_STEP_FIRST, + ENABLING_STEP_SETUP_UNSOLICITED_EVENTS, + ENABLING_STEP_ENABLE_UNSOLICITED_EVENTS, + ENABLING_STEP_LAST +} EnablingStep; + +struct _EnablingContext { + EnablingStep step; + MmGdbusModemCellBroadcast *skeleton; +}; + +static void +enabling_context_free (EnablingContext *ctx) +{ + if (ctx->skeleton) + g_object_unref (ctx->skeleton); + g_free (ctx); +} + +gboolean +mm_iface_modem_cell_broadcast_enable_finish (MMIfaceModemCellBroadcast *self, + GAsyncResult *res, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +setup_unsolicited_events_ready (MMIfaceModemCellBroadcast *self, + GAsyncResult *res, + GTask *task) +{ + EnablingContext *ctx; + GError *error = NULL; + + MM_IFACE_MODEM_CELL_BROADCAST_GET_IFACE (self)->setup_unsolicited_events_finish (self, res, &error); + if (error) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + /* Go on to next step */ + ctx = g_task_get_task_data (task); + ctx->step++; + interface_enabling_step (task); +} + +static void +enable_unsolicited_events_ready (MMIfaceModemCellBroadcast *self, + GAsyncResult *res, + GTask *task) +{ + EnablingContext *ctx; + GError *error = NULL; + + /* Not critical! */ + if (!MM_IFACE_MODEM_CELL_BROADCAST_GET_IFACE (self)->enable_unsolicited_events_finish (self, res, &error)) { + mm_obj_dbg (self, "Can't enable unsolicited events: %s", error->message); + g_error_free (error); + } + + /* Go on with next step */ + ctx = g_task_get_task_data (task); + ctx->step++; + interface_enabling_step (task); +} + +static void +interface_enabling_step (GTask *task) +{ + MMIfaceModemCellBroadcast *self; + EnablingContext *ctx; + + /* Don't run new steps if we're cancelled */ + if (g_task_return_error_if_cancelled (task)) { + g_object_unref (task); + return; + } + + self = g_task_get_source_object (task); + ctx = g_task_get_task_data (task); + + switch (ctx->step) { + case ENABLING_STEP_FIRST: { + g_autoptr (MMCbmList) list = NULL; + + list = mm_cbm_list_new (MM_BASE_MODEM (self)); + g_object_set (self, + MM_IFACE_MODEM_CELL_BROADCAST_CBM_LIST, list, + NULL); + + /* Connect to list's signals */ + g_signal_connect (list, + MM_CBM_ADDED, + G_CALLBACK (cbm_added), + ctx->skeleton); + g_signal_connect (list, + MM_CBM_DELETED, + G_CALLBACK (cbm_deleted), + ctx->skeleton); + ctx->step++; + } /* fall through */ + + case ENABLING_STEP_SETUP_UNSOLICITED_EVENTS: + /* Allow setting up unsolicited events */ + if (MM_IFACE_MODEM_CELL_BROADCAST_GET_IFACE (self)->setup_unsolicited_events && + MM_IFACE_MODEM_CELL_BROADCAST_GET_IFACE (self)->setup_unsolicited_events_finish) { + MM_IFACE_MODEM_CELL_BROADCAST_GET_IFACE (self)->setup_unsolicited_events ( + self, + (GAsyncReadyCallback)setup_unsolicited_events_ready, + task); + return; + } + ctx->step++; + /* fall through */ + + case ENABLING_STEP_ENABLE_UNSOLICITED_EVENTS: + /* Allow enabling unsolicited events */ + if (MM_IFACE_MODEM_CELL_BROADCAST_GET_IFACE (self)->enable_unsolicited_events && + MM_IFACE_MODEM_CELL_BROADCAST_GET_IFACE (self)->enable_unsolicited_events_finish) { + MM_IFACE_MODEM_CELL_BROADCAST_GET_IFACE (self)->enable_unsolicited_events ( + self, + (GAsyncReadyCallback)enable_unsolicited_events_ready, + task); + return; + } + ctx->step++; + /* fall through */ + + case ENABLING_STEP_LAST: + /* We are done without errors! */ + g_task_return_boolean (task, TRUE); + g_object_unref (task); + return; + + default: + break; + } + + g_assert_not_reached (); +} + +void +mm_iface_modem_cell_broadcast_enable (MMIfaceModemCellBroadcast *self, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + EnablingContext *ctx; + GTask *task; + + ctx = g_new0 (EnablingContext, 1); + ctx->step = ENABLING_STEP_FIRST; + + task = g_task_new (self, cancellable, callback, user_data); + g_task_set_task_data (task, ctx, (GDestroyNotify)enabling_context_free); + + g_object_get (self, + MM_IFACE_MODEM_CELL_BROADCAST_DBUS_SKELETON, &ctx->skeleton, + NULL); + if (!ctx->skeleton) { + g_task_return_new_error (task, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Can't get interface skeleton"); + g_object_unref (task); + return; + } + + interface_enabling_step (task); +} + +/*****************************************************************************/ + +gboolean +mm_iface_modem_cell_broadcast_take_part (MMIfaceModemCellBroadcast *self, + MMCbmPart *cbm_part, + MMCbmState state) +{ + g_autoptr(MMCbmList) list = NULL; + g_autoptr(GError) error = NULL; + gboolean added = FALSE; + + g_object_get (self, + MM_IFACE_MODEM_CELL_BROADCAST_CBM_LIST, &list, + NULL); + + if (list) { + added = mm_cbm_list_take_part (list, cbm_part, state, &error); + if (!added) + mm_obj_dbg (self, "Can't take part in CBM list: %s", error->message); + } + + /* If part wasn't taken, we need to free the part ourselves */ + if (!added) + mm_cbm_part_free (cbm_part); + + return added; +} + +/*****************************************************************************/ + +static void +mm_iface_modem_cell_broadcast_default_init (MMIfaceModemCellBroadcastInterface *iface) +{ + static gsize initialized = 0; + + if (!g_once_init_enter (&initialized)) + return; + + /* Properties */ + g_object_interface_install_property ( + iface, + g_param_spec_object (MM_IFACE_MODEM_CELL_BROADCAST_DBUS_SKELETON, + "CellBroadcast DBus skeleton", + "DBus skeleton for the CellBroadcast interface", + MM_GDBUS_TYPE_MODEM_CELL_BROADCAST_SKELETON, + G_PARAM_READWRITE)); + + g_object_interface_install_property ( + iface, + g_param_spec_object (MM_IFACE_MODEM_CELL_BROADCAST_CBM_LIST, + "CBM list", + "List of CBM objects managed in the interface", + MM_TYPE_CBM_LIST, + G_PARAM_READWRITE)); + + g_once_init_leave (&initialized, 1); +} diff --git a/src/mm-iface-modem-cell-broadcast.h b/src/mm-iface-modem-cell-broadcast.h new file mode 100644 index 00000000..5990b865 --- /dev/null +++ b/src/mm-iface-modem-cell-broadcast.h @@ -0,0 +1,120 @@ +/* -*- 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) 2024 Guido Günther <agx@sigxcpu.org> + */ + +#ifndef MM_IFACE_MODEM_CELLBROADCAST_H +#define MM_IFACE_MODEM_CELLBROADCAST_H + +#include <glib-object.h> +#include <gio/gio.h> + +#define _LIBMM_INSIDE_MM +#include <libmm-glib.h> + +#include "mm-cbm-part.h" +#include "mm-base-cbm.h" + +#define MM_TYPE_IFACE_MODEM_CELL_BROADCAST mm_iface_modem_cell_broadcast_get_type () +G_DECLARE_INTERFACE (MMIfaceModemCellBroadcast, mm_iface_modem_cell_broadcast, MM, IFACE_MODEM_CELL_BROADCAST, MMIfaceModem) + +#define MM_IFACE_MODEM_CELL_BROADCAST_DBUS_SKELETON "iface-modem-cell-broadcast-dbus-skeleton" +#define MM_IFACE_MODEM_CELL_BROADCAST_CBM_LIST "iface-modem-cell-broadcast-cbm-list" + +struct _MMIfaceModemCellBroadcastInterface { + GTypeInterface g_iface; + + /* Check for CellBroadcast support (async) */ + void (* check_support) (MMIfaceModemCellBroadcast *self, + GAsyncReadyCallback callback, + gpointer user_data); + gboolean (* check_support_finish) (MMIfaceModemCellBroadcast *self, + GAsyncResult *res, + GError **error); + + /* Asynchronous setting up unsolicited CellBroadcast reception events */ + void (* setup_unsolicited_events) (MMIfaceModemCellBroadcast *self, + GAsyncReadyCallback callback, + gpointer user_data); + gboolean (* setup_unsolicited_events_finish) (MMIfaceModemCellBroadcast *self, + GAsyncResult *res, + GError **error); + + /* Asynchronous cleaning up of unsolicited CellBroadcast reception events */ + void (* cleanup_unsolicited_events) (MMIfaceModemCellBroadcast *self, + GAsyncReadyCallback callback, + gpointer user_data); + gboolean (* cleanup_unsolicited_events_finish) (MMIfaceModemCellBroadcast *self, + GAsyncResult *res, + GError **error); + + /* Asynchronous enabling unsolicited CellBroadcast reception events */ + void (* enable_unsolicited_events) (MMIfaceModemCellBroadcast *self, + GAsyncReadyCallback callback, + gpointer user_data); + gboolean (* enable_unsolicited_events_finish) (MMIfaceModemCellBroadcast *self, + GAsyncResult *res, + GError **error); + + /* Asynchronous disabling unsolicited CellBroadcast reception events */ + void (* disable_unsolicited_events) (MMIfaceModemCellBroadcast *self, + GAsyncReadyCallback callback, + gpointer user_data); + gboolean (* disable_unsolicited_events_finish) (MMIfaceModemCellBroadcast *self, + GAsyncResult *res, + GError **error); + + /* Create Cbm objects */ + MMBaseCbm * (* create_cbm) (MMIfaceModemCellBroadcast *self); +}; + +/* Initialize CellBroadcast interface (async) */ +void mm_iface_modem_cell_broadcast_initialize (MMIfaceModemCellBroadcast *self, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +gboolean mm_iface_modem_cell_broadcast_initialize_finish (MMIfaceModemCellBroadcast *self, + GAsyncResult *res, + GError **error); +/* Enable CellBroadcast interface (async) */ +void mm_iface_modem_cell_broadcast_enable (MMIfaceModemCellBroadcast *self, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +gboolean mm_iface_modem_cell_broadcast_enable_finish (MMIfaceModemCellBroadcast *self, + GAsyncResult *res, + GError **error); + +/* Disable CellBroadcast interface (async) */ +void mm_iface_modem_cell_broadcast_disable (MMIfaceModemCellBroadcast *self, + GAsyncReadyCallback callback, + gpointer user_data); +gboolean mm_iface_modem_cell_broadcast_disable_finish (MMIfaceModemCellBroadcast *self, + GAsyncResult *res, + GError **error); +/* Shutdown CellBroadcast interface */ +void mm_iface_modem_cell_broadcast_shutdown (MMIfaceModemCellBroadcast *self); + +/* Bind properties for simple GetStatus() */ +void mm_iface_modem_cell_broadcast_bind_simple_status (MMIfaceModemCellBroadcast *self, + MMSimpleStatus *status); + +/* Report new CBM part */ +gboolean mm_iface_modem_cell_broadcast_take_part (MMIfaceModemCellBroadcast *self, + MMCbmPart *cbm_part, + MMCbmState state); + +/* CBM creation */ +MMBaseCbm *mm_iface_modem_cell_broadcast_create_cbm (MMIfaceModemCellBroadcast *self); + +#endif /* MM_IFACE_MODEM_CELLBROADCAST_H */ |