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-cbm-list.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-cbm-list.c')
-rw-r--r-- | src/mm-cbm-list.c | 402 |
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); +} |