1
0
Fork 0
mirror of https://gitlab.gnome.org/GNOME/calls.git synced 2024-05-18 11:09:28 +00:00

Merge branch 'wip/policy-engine' into 'main'

Draft: Revamp plugin infrastructure to accomodate "policy engine" plugins

See merge request GNOME/calls!588
This commit is contained in:
Evangelos Ribeiro Tzaras 2024-04-18 12:45:23 +00:00
commit 7a7a3b451f
14 changed files with 1321 additions and 0 deletions

View file

@ -4,5 +4,7 @@ subdir('provider/dummy')
subdir('provider/ofono')
subdir('provider/sip')
subdir('provider/tests')
subdir('policy/keyfile')
subdir('policy/tests')
calls_plugins = [calls_mm, calls_dummy, calls_ofono, calls_sip]

View file

@ -0,0 +1,221 @@
/*
* Copyright (C) 2023 Purism SPC
*
* This file is part of Calls.
*
* Calls 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 3 of the License, or
* (at your option) any later version.
*
* Calls 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.
*
* You should have received a copy of the GNU General Public License
* along with Calls. If not, see <http://www.gnu.org/licenses/>.
*
* Author: Evangelos Ribeiro Tzaras <devrtz@fortysixandtwo.eu>
*
* SPDX-License-Identifier: GPL-3.0-or-later
*
*
*/
#define G_LOG_DOMAIN "CallsKeyfileEngine"
#include "calls-keyfile-engine.h"
#include "calls-util.h"
/**
* SECTION:calls-keyfile-engine
* @short_description: #GKeyFile based #CallsPolicyEngine
* @Title: CallsKeyfileEngine
*
* A simple #CallsPolicyEngine that looks up numbers in a #GKeyFile
*/
struct _CallsKeyfileEngine {
GObject parent_instance;
GKeyFile *keyfile;
};
static void calls_keyfile_policy_engine_interface_init (CallsPolicyEngineInterface *iface);
G_DEFINE_TYPE_WITH_CODE
(CallsKeyfileEngine, calls_keyfile_engine, G_TYPE_OBJECT,
G_IMPLEMENT_INTERFACE (CALLS_TYPE_POLICY_ENGINE,
calls_keyfile_policy_engine_interface_init))
static char *
normalize_id (const char *id)
{
/* TODO */
return g_strdup (id);
}
static CallsPolicyDecision
decision_from_nick (const char *nick)
{
g_autoptr (GEnumClass) klass = NULL;
GEnumValue *value;
CallsPolicyDecision ret;
if (STR_IS_NULL_OR_EMPTY (nick))
return CALLS_POLICY_DECISION_PASS;
klass = g_type_class_ref (CALLS_TYPE_POLICY_DECISION);
value = g_enum_get_value_by_nick (klass, nick);
if (value)
ret = (CallsPolicyDecision) value->value;
else
ret = CALLS_POLICY_DECISION_INTERNAL_ERROR;
return ret;
}
static void
calls_keyfile_engine_decide (CallsPolicyEngine *policy_engine,
const char *id,
const char *protocol,
const char *origin_id,
GTask *task)
{
CallsKeyfileEngine *self = (CallsKeyfileEngine *) policy_engine;
g_autoptr (GError) error = NULL;
g_auto (GStrv) keys = NULL;
GVariantDict dict;
g_autofree char *id_normalized = NULL;
g_autofree char *action_lookup = NULL;
CallsPolicyDecision action = CALLS_POLICY_DECISION_PASS;
gsize n_keys;
gboolean dict_has_keys = FALSE;
g_assert (CALLS_IS_KEYFILE_ENGINE (self));
g_assert (self->keyfile);
g_assert (G_IS_TASK (task));
//id_normalized = calls_util_number_normalize (id); // TODO
id_normalized = normalize_id (id);
keys = g_key_file_get_keys (self->keyfile, id_normalized, &n_keys, NULL);
if (!keys)
goto out;
action_lookup =
g_key_file_get_string (self->keyfile,
id_normalized,
"action",
&error);
if (STR_IS_NULL_OR_EMPTY (action_lookup))
goto out;
action = decision_from_nick (action_lookup);
/* add any arguments found in the keyfile */
g_variant_dict_init (&dict, NULL);
for (guint i = 0; i < n_keys; i++) {
g_autofree char *val = NULL;
if (g_strcmp0 (keys[i], "action") == 0)
continue;
val = g_key_file_get_string (self->keyfile, id_normalized, keys[i], NULL);
if (STR_IS_NULL_OR_EMPTY (val)) {
g_warning ("Empty value found for key '%s'", keys[i]);
continue;
}
g_variant_dict_insert (&dict, keys[i], "s", val);
dict_has_keys = TRUE;
}
if (!dict_has_keys)
g_variant_dict_clear (&dict);
out:
g_task_return_pointer (task,
calls_policy_decision_new (action,
dict_has_keys ? g_variant_dict_end (&dict) : NULL),
(GDestroyNotify) g_variant_unref);
}
static void
calls_keyfile_engine_dispose (GObject *object)
{
CallsKeyfileEngine *self = CALLS_KEYFILE_ENGINE (object);
g_clear_pointer (&self->keyfile, g_key_file_unref);
G_OBJECT_CLASS (calls_keyfile_engine_parent_class)->dispose (object);
}
static void
calls_keyfile_policy_engine_interface_init (CallsPolicyEngineInterface *iface)
{
iface->decide = calls_keyfile_engine_decide;
}
static void
calls_keyfile_engine_class_init (CallsKeyfileEngineClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->dispose = calls_keyfile_engine_dispose;
}
static void
calls_keyfile_engine_init (CallsKeyfileEngine *self)
{
self->keyfile = g_key_file_new ();
}
CallsKeyfileEngine *
calls_keyfile_engine_new (void)
{
return g_object_new (CALLS_TYPE_KEYFILE_ENGINE, NULL);
}
gboolean
calls_keyfile_engine_load_data (CallsKeyfileEngine *self,
const char *data,
gsize len,
GError **error)
{
g_return_val_if_fail (CALLS_IS_KEYFILE_ENGINE (self), FALSE);
g_return_val_if_fail (self->keyfile, FALSE);
return g_key_file_load_from_data (self->keyfile,
data,
len,
G_KEY_FILE_NONE,
error);
}
gboolean
calls_keyfile_engine_load_file (CallsKeyfileEngine *self,
const char *path,
GError **error)
{
g_return_val_if_fail (CALLS_IS_KEYFILE_ENGINE (self), FALSE);
return g_key_file_load_from_file (self->keyfile,
path,
G_KEY_FILE_KEEP_COMMENTS,
error);
}

