572 lines
17 KiB
C
572 lines
17 KiB
C
/* -*- 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 incomplete multipart messages, so clients can make use of it
|
|
* Only the STATE of the CBM object will be valid in the exported DBus
|
|
* interface initially.*/
|
|
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));
|
|
}
|