aboutsummaryrefslogtreecommitdiff
path: root/src/mm-base-cbm.c
diff options
context:
space:
mode:
authorGuido Günther <agx@sigxcpu.org>2023-12-05 20:05:17 +0100
committerAleksander Morgado <aleksander@aleksander.es>2024-12-01 21:41:10 +0000
commit62f7b76e8ea8f682048840741d1177e6c93a1c80 (patch)
tree37c6a4f9b96b942daf0fa1f85c6c322134a20469 /src/mm-base-cbm.c
parenteedf78d6622d09862d9e91a54358f2b56344cc22 (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.c571
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));
+}