View file

@ -0,0 +1,45 @@
/*
* Copyright (C) 2023 Purism SPC
*
* This file is part of Calls.
*
* Calls 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 3 of the License, or
* (at your option) any later version.
*
* Calls 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.
*
* You should have received a copy of the GNU General Public License
* along with Calls. If not, see <http://www.gnu.org/licenses/>.
*
* Author: Evangelos Ribeiro Tzaras <devrtz@fortysixandtwo.eu>
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#pragma once
#include "calls-policy-engine.h"
G_BEGIN_DECLS
#define CALLS_TYPE_KEYFILE_ENGINE calls_keyfile_engine_get_type ()
G_DECLARE_FINAL_TYPE (CallsKeyfileEngine, calls_keyfile_engine, CALLS, KEYFILE_ENGINE, GObject)
CallsKeyfileEngine *calls_keyfile_engine_new (void);
gboolean calls_keyfile_engine_load_data (CallsKeyfileEngine *self,
const char *data,
gsize len,
GError **error);
gboolean calls_keyfile_engine_load_file (CallsKeyfileEngine *self,
const char *path,
GError **error);
G_END_DECLS

View file

@ -0,0 +1,8 @@
[Plugin]
Module=keyfile
Name=Keyfile
Description=Keyfile based policy engine
Authors=Evangelos Ribeiro Tzaras <devrtz@fortysixandtwo.eu>
Copyright=Copyright (C) 2021-2022 Purism SPC
Website=@PACKAGE_URL_RAW@
Version=@PACKAGE_VERSION@

View file

@ -0,0 +1,54 @@
#
# Copyright (C) 2022 Purism SPC
#
# This file is part of Calls.
#
# Calls 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 3 of the License, or
# (at your option) any later version.
#
# Calls 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.
#
# You should have received a copy of the GNU General Public License
# along with Calls. If not, see <http://www.gnu.org/licenses/>.
#
# Author: Evangelos Ribeiro Tzaras <devrtz@fortysixandtwo.eu>
#
# SPDX-License-Identifier: GPL-3.0-or-later
#
keyfile_include = include_directories('.')
keyfile_install_dir = join_paths(full_calls_plugin_libdir, 'keyfile')
keyfile_plugin = configure_file(
input: 'keyfile.plugin.in',
output: 'keyfile.plugin',
configuration: config_data,
install: true,
install_dir: keyfile_install_dir,
)
keyfile_deps = [
dependency('gobject-2.0'),
dependency('gtk+-3.0'),
dependency('libpeas-1.0'),
]
keyfile_sources = files([
'calls-keyfile-engine.c', 'calls-keyfile-engine.h',
])
calls_keyfile_policy = shared_module(
'keyfile-policy',
keyfile_sources,
dependencies: keyfile_deps,
include_directories: calls_includes,
install: true,
install_dir: keyfile_install_dir,
)

View file

@ -0,0 +1,72 @@
#
# Copyright (C) 2022 Purism SPC
#
# This file is part of Calls.
#
# Calls 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 3 of the License, or
# (at your option) any later version.
#
# Calls 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.
#
# You should have received a copy of the GNU General Public License
# along with Calls. If not, see <http://www.gnu.org/licenses/>.
#
# Author: Evangelos Ribeiro Tzaras <devrtz@fortysixandtwo.eu>
#
# SPDX-License-Identifier: GPL-3.0-or-later
#
if get_option('tests')
test_env = [
'G_TEST_SRCDIR=@0@'.format(meson.current_source_dir()),
'G_TEST_BUILDDIR=@0@'.format(meson.current_build_dir()),
'G_DEBUG=gc-friendly,fatal-warnings',
'GSETTINGS_BACKEND=memory',
'PYTHONDONTWRITEBYTECODE=yes',
'MALLOC_CHECK_=2',
'NO_AT_BRIDGE=1',
'GSETTINGS_SCHEMA_DIR=@0@/data'.format(meson.project_build_root()),
'CALLS_PLUGIN_DIR=@0@/plugins'.format(meson.project_build_root()),
]
test_cflags = [
'-DFOR_TESTING',
'-Wno-error=deprecated-declarations',
]
tests = [
['reject', ['test-keyfile-reject.c']],
]
foreach test : tests
name = test[0]
test_sources = []
foreach src : test[1]
test_sources += src
endforeach
t = executable(name, test_sources, keyfile_sources,
c_args: test_cflags,
link_args: test_link_args,
pie: true,
link_with: [calls_vala, libcalls],
dependencies: keyfile_deps,
include_directories: [
calls_includes,
keyfile_include,
]
)
test(name, t, env: test_env)
endforeach
endif

View file

@ -0,0 +1,68 @@
/*
* Copyright (C) 2022 Purism SPC
*
* SPDX-License-Identifier: GPL-3.0+
*/
#include "calls-keyfile-engine.h"
#include "calls-policy-engine.h"
#include <glib.h>
static GMainLoop *loop;
static void
on_decision_ready (GObject *source_object,
GAsyncResult *res,
gpointer user_data)
{
g_autoptr (GError) error = NULL;
CallsPolicyEngine *engine = CALLS_POLICY_ENGINE (source_object);
GVariant *decision = calls_policy_engine_decide_finish (engine, res, &error);
g_assert_no_error (error);
g_assert_cmpvariant (decision, user_data);
g_main_loop_quit (loop);
}
static void
test_reject (void)
{
CallsKeyfileEngine *kf_engine = calls_keyfile_engine_new ();
g_autoptr (GVariant) expect_reject = calls_policy_decision_new (CALLS_POLICY_DECISION_REJECT, NULL);
const char *data =
"[0123456789]\n"
"action=reject\n";
loop = g_main_loop_new (NULL, FALSE);
g_assert_true (calls_keyfile_engine_load_data (kf_engine, data, -1, NULL));
calls_policy_engine_decide (CALLS_POLICY_ENGINE (kf_engine),
"0123456789",
"tel",
NULL,
0,
on_decision_ready,
expect_reject);
g_main_loop_run (loop);
g_assert_finalize_object (kf_engine);
g_clear_pointer (&loop, g_main_loop_unref);
}
int
main (gint argc,
gchar *argv[])
{
g_test_init (&argc, &argv, NULL);
g_test_add_func ("/Calls/PolicyEngine/Keyfile/reject", test_reject);
return g_test_run ();
}

