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>
This commit is contained in:
Guido Günther
2024-12-01 21:41:10 +00:00
committed by Aleksander Morgado
parent eedf78d662
commit 62f7b76e8e
16 changed files with 2103 additions and 1 deletions
+1
View File
@@ -133,6 +133,7 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
/* Prefix for object paths */ /* Prefix for object paths */
#define MM_DBUS_MODEM_PREFIX MM_DBUS_PATH "/Modem" #define MM_DBUS_MODEM_PREFIX MM_DBUS_PATH "/Modem"
#define MM_DBUS_BEARER_PREFIX MM_DBUS_PATH "/Bearer" #define MM_DBUS_BEARER_PREFIX MM_DBUS_PATH "/Bearer"
#define MM_DBUS_CBM_PREFIX MM_DBUS_PATH "/CBM"
#define MM_DBUS_SIM_PREFIX MM_DBUS_PATH "/SIM" #define MM_DBUS_SIM_PREFIX MM_DBUS_PATH "/SIM"
#define MM_DBUS_SMS_PREFIX MM_DBUS_PATH "/SMS" #define MM_DBUS_SMS_PREFIX MM_DBUS_PATH "/SMS"
#define MM_DBUS_CALL_PREFIX MM_DBUS_PATH "/Call" #define MM_DBUS_CALL_PREFIX MM_DBUS_PATH "/Call"
+16
View File
@@ -1107,6 +1107,22 @@ typedef enum { /*< underscore_name=mm_sms_cdma_service_category >*/
MM_SMS_CDMA_SERVICE_CATEGORY_CMAS_TEST = 0x1004, MM_SMS_CDMA_SERVICE_CATEGORY_CMAS_TEST = 0x1004,
} MMSmsCdmaServiceCategory; } MMSmsCdmaServiceCategory;
/**
* MMCbmState:
* @MM_CBM_STATE_UNKNOWN: State unknown or not reportable.
* @MM_CBM_STATE_RECEIVING: The message is being received but is not yet complete.
* @MM_CBM_STATE_RECEIVED: The message has been completely received.
*
* State of a given CBM.
*
* Since: 1.24
*/
typedef enum { /*< underscore_name=mm_cbm_state >*/
MM_CBM_STATE_UNKNOWN = 0,
MM_CBM_STATE_RECEIVING = 1,
MM_CBM_STATE_RECEIVED = 2,
} MMCbmState;
/** /**
* MMModemLocationSource: * MMModemLocationSource:
* @MM_MODEM_LOCATION_SOURCE_NONE: None. * @MM_MODEM_LOCATION_SOURCE_NONE: None.
+2
View File
@@ -7,7 +7,9 @@
<xi:include href="org.freedesktop.ModemManager1.Bearer.xml"/> <xi:include href="org.freedesktop.ModemManager1.Bearer.xml"/>
<xi:include href="org.freedesktop.ModemManager1.Sms.xml"/> <xi:include href="org.freedesktop.ModemManager1.Sms.xml"/>
<xi:include href="org.freedesktop.ModemManager1.Call.xml"/> <xi:include href="org.freedesktop.ModemManager1.Call.xml"/>
<xi:include href="org.freedesktop.ModemManager1.Cbm.xml"/>
<xi:include href="org.freedesktop.ModemManager1.Modem.xml"/> <xi:include href="org.freedesktop.ModemManager1.Modem.xml"/>
<xi:include href="org.freedesktop.ModemManager1.Modem.CellBroadcast.xml"/>
<xi:include href="org.freedesktop.ModemManager1.Modem.Voice.xml"/> <xi:include href="org.freedesktop.ModemManager1.Modem.Voice.xml"/>
<xi:include href="org.freedesktop.ModemManager1.Modem.Modem3gpp.xml"/> <xi:include href="org.freedesktop.ModemManager1.Modem.Modem3gpp.xml"/>
<xi:include href="org.freedesktop.ModemManager1.Modem.Modem3gpp.ProfileManager.xml"/> <xi:include href="org.freedesktop.ModemManager1.Modem.Modem3gpp.ProfileManager.xml"/>
+4 -1
View File
@@ -13,6 +13,7 @@ mm_ifaces_bearer = files('org.freedesktop.ModemManager1.Bearer.xml')
mm_ifaces_call = files('org.freedesktop.ModemManager1.Call.xml') mm_ifaces_call = files('org.freedesktop.ModemManager1.Call.xml')
mm_ifaces_modem = files( mm_ifaces_modem = files(
'org.freedesktop.ModemManager1.Modem.CellBroadcast.xml',
'org.freedesktop.ModemManager1.Modem.Firmware.xml', 'org.freedesktop.ModemManager1.Modem.Firmware.xml',
'org.freedesktop.ModemManager1.Modem.Location.xml', 'org.freedesktop.ModemManager1.Modem.Location.xml',
'org.freedesktop.ModemManager1.Modem.Messaging.xml', 'org.freedesktop.ModemManager1.Modem.Messaging.xml',
@@ -29,10 +30,12 @@ mm_ifaces_modem = files(
'org.freedesktop.ModemManager1.Modem.xml', 'org.freedesktop.ModemManager1.Modem.xml',
) )
mm_ifaces_cbm = files('org.freedesktop.ModemManager1.Cbm.xml',)
mm_ifaces_sim = files('org.freedesktop.ModemManager1.Sim.xml') mm_ifaces_sim = files('org.freedesktop.ModemManager1.Sim.xml')
mm_ifaces_sms = files('org.freedesktop.ModemManager1.Sms.xml',) mm_ifaces_sms = files('org.freedesktop.ModemManager1.Sms.xml',)
install_data( install_data(
mm_ifaces + mm_ifaces_bearer + mm_ifaces_call + mm_ifaces_modem + mm_ifaces_sim + mm_ifaces_sms, mm_ifaces + mm_ifaces_bearer + mm_ifaces_call + mm_ifaces_cbm +
mm_ifaces_modem + mm_ifaces_sim + mm_ifaces_sms,
install_dir: dbus_interfaces_dir, install_dir: dbus_interfaces_dir,
) )
@@ -0,0 +1,69 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!--
ModemManager 1.0 Interface Specification
Copyright (C) 2024 Guido Günther <agx@sigxcpu.org>
-->
<node name="/" xmlns:doc="http://www.freedesktop.org/dbus/1.0/doc.dtd">
<!--
org.freedesktop.ModemManager1.Cbm:
@short_description: The ModemManager Cbm interface.
The Cbm interface defines operations and properties of a single cell broadcast message.
-->
<interface name="org.freedesktop.ModemManager1.Cbm">
<!--
State:
A <link linkend="MMCbmState">MMCbmState</link> value,
describing the state of the message.
Since: 1.24
-->
<property name="State" type="u" access="read" />
<!--
Text:
Message text, in UTF-8.
Since: 1.24
-->
<property name="Text" type="s" access="read" />
<!--
Channel:
The channel (or message identifier) specifying the source and
type of the cell broadcast message.
Since: 1.24
-->
<property name="Channel" type="u" access="read" />
<!--
MessageCode:
The message code of this CBM. The message code differentiates
between CBMs from the same channel.
Since: 1.24
-->
<property name="MessageCode" type="u" access="read" />
<!--
Update:
The update number of this CBM. The update number allows to update CBMs
with the same channel and message code.
Since: 1.24
-->
<property name="Update" type="u" access="read" />
</interface>
</node>
@@ -0,0 +1,91 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!--
ModemManager 1.0 Interface Specification
Copyright (C) 2024 Guido Günther <agx@sigxcpu.org>
-->
<node name="/" xmlns:doc="http://www.freedesktop.org/dbus/1.0/doc.dtd">
<!--
org.freedesktop.ModemManager1.Modem.CellBroadcast:
@short_description: The ModemManager CellBroadcast service interface.
This interface allows clients to configure the details of CellBroadcast
service information.
This interface will only be available once the modem is ready to be
registered in the cellular network.
-->
<interface name="org.freedesktop.ModemManager1.Modem.CellBroadcast">
<!--
List:
@result: The list of CBM object paths.
Retrieve all cell broadcast messages.
This method should only be used once and subsequent information
retrieved either by listening for the
#org.freedesktop.ModemManager1.Modem.CellBroadcast::Added signal,
or by querying the specific CBM object of interest.
Since: 1.24
-->
<method name="List">
<arg name="result" type="ao" direction="out" />
</method>
<!--
Delete:
@path: The object path of the CBM to delete.
Delete an cell broadcast message
Since: 1.24
-->
<method name="Delete">
<arg name="path" type="o" direction="in" />
</method>
<!--
Added:
@path: Object path of the new CBM.
Emitted when any part of a new cell broadcast message has been
received. Not all parts may have been received and the message may not
be complete.
Check the
'<link linkend="gdbus-property-org-freedesktop-ModemManager1-Cbm.State">State</link>'
property to determine if the message is complete.
Since: 1.24
-->
<signal name="Added">
<arg name="path" type="o" />
</signal>
<!--
Deleted:
@path: Object path of the now deleted CBM.
Emitted when a message has been deleted.
Since: 1.24
-->
<signal name="Deleted">
<arg name="path" type="o" />
</signal>
<!--
CellBroadcasts:
The list of CBM object paths.
Since: 1.24
-->
<property name="CellBroadcasts" type="ao" access="read" />
</interface>
</node>
+1
View File
@@ -132,6 +132,7 @@ gen_sources += custom_target(
gdbus_ifaces = { gdbus_ifaces = {
'bearer': {'sources': mm_ifaces_bearer, 'object_manager': false}, 'bearer': {'sources': mm_ifaces_bearer, 'object_manager': false},
'call': {'sources': mm_ifaces_call, 'object_manager': false}, 'call': {'sources': mm_ifaces_call, 'object_manager': false},
'cbm': {'sources': mm_ifaces_cbm, 'object_manager': false},
'manager': {'sources': mm_ifaces, 'object_manager': false}, 'manager': {'sources': mm_ifaces, 'object_manager': false},
'sim': {'sources': mm_ifaces_sim, 'object_manager': false}, 'sim': {'sources': mm_ifaces_sim, 'object_manager': false},
} }
+1
View File
@@ -103,6 +103,7 @@
#include <mm-gdbus-manager.h> #include <mm-gdbus-manager.h>
#include <mm-gdbus-modem.h> #include <mm-gdbus-modem.h>
#include <mm-gdbus-bearer.h> #include <mm-gdbus-bearer.h>
#include <mm-gdbus-cbm.h>
#include <mm-gdbus-sim.h> #include <mm-gdbus-sim.h>
#include <mm-gdbus-sms.h> #include <mm-gdbus-sms.h>
#include <mm-gdbus-call.h> #include <mm-gdbus-call.h>
+3
View File
@@ -259,6 +259,7 @@ sources = files(
'mm-auth-provider.c', 'mm-auth-provider.c',
'mm-base-bearer.c', 'mm-base-bearer.c',
'mm-base-call.c', 'mm-base-call.c',
'mm-base-cbm.c',
'mm-base-manager.c', 'mm-base-manager.c',
'mm-base-modem-at.c', 'mm-base-modem-at.c',
'mm-base-modem.c', 'mm-base-modem.c',
@@ -268,6 +269,7 @@ sources = files(
'mm-broadband-bearer.c', 'mm-broadband-bearer.c',
'mm-broadband-modem.c', 'mm-broadband-modem.c',
'mm-call-list.c', 'mm-call-list.c',
'mm-cbm-list.c',
'mm-context.c', 'mm-context.c',
'mm-device.c', 'mm-device.c',
'mm-dispatcher.c', 'mm-dispatcher.c',
@@ -280,6 +282,7 @@ sources = files(
'mm-iface-modem-3gpp-ussd.c', 'mm-iface-modem-3gpp-ussd.c',
'mm-iface-modem.c', 'mm-iface-modem.c',
'mm-iface-modem-cdma.c', 'mm-iface-modem-cdma.c',
'mm-iface-modem-cell-broadcast.c',
'mm-iface-modem-firmware.c', 'mm-iface-modem-firmware.c',
'mm-iface-modem-location.c', 'mm-iface-modem-location.c',
'mm-iface-modem-messaging.c', 'mm-iface-modem-messaging.c',
+1
View File
@@ -37,6 +37,7 @@
#define MM_AUTHORIZATION_LOCATION "org.freedesktop.ModemManager1.Location" #define MM_AUTHORIZATION_LOCATION "org.freedesktop.ModemManager1.Location"
#define MM_AUTHORIZATION_TIME "org.freedesktop.ModemManager1.Time" #define MM_AUTHORIZATION_TIME "org.freedesktop.ModemManager1.Time"
#define MM_AUTHORIZATION_FIRMWARE "org.freedesktop.ModemManager1.Firmware" #define MM_AUTHORIZATION_FIRMWARE "org.freedesktop.ModemManager1.Firmware"
#define MM_AUTHORIZATION_CELL_BROADCAST "org.freedesktop.ModemManager1.CellBroadcast"
typedef struct _MMAuthProvider MMAuthProvider; typedef struct _MMAuthProvider MMAuthProvider;
typedef struct _MMAuthProviderClass MMAuthProviderClass; typedef struct _MMAuthProviderClass MMAuthProviderClass;
+571
View File
@@ -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));
}
+81
View File
@@ -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 */
+402
View File
@@ -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);
}
+86
View File
@@ -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 */
+654
View File
@@ -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);
}
+120
View File
@@ -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 */