aboutsummaryrefslogtreecommitdiff
path: root/src/mm-cbm-list.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-cbm-list.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-cbm-list.c')
-rw-r--r--src/mm-cbm-list.c402
1 files changed, 402 insertions, 0 deletions
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);
+}