/* -*- 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 */ #include #include #include #include #include #include #include #define _LIBMM_INSIDE_MM #include #include "mm-base-cbm.h" #include "mm-cbm-part.h" #include "mm-log-object.h" #include "mm-modem-helpers.h" #include "mm-error-helpers.h" #include "mm-bind.h" static void log_object_iface_init (MMLogObjectInterface *iface); static void bind_iface_init (MMBindInterface *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) G_IMPLEMENT_INTERFACE (MM_TYPE_BIND, bind_iface_init)) enum { PROP_0, PROP_PATH, PROP_CONNECTION, PROP_BIND_TO, 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 object this CBM is bound to */ GObject *bind_to; /* 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 (GObject *bind_to) { return MM_BASE_CBM (g_object_new (MM_TYPE_BASE_CBM, MM_BIND_TO, bind_to, NULL)); } MMBaseCbm * mm_base_cbm_new_with_part (GObject *bind_to, MMCbmState state, guint max_parts, MMCbmPart *first_part, GError **error) { MMBaseCbm *self; if (state == MM_CBM_STATE_RECEIVED) state = MM_CBM_STATE_RECEIVING; /* Create a CBM object as defined by the interface */ self = mm_base_cbm_new (bind_to); 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_BIND_TO: g_clear_object (&self->priv->bind_to); self->priv->bind_to = g_value_dup_object (value); mm_bind_to (MM_BIND (self), MM_BASE_CBM_CONNECTION, self->priv->bind_to); 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_BIND_TO: g_value_set_object (value, self->priv->bind_to); 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->bind_to); 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 bind_iface_init (MMBindInterface *iface) { } 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); g_object_class_override_property (object_class, PROP_BIND_TO, MM_BIND_TO); 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)); }