168
src/calls-extension.c Normal file
View file

@ -0,0 +1,168 @@
/*
* Copyright (C) 2023 Purism SPC
*
* This file is part of Calls.
*
* Calls 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 3 of the License, or
* (at your option) any later version.
*
* Calls 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.
*
* You should have received a copy of the GNU General Public License
* along with Calls. If not, see <http://www.gnu.org/licenses/>.
*
* Author: Evangelos Ribeiro Tzaras <devrtz@fortysixandtwo.eu>
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#include "calls-extension.h"
#include "enum-types.h"
/**
* SECTION:calls-extension
* @short_description: An extension for calls
* @Title: CallsExtension
*
* A base class for all plugin extensions.
*/
enum {
PROP_0,
PROP_TYPE,
PROP_PROVIDER,
PROP_LAST_PROP
};
static GParamSpec *props[PROP_LAST_PROP];
typedef struct {
GObject parent_instance;
CallsExtensionType type;
CallsProvider *provider;
} CallsExtensionPrivate;
struct _CallsExtensionClass {
GObjectClass parent_class;
};
G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE (CallsExtension, calls_extension, G_TYPE_OBJECT)
static void
calls_extension_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
CallsExtension *self = CALLS_EXTENSION (object);
CallsExtensionPrivate *priv = calls_extension_get_instance_private (self);
switch (prop_id) {
case PROP_TYPE:
priv->type = g_value_get_enum (value);
break;
case PROP_PROVIDER:
priv->provider = g_value_dup_object (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}
}
static void
calls_extension_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
CallsExtension *self = CALLS_EXTENSION (object);
switch (prop_id) {
case PROP_TYPE:
g_value_set_enum (value, calls_extension_get_extension_type (self));
break;
case PROP_PROVIDER:
g_value_set_object (value, calls_extension_get_provider (self));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}
}
static void
calls_extension_init (CallsExtension *self)
{
}
static void
calls_extension_class_init (CallsExtensionClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->set_property = calls_extension_set_property;
object_class->get_property = calls_extension_get_property;
/**
* CallsExtension:type:
*
* The #CallsExtensionType implemented by this plugin
*/
props[PROP_TYPE] =
g_param_spec_enum ("type",
"",
"",
CALLS_TYPE_EXTENSION_TYPE,
CALLS_EXTENSION_TYPE_UNKNOWN,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
G_PARAM_STATIC_STRINGS);
/**
* CallsExtension:provider:
*
* The #CallsProvider implemented by this plugin
*/
props[PROP_PROVIDER] =
g_param_spec_object ("provider",
"",
"",
CALLS_TYPE_PROVIDER,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
G_PARAM_STATIC_STRINGS);
}
CallsProvider *
calls_extension_get_provider (CallsExtension *self)
{
CallsExtensionPrivate *priv = calls_extension_get_instance_private (self);
g_return_val_if_fail (CALLS_IS_EXTENSION (self), NULL);
g_return_val_if_fail (priv->type == CALLS_EXTENSION_TYPE_PROVIDER, NULL);
return priv->provider;
}
CallsExtensionType
calls_extension_get_extension_type (CallsExtension *self)
{
CallsExtensionPrivate *priv = calls_extension_get_instance_private (self);
g_return_val_if_fail (CALLS_IS_EXTENSION (self), CALLS_EXTENSION_TYPE_UNKNOWN);
return priv->type;
}

47
src/calls-extension.h Normal file
View file

@ -0,0 +1,47 @@
/*
* Copyright (C) 2023 Purism SPC
*
* This file is part of Calls.
*
* Calls 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 3 of the License, or
* (at your option) any later version.
*
* Calls 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.
*
* You should have received a copy of the GNU General Public License
* along with Calls. If not, see <http://www.gnu.org/licenses/>.
*
* Author: Evangelos Ribeiro Tzaras <devrtz@fortysixandtwo.eu>
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#include "calls-provider.h"
#include <glib-object.h>
G_BEGIN_DECLS
/**
* CallsPluginType:
* @CALLS_EXTENSION_TYPE_UNKNOWN: Unknown type of extension
* @CALLS_EXTENSION_TYPE_PROVIDER: Provider extension
*/
typedef enum {
CALLS_EXTENSION_TYPE_UNKNOWN,
CALLS_EXTENSION_TYPE_PROVIDER,
} CallsExtensionType;
#define CALLS_TYPE_EXTENSION (calls_extension_get_type ())
G_DECLARE_DERIVABLE_TYPE (CallsExtension, calls_extension, CALLS, EXTENSION, GObject)
CallsExtensionType calls_extension_get_extension_type (CallsExtension *self);
CallsProvider *calls_extension_get_provider (CallsExtension *self);
G_END_DECLS

268
src/calls-policy-engine.c Normal file
View file

@ -0,0 +1,268 @@
/*
* Copyright (C) 2023 Purism SPC
*
* This file is part of Calls.
*
* Calls 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 3 of the License, or
* (at your option) any later version.
*
* Calls 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.
*
* You should have received a copy of the GNU General Public License
* along with Calls. If not, see <http://www.gnu.org/licenses/>.
*
* Author: Evangelos Ribeiro Tzaras <devrtz@fortysixandtwo.eu>
*
* SPDX-License-Identifier: GPL-3.0-or-later
*
*
*/
#define G_LOG_DOMAIN "CallsPolicyEngine"
#include "calls-policy-engine.h"
#include "calls-util.h"
#include "enum-types.h"
/**
* SECTION:calls-policy-engine
* @short_description: Policy engines allow taking predefined actions for incoming calls
* @Title: CallsPolicyEngine
*
* The #CallsPolicyEngine interface provides an asynchronous decide() method
* which returns a #CALLS_POLICY_DECISION
*
*/
G_DEFINE_INTERFACE (CallsPolicyEngine, calls_policy_engine, G_TYPE_OBJECT)
static void
calls_policy_engine_default_init (CallsPolicyEngineInterface *iface)
{
}
typedef struct {
GCancellable *cancel;
guint timeout_id;
} PolicyTaskData;
static void
policy_task_data_free (PolicyTaskData *data)
{
g_object_unref (data->cancel);
g_clear_handle_id (&data->timeout_id, g_source_remove);
g_free (data);
}
#if !GLIB_CHECK_VERSION (2, 74,0)
static gboolean
on_cancel_task (PolicyTaskData *data)
{
g_cancellable_cancel (data->cancel);
return G_SOURCE_REMOVE;
}
#endif
/**
* calls_policy_decision_is_valid:
* @decision: A #GVariant of type @CALLS_POLICY_VARIANT_FORMAT
* @error: (optional) (out): The return location of a #GError
*
* Checks if @decision is of the correct type and depending
* on the type of decision if the dictionary inside @decision contains
* required keys.
*
* Returns: Returns %TRUE if @decision is valid, %FALSE otherwise.
*/
gboolean
calls_policy_decision_is_valid (GVariant *decision,
GError **error)
{
g_autoptr (GVariant) var_dict = NULL;
GVariantDict dict;
CallsPolicyDecision decision_type;
gboolean valid = FALSE;
if (!decision || g_strcmp0 (g_variant_get_type_string (decision), CALLS_POLICY_DECISION_FORMAT)) {
g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, "Not a valid decision");
return FALSE;
}
g_variant_get (decision, CALLS_POLICY_DECISION_FORMAT_CONSUME_DICT, &decision_type, &var_dict);
g_variant_dict_init (&dict, var_dict);
switch (decision_type) {
case CALLS_POLICY_DECISION_INTERNAL_ERROR:
case CALLS_POLICY_DECISION_PASS:
case CALLS_POLICY_DECISION_SILENCE:
case CALLS_POLICY_DECISION_REJECT:
case CALLS_POLICY_DECISION_FORCE_ALLOW:
valid = TRUE;
break;
case CALLS_POLICY_DECISION_SMS:
if (g_variant_dict_contains (&dict, "sms-text"))
valid = TRUE;
else
g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT,
"Dictionary must contain 'sms-text' key");
break;
case CALLS_POLICY_DECISION_REDIRECT:
if (g_variant_dict_contains (&dict, "redirect-to"))
valid = TRUE;
else
g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT,
"Dictionary must contain 'redirect-to' key");
break;
default:
g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT,
"Unknown decision type %d", decision_type);
break;
}
g_variant_dict_clear (&dict);
return valid;
}
/**
* calls_policy_engine_decide_finish:
* @self: A #CallsPolicyEngine
* @res: A #GAsyncResult
* @error: (out) (optional): return location for a #GError
*
* Get the result of the policy decision.
*
* Returns: (nullable) (transfer full): A #GVariant of type @CALLS_POLICY_VARIANT_FORMAT
* where the first argument is the #CallsPolicyDecision and the second argument
* a dictionary containing additional hints, or %NULL if there was an error.
*
* The returned value is never floating. You should free it with
* g_variant_unref() when you're done with it.
*/
GVariant *
calls_policy_engine_decide_finish (CallsPolicyEngine *self,
GAsyncResult *res,
GError **error)
{
GTask *task;
GVariant *decision;
g_return_val_if_fail (g_task_is_valid (res, self),
g_variant_new (CALLS_POLICY_DECISION_FORMAT,
CALLS_POLICY_DECISION_INTERNAL_ERROR,
NULL));
task = G_TASK (res);
decision = g_task_propagate_pointer (task, error);
g_object_unref (task);
if (!calls_policy_decision_is_valid (decision, error)) {
g_variant_unref (decision);
return g_variant_new (CALLS_POLICY_DECISION_FORMAT,
CALLS_POLICY_DECISION_INTERNAL_ERROR,
NULL);
}
return decision;
}
/**
* calls_policy_engine_decide:
* @self: A #CallsPolicyEngine
* @id: The id of a call to check
* @protocol: The protocol used for this call
* @origin_id: The id of the origin which received the call
* @timeout_msec: How long to wait for a decision before timing out. Set to 0 to avoid timing out.
* @callback: The #GAsyncReadyCallback to call once a decision has been reached
* @user_data: The user data for the @callback
*
* Ask the policy engine to come to a policy decision.
*/
void
calls_policy_engine_decide (CallsPolicyEngine *self,
const char *id,
const char *protocol,
const char *origin_id,
guint timeout_msec,
GAsyncReadyCallback callback,
gpointer user_data)
{
CallsPolicyEngineInterface *iface = CALLS_POLICY_ENGINE_GET_IFACE (self);
GTask *task;
PolicyTaskData *task_data;
g_return_if_fail (CALLS_IS_POLICY_ENGINE (self));
g_return_if_fail (!STR_IS_NULL_OR_EMPTY (id));
g_return_if_fail (iface->decide != NULL);
task_data = g_new0 (PolicyTaskData, 1);
task_data->cancel = g_cancellable_new ();
if (timeout_msec > 0) {
#if GLIB_CHECK_VERSION (2, 74,0)
/** This raises a deprecation warning (on 2.74.6 ?!):
* g_timeout_add_once is deprecated: Not available before 2.74 [-Wdeprecated-declarations]
*/
G_GNUC_BEGIN_IGNORE_DEPRECATIONS
task_data->timeout_id =
g_timeout_add_once (timeout_msec, (GSourceOnceFunc) g_cancellable_cancel, task_data->cancel);
G_GNUC_END_IGNORE_DEPRECATIONS
#else
task_data->timeout_id = g_timeout_add (timeout_msec, G_SOURCE_FUNC (on_cancel_task), task_data);
#endif
}
task = g_task_new (self, task_data->cancel, callback, user_data);
g_task_set_task_data (task, task_data, (GDestroyNotify) policy_task_data_free);
iface->decide (self,
id,
protocol,
origin_id,
task);
}
/**
* calls_policy_decision_new:
* @decision: A #CallsPolicyDecision
* @params: (optional): A #GVariant dictionary of parameters
*
* Returns: (nullable) (transfer full): A helper function to build the GVariant
* as expected to be returned by implementations of #CallsPolicyEngine.
* The GVariant is of type @CALLS_POLICY_VARIANT_FORMAT with the first
* argument being @decision optionally followed by a parameter dictionary
* as given by @params.
*
* Assumes ownership of @params, if given.
*
* Returns %NULL if @decision is not a member of the #CallsPolicyDecision enum.
*/
GVariant *
calls_policy_decision_new (CallsPolicyDecision decision,
GVariant *params)
{
g_autoptr (GEnumClass) enum_class = g_type_class_ref (CALLS_TYPE_POLICY_DECISION);
if (!g_enum_get_value (enum_class, decision))
return NULL;
if (params)
return g_variant_new (CALLS_POLICY_DECISION_FORMAT_CONSUME_DICT, decision, params);
else
return g_variant_new (CALLS_POLICY_DECISION_FORMAT, decision, NULL);
}

91
src/calls-policy-engine.h Normal file
View file

@ -0,0 +1,91 @@
/*
* Copyright (C) 2023 Purism SPC
*
* This file is part of Calls.
*
* Calls 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 3 of the License, or
* (at your option) any later version.
*
* Calls 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.
*
* You should have received a copy of the GNU General Public License
* along with Calls. If not, see <http://www.gnu.org/licenses/>.
*
* Author: Evangelos Ribeiro Tzaras <devrtz@fortysixandtwo.eu>
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#pragma once
#include "enum-types.h"
#include <gio/gio.h>
#include <glib-object.h>
G_BEGIN_DECLS
#define CALLS_POLICY_DECISION_FORMAT "(ia{sv})"
#define CALLS_POLICY_DECISION_FORMAT_CONSUME_DICT "(i@a{sv})"
/**
* CallsPolicyDecision:
* all unknown values should be treated as @CALLS_POLICY_DECISION_PASS
* @CALLS_POLICY_DECISION_INTERNAL_ERROR: An error occurred
* @CALLS_POLICY_DECISION_PASS: Allow call
* @CALLS_POLICY_DECISION_SMS: Send a predefined SMS (not implemented)
* @CALLS_POLICY_DECISION_SILENCE: Set ringing to silent
* @CALLS_POLICY_DECISION_REJECT: Reject (hang up) the call
* @CALLS_POLICY_DECISION_IGNORE: Ignore the call (don't hang up)
* @CALLS_POLICY_DECISION_REDIRECT: Redirect to a different number
* @CALLS_POLICY_DECISION_REJECT: Reject the call
* @CALLS_POLICY_DECISION_FORCE_ALLOW: Allow
*/
typedef enum {
CALLS_POLICY_DECISION_INTERNAL_ERROR = -1,
CALLS_POLICY_DECISION_PASS = 0,
CALLS_POLICY_DECISION_SMS,
CALLS_POLICY_DECISION_SILENCE,
CALLS_POLICY_DECISION_REJECT = 16,
CALLS_POLICY_DECISION_IGNORE = 32,
CALLS_POLICY_DECISION_REDIRECT = 48,
CALLS_POLICY_DECISION_FORCE_ALLOW = 128,
} CallsPolicyDecision;
#define CALLS_TYPE_POLICY_ENGINE (calls_policy_engine_get_type ())
G_DECLARE_INTERFACE (CallsPolicyEngine, calls_policy_engine, CALLS, POLICY_ENGINE, GObject)
struct _CallsPolicyEngineInterface {
GTypeInterface parent_iface;
void (*decide) (CallsPolicyEngine *self,
const char *id,
const char *protocol,
const char *origin_id,
GTask *task);
};
GVariant *calls_policy_engine_decide_finish (CallsPolicyEngine *self,
GAsyncResult *res,
GError **error);
void calls_policy_engine_decide (CallsPolicyEngine *self,
const char *id,
const char *protocol,
const char *origin_id,
guint timeout_msec,
GAsyncReadyCallback callback,
gpointer user_data);
gboolean calls_policy_decision_is_valid (GVariant *decision,
GError **error);
GVariant *calls_policy_decision_new (CallsPolicyDecision decision,
GVariant *params);
G_END_DECLS

225
src/calls-policy-manager.c Normal file
View file

@ -0,0 +1,225 @@
/*
* Copyright (C) 2023 Purism SPC
*
* This file is part of Calls.
*
* Calls 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 3 of the License, or
* (at your option) any later version.
*
* Calls 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.
*
* You should have received a copy of the GNU General Public License
* along with Calls. If not, see <http://www.gnu.org/licenses/>.
*
* Author: Evangelos Ribeiro Tzaras <devrtz@fortysixandtwo.eu>
*
* SPDX-License-Identifier: GPL-3.0-or-later
*
*
*/
#define G_LOG_DOMAIN "CallsPolicyManager"
#include "calls-policy-engine.h"
#include "calls-policy-manager.h"
/**
* SECTION:calls-policy-manager
* @short_description: Manages call policy backends
* @Title: CallsPolicyManager
*
* Queries managed policy engines for their individual #CallsPolicyDecision
* and forms a final decision.
*/
enum {
PROP_0,
PROP_ENGINES,
PROP_LAST_PROP
};
static GParamSpec *props[PROP_LAST_PROP];
struct _CallsPolicyManager {
GObject parent_instance;
GListModel *engines;
GCancellable *cancel;
};
G_DEFINE_TYPE (CallsPolicyManager, calls_policy_manager, G_TYPE_OBJECT)
typedef struct {
char *id;
GVariant *decision;
guint n_decisions;
GAsyncReadyCallback callback;
gpointer user_data;
} DecisionAggregationData;
static void
aggregate_decision (DecisionAggregationData *data,
GVariant *new_decision)
{
g_autoptr (GError) error = NULL;
g_assert (data);
data->n_decisions++;
if (!calls_policy_decision_is_valid (new_decision, &error)) {
g_warning ("Invalid policy decision: %s", error->message);
if (new_decision)
g_variant_unref (new_decision);
return;
}
if (!data->decision) {
data->decision = new_decision;
} else {
CallsPolicyDecision new_decision_type;
CallsPolicyDecision old_decision_type;
g_variant_get (new_decision, CALLS_POLICY_DECISION_FORMAT, &new_decision_type, NULL);
g_variant_get (data->decision, CALLS_POLICY_DECISION_FORMAT, &old_decision_type, NULL);
if (new_decision_type > old_decision_type) {
g_variant_unref (data->decision);
data->decision = new_decision;
} else {
g_variant_unref (new_decision);
}
}
}
static void
on_decision_ready (GObject *object,
GAsyncResult *res,
gpointer user_data)
{
;
}
static void
calls_policy_manager_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
CallsPolicyManager *self = CALLS_POLICY_MANAGER (object);
switch (prop_id) {
case PROP_ENGINES:
self->engines = g_value_dup_object (value);
g_assert (g_list_model_get_item_type (self->engines) == CALLS_TYPE_POLICY_ENGINE);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}
}
static void
calls_policy_manager_dispose (GObject *object)
{
CallsPolicyManager *self = CALLS_POLICY_MANAGER (object);
g_clear_object (&self->engines);
g_clear_object (&self->cancel);
G_OBJECT_CLASS (calls_policy_manager_parent_class)->dispose (object);
}
static void
calls_policy_manager_class_init (CallsPolicyManagerClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->set_property = calls_policy_manager_set_property;
object_class->dispose = calls_policy_manager_dispose;
/**
* CallsPolicyManager:engines:
*
* A #GListModel of [type@CALLS_POLICY_ENGINE]s which get queried
* by @calls_policy_manager_decide
*/
props[PROP_ENGINES] =
g_param_spec_object ("engines",
"",
"",
G_TYPE_LIST_MODEL,
G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY);
}
static void
calls_policy_manager_init (CallsPolicyManager *self)
{
self->cancel = g_cancellable_new ();
}
CallsPolicyManager *
calls_policy_manager_new (void)
{
return g_object_new (CALLS_TYPE_POLICY_MANAGER, NULL);
}
void
calls_policy_manager_decide (CallsPolicyManager *self,
const char *id,
const char *protocol,
const char *origin_id,
guint timeout_msec,
GAsyncReadyCallback callback,
gpointer user_data)
{
GTask *task;
DecisionAggregationData *data;
guint n_engines;
g_return_if_fail (CALLS_IS_POLICY_MANAGER (self));
n_engines = g_list_model_get_n_items (self->engines);
if (n_engines == 0) {
GVariant *decision = calls_policy_decision_new (CALLS_POLICY_DECISION_PASS, NULL);
task = g_task_new (self, self->cancel, callback, user_data);
g_task_return_pointer (task, decision, (GDestroyNotify) g_variant_unref);
return;
}
data = g_new0 (DecisionAggregationData, 1);
data->id = g_strdup (id);
data->callback = callback;
data->user_data = user_data;
for (guint i = 0; i < n_engines; i++) {
g_autoptr (CallsPolicyEngine) engine = g_list_model_get_item (self->engines, i);
calls_policy_engine_decide (engine, id, protocol, origin_id, timeout_msec, on_decision_ready, data);
}
}
GVariant *
calls_policy_manager_decide_finish (CallsPolicyManager *self,
GAsyncResult *res,
GError **error)
{
return NULL;
}

View file

@ -0,0 +1,47 @@
/*
* Copyright (C) 2023 Purism SPC
*
* This file is part of Calls.
*
* Calls 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 3 of the License, or
* (at your option) any later version.
*
* Calls 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.
*
* You should have received a copy of the GNU General Public License
* along with Calls. If not, see <http://www.gnu.org/licenses/>.
*
* Author: Evangelos Ribeiro Tzaras <devrtz@fortysixandtwo.eu>
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#pragma once
#include <gio/gio.h>
#include <glib-object.h>
G_BEGIN_DECLS
#define CALLS_TYPE_POLICY_MANAGER calls_policy_manager_get_type ()
G_DECLARE_FINAL_TYPE (CallsPolicyManager, calls_policy_manager, CALLS, POLICY_MANAGER, GObject);
CallsPolicyManager *calls_policy_manager_new (void);
void calls_policy_manager_decide (CallsPolicyManager *self,
const char *id,
const char *protocol,
const char *origin_id,
guint timeout_msec,
GAsyncReadyCallback callback,
gpointer user_data);
GVariant *calls_policy_manager_decide_finish (CallsPolicyManager *self,
GAsyncResult *res,
GError **error);
G_END_DECLS

View file

@ -66,8 +66,10 @@ calls_enum_headers = files(
'calls-account.h',
'calls-call.h',
'calls-manager.h',
'calls-policy-engine.h',
'calls-ringer.h',
'calls-ussd.h',
'calls-extension.h',
]
)
calls_enum_sources = gnome.mkenums_simple('enum-types',
@ -104,6 +106,7 @@ calls_sources = files([
'calls-dbus-manager.c', 'calls-dbus-manager.h',
'calls-emergency-calls-manager.c', 'calls-emergency-calls-manager.h',
'calls-emergency-call-types.c', 'calls-emergency-call-types.h',
'calls-extension.c', 'calls-extension.h',
'calls-history-box.c', 'calls-history-box.h',
'calls-in-app-notification.c', 'calls-in-app-notification.h',
'calls-log.c', 'calls-log.h',
@ -116,6 +119,8 @@ calls_sources = files([
'calls-origin.c', 'calls-origin.h',
'calls-plugin.c', 'calls-plugin.h',
'calls-plugin-manager.c', 'calls-plugin-manager.h',
'calls-policy-engine.c', 'calls-policy-engine.h',
'calls-policy-manager.c', 'calls-policy-manager.h',
'calls-provider.c', 'calls-provider.h',
'calls-record-store.c', 'calls-record-store.h',
'calls-ringer.c', 'calls-ringer.h',