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/mm-base-cbm.c | |
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/mm-base-cbm.c')
-rw-r--r-- | src/mm-base-cbm.c | 571 |
1 files changed, 571 insertions, 0 deletions
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)); +} |