tests: test call DTMF functionality
Signed-off-by: Dan Williams <dan@ioncontrol.co>
This commit is contained in:
+58
-14
@@ -315,18 +315,11 @@ daemon_enums_types_dep = declare_dependency(
|
||||
include_directories: '.',
|
||||
)
|
||||
|
||||
# Additional vendor plugins
|
||||
subdir('plugins')
|
||||
|
||||
# ModemManager daemon
|
||||
sources = files(
|
||||
'main.c',
|
||||
'mm-auth-provider.c',
|
||||
base_sources = files(
|
||||
'mm-base-bearer.c',
|
||||
'mm-base-call.c',
|
||||
'mm-call-at.c',
|
||||
'mm-base-cbm.c',
|
||||
'mm-base-manager.c',
|
||||
'mm-base-modem-at.c',
|
||||
'mm-base-modem.c',
|
||||
'mm-base-sim.c',
|
||||
@@ -341,7 +334,6 @@ sources = files(
|
||||
'mm-dispatcher-connection.c',
|
||||
'mm-dispatcher-fcc-unlock.c',
|
||||
'mm-dispatcher-modem-setup.c',
|
||||
'mm-filter.c',
|
||||
'mm-iface-modem-3gpp.c',
|
||||
'mm-iface-modem-3gpp-profile-manager.c',
|
||||
'mm-iface-modem-3gpp-ussd.c',
|
||||
@@ -359,15 +351,68 @@ sources = files(
|
||||
'mm-iface-modem-voice.c',
|
||||
'mm-iface-op-lock.c',
|
||||
'mm-log-helpers.c',
|
||||
'mm-plugin.c',
|
||||
'mm-plugin-manager.c',
|
||||
'mm-port-probe.c',
|
||||
'mm-port-probe-at.c',
|
||||
'mm-private-boxed-types.c',
|
||||
'mm-sleep-context.c',
|
||||
)
|
||||
|
||||
# MM base library (used by MM and tests)
|
||||
incs = [
|
||||
top_inc,
|
||||
kerneldevice_inc,
|
||||
]
|
||||
|
||||
deps = [
|
||||
libmm_glib_dep,
|
||||
libhelpers_dep,
|
||||
libauth_dep,
|
||||
libport_dep,
|
||||
libqcdm_dep,
|
||||
daemon_enums_types_dep,
|
||||
]
|
||||
|
||||
private_deps = []
|
||||
|
||||
c_args = [
|
||||
'-DMM_COMPILATION',
|
||||
'-DPLUGINDIR="@0@"'.format(mm_prefix / mm_pkglibdir),
|
||||
'-DMODEMSETUPDIRPACKAGE="@0@"'.format(mm_prefix / mm_pkglibdir / 'modem-setup.d'),
|
||||
'-DMODEMSETUPDIRUSER="@0@"'.format(mm_prefix / mm_pkgsysconfdir / 'modem-setup.d'),
|
||||
'-DFCCUNLOCKDIRPACKAGE="@0@"'.format(mm_prefix / mm_pkglibdir / 'fcc-unlock.d'),
|
||||
'-DFCCUNLOCKDIRUSER="@0@"'.format(mm_prefix / mm_pkgsysconfdir / 'fcc-unlock.d'),
|
||||
'-DCONNECTIONDIRPACKAGE="@0@"'.format(mm_prefix / mm_pkglibdir / 'connection.d'),
|
||||
'-DCONNECTIONDIRUSER="@0@"'.format(mm_prefix / mm_pkgsysconfdir / 'connection.d'),
|
||||
]
|
||||
|
||||
libmmbase = static_library(
|
||||
'mmbase',
|
||||
sources: base_sources + daemon_enums_sources,
|
||||
include_directories: incs,
|
||||
dependencies: deps,
|
||||
c_args: c_args,
|
||||
)
|
||||
|
||||
libmmbase_dep = declare_dependency(
|
||||
include_directories: ['.', kerneldevice_inc],
|
||||
dependencies: deps,
|
||||
link_with: libmmbase,
|
||||
)
|
||||
|
||||
# Additional vendor plugins
|
||||
subdir('plugins')
|
||||
|
||||
# ModemManager daemon
|
||||
sources = files(
|
||||
'main.c',
|
||||
'mm-base-manager.c',
|
||||
'mm-filter.c',
|
||||
'mm-plugin.c',
|
||||
'mm-plugin-manager.c',
|
||||
'mm-port-probe.c',
|
||||
'mm-port-probe-at.c',
|
||||
)
|
||||
|
||||
sources += daemon_enums_sources
|
||||
sources += base_sources
|
||||
|
||||
deps = [
|
||||
gmodule_dep,
|
||||
@@ -383,7 +428,6 @@ endif
|
||||
|
||||
c_args = [
|
||||
'-DMM_COMPILATION',
|
||||
'-DPLUGINDIR="@0@"'.format(mm_prefix / mm_pkglibdir),
|
||||
'-DMODEMSETUPDIRPACKAGE="@0@"'.format(mm_prefix / mm_pkglibdir / 'modem-setup.d'),
|
||||
'-DMODEMSETUPDIRUSER="@0@"'.format(mm_prefix / mm_pkgsysconfdir / 'modem-setup.d'),
|
||||
'-DFCCUNLOCKDIRPACKAGE="@0@"'.format(mm_prefix / mm_pkglibdir / 'fcc-unlock.d'),
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
/* -*- 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) 2025 Dan Williams <dan@ioncontrol.co>
|
||||
*/
|
||||
|
||||
#ifndef MM_TEST_UTILS_H
|
||||
#define MM_TEST_UTILS_H
|
||||
|
||||
#include <glib.h>
|
||||
#include <glib-object.h>
|
||||
|
||||
/*****************************************************************************/
|
||||
|
||||
/* cmp = TRUE (s1 contains s2) or FALSE (s1 does not contain s2) */
|
||||
#define mm_assert_strstr(s1, cmp, s2) \
|
||||
G_STMT_START { \
|
||||
const char *__s1 = (s1), *__s2 = (s2); \
|
||||
if (strstr (__s1, __s2) == cmp) ; else \
|
||||
g_assertion_message_cmpstr (G_LOG_DOMAIN, __FILE__, __LINE__, G_STRFUNC, \
|
||||
#s1 " %s " #s2, \
|
||||
__s1, \
|
||||
cmp ? "contains" : "does not contain", \
|
||||
__s2); \
|
||||
} G_STMT_END
|
||||
|
||||
/* Asserts that err's message contains s1 */
|
||||
#define mm_assert_error_str(err, s1) \
|
||||
G_STMT_START { \
|
||||
if (!err || !err->message || !strstr (err->message, s1)) { \
|
||||
GString *gstring; \
|
||||
gstring = g_string_new ("assertion failed "); \
|
||||
if (err) \
|
||||
g_string_append_printf (gstring, "%s (%s, %d) did not contain '%s'", \
|
||||
err->message, g_quark_to_string (err->domain), err->code, s1); \
|
||||
else \
|
||||
g_string_append_printf (gstring, "%s is NULL", #err); \
|
||||
g_assertion_message (G_LOG_DOMAIN, __FILE__, __LINE__, G_STRFUNC, gstring->str); \
|
||||
g_string_free (gstring, TRUE); \
|
||||
} \
|
||||
} G_STMT_END
|
||||
|
||||
#endif /* MM_TEST_UTILS_H */
|
||||
@@ -0,0 +1,409 @@
|
||||
/* -*- 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) 2025 Dan Williams <dan@ioncontrol.co>
|
||||
*/
|
||||
|
||||
#include <glib.h>
|
||||
#include <glib-object.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <locale.h>
|
||||
|
||||
#define _LIBMM_INSIDE_MM
|
||||
#include <libmm-glib.h>
|
||||
|
||||
#include "fake-call.h"
|
||||
|
||||
G_DEFINE_TYPE (MMFakeCall, mm_fake_call, MM_TYPE_BASE_CALL)
|
||||
|
||||
/*****************************************************************************/
|
||||
/* Start the CALL */
|
||||
|
||||
static gboolean
|
||||
call_start_finish (MMBaseCall *self,
|
||||
GAsyncResult *res,
|
||||
GError **error)
|
||||
{
|
||||
return g_task_propagate_boolean (G_TASK (res), error);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
call_start_ready (GTask *task)
|
||||
{
|
||||
MMFakeCall *self;
|
||||
|
||||
self = g_task_get_source_object (task);
|
||||
|
||||
self->priv->idle_id = 0;
|
||||
|
||||
if (self->priv->start_error_msg) {
|
||||
g_task_return_new_error (task,
|
||||
MM_CORE_ERROR,
|
||||
MM_CORE_ERROR_FAILED,
|
||||
"%s",
|
||||
self->priv->start_error_msg);
|
||||
} else
|
||||
g_task_return_boolean (task, TRUE);
|
||||
|
||||
g_object_unref (task);
|
||||
return G_SOURCE_REMOVE;
|
||||
}
|
||||
|
||||
static void
|
||||
call_start (MMBaseCall *_self,
|
||||
GCancellable *cancellable,
|
||||
GAsyncReadyCallback callback,
|
||||
gpointer user_data)
|
||||
{
|
||||
MMFakeCall *self = MM_FAKE_CALL (_self);
|
||||
GTask *task;
|
||||
|
||||
g_assert_cmpint (self->priv->idle_id, ==, 0);
|
||||
|
||||
task = g_task_new (self, NULL, callback, user_data);
|
||||
self->priv->idle_id = g_idle_add ((GSourceFunc) call_start_ready, task);
|
||||
}
|
||||
|
||||
/*****************************************************************************/
|
||||
/* Accept the call */
|
||||
|
||||
static gboolean
|
||||
call_accept_finish (MMBaseCall *self,
|
||||
GAsyncResult *res,
|
||||
GError **error)
|
||||
{
|
||||
return g_task_propagate_boolean (G_TASK (res), error);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
call_accept_ready (GTask *task)
|
||||
{
|
||||
MMFakeCall *self;
|
||||
|
||||
self = g_task_get_source_object (task);
|
||||
|
||||
self->priv->idle_id = 0;
|
||||
|
||||
if (self->priv->accept_error_msg) {
|
||||
g_task_return_new_error (task,
|
||||
MM_CORE_ERROR,
|
||||
MM_CORE_ERROR_FAILED,
|
||||
"%s",
|
||||
self->priv->accept_error_msg);
|
||||
} else
|
||||
g_task_return_boolean (task, TRUE);
|
||||
|
||||
g_object_unref (task);
|
||||
return G_SOURCE_REMOVE;
|
||||
}
|
||||
|
||||
static void
|
||||
call_accept (MMBaseCall *_self,
|
||||
GAsyncReadyCallback callback,
|
||||
gpointer user_data)
|
||||
{
|
||||
MMFakeCall *self = MM_FAKE_CALL (_self);
|
||||
GTask *task;
|
||||
|
||||
g_assert_cmpint (self->priv->idle_id, ==, 0);
|
||||
|
||||
task = g_task_new (self, NULL, callback, user_data);
|
||||
self->priv->idle_id = g_idle_add ((GSourceFunc) call_accept_ready, task);
|
||||
}
|
||||
|
||||
/*****************************************************************************/
|
||||
/* Deflect the call */
|
||||
|
||||
static gboolean
|
||||
call_deflect_finish (MMBaseCall *self,
|
||||
GAsyncResult *res,
|
||||
GError **error)
|
||||
{
|
||||
return g_task_propagate_boolean (G_TASK (res), error);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
call_deflect_ready (GTask *task)
|
||||
{
|
||||
MMFakeCall *self;
|
||||
|
||||
self = g_task_get_source_object (task);
|
||||
|
||||
self->priv->idle_id = 0;
|
||||
|
||||
if (self->priv->deflect_error_msg) {
|
||||
g_task_return_new_error (task,
|
||||
MM_CORE_ERROR,
|
||||
MM_CORE_ERROR_FAILED,
|
||||
"%s",
|
||||
self->priv->deflect_error_msg);
|
||||
} else
|
||||
g_task_return_boolean (task, TRUE);
|
||||
|
||||
g_object_unref (task);
|
||||
return G_SOURCE_REMOVE;
|
||||
}
|
||||
|
||||
static void
|
||||
call_deflect (MMBaseCall *_self,
|
||||
const gchar *number,
|
||||
GAsyncReadyCallback callback,
|
||||
gpointer user_data)
|
||||
{
|
||||
MMFakeCall *self = MM_FAKE_CALL (_self);
|
||||
GTask *task;
|
||||
|
||||
g_assert_cmpint (self->priv->idle_id, ==, 0);
|
||||
|
||||
task = g_task_new (self, NULL, callback, user_data);
|
||||
self->priv->idle_id = g_idle_add ((GSourceFunc) call_deflect_ready, task);
|
||||
}
|
||||
|
||||
/*****************************************************************************/
|
||||
/* Hangup the call */
|
||||
|
||||
static gboolean
|
||||
call_hangup_finish (MMBaseCall *self,
|
||||
GAsyncResult *res,
|
||||
GError **error)
|
||||
{
|
||||
return g_task_propagate_boolean (G_TASK (res), error);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
call_hangup_ready (GTask *task)
|
||||
{
|
||||
MMFakeCall *self;
|
||||
|
||||
self = g_task_get_source_object (task);
|
||||
|
||||
self->priv->idle_id = 0;
|
||||
|
||||
if (self->priv->hangup_error_msg) {
|
||||
g_task_return_new_error (task,
|
||||
MM_CORE_ERROR,
|
||||
MM_CORE_ERROR_FAILED,
|
||||
"%s",
|
||||
self->priv->hangup_error_msg);
|
||||
} else
|
||||
g_task_return_boolean (task, TRUE);
|
||||
|
||||
g_object_unref (task);
|
||||
return G_SOURCE_REMOVE;
|
||||
}
|
||||
|
||||
static void
|
||||
call_hangup (MMBaseCall *_self,
|
||||
GAsyncReadyCallback callback,
|
||||
gpointer user_data)
|
||||
{
|
||||
MMFakeCall *self = MM_FAKE_CALL (_self);
|
||||
GTask *task;
|
||||
|
||||
g_assert_cmpint (self->priv->idle_id, ==, 0);
|
||||
|
||||
task = g_task_new (self, NULL, callback, user_data);
|
||||
self->priv->idle_id = g_idle_add ((GSourceFunc) call_hangup_ready, task);
|
||||
}
|
||||
|
||||
/*****************************************************************************/
|
||||
/* Send DTMF tone to call */
|
||||
|
||||
static gssize
|
||||
call_send_dtmf_finish (MMBaseCall *self,
|
||||
GAsyncResult *res,
|
||||
GError **error)
|
||||
{
|
||||
return g_task_propagate_int (G_TASK (res), error);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
call_send_dtmf_ready (GTask *task)
|
||||
{
|
||||
MMFakeCall *self;
|
||||
|
||||
self = g_task_get_source_object (task);
|
||||
|
||||
self->priv->idle_id = 0;
|
||||
|
||||
if (self->priv->dtmf_error_msg) {
|
||||
g_task_return_new_error (task,
|
||||
MM_CORE_ERROR,
|
||||
MM_CORE_ERROR_FAILED,
|
||||
"%s",
|
||||
self->priv->dtmf_error_msg);
|
||||
} else
|
||||
g_task_return_int (task, self->priv->dtmf_num_accepted);
|
||||
|
||||
self->priv->dtmf_in_send = FALSE;
|
||||
g_object_unref (task);
|
||||
return G_SOURCE_REMOVE;
|
||||
}
|
||||
|
||||
static void
|
||||
call_send_dtmf (MMBaseCall *_self,
|
||||
const gchar *dtmf,
|
||||
GAsyncReadyCallback callback,
|
||||
gpointer user_data)
|
||||
{
|
||||
MMFakeCall *self = MM_FAKE_CALL (_self);
|
||||
GTask *task;
|
||||
|
||||
g_assert_cmpint (self->priv->idle_id, ==, 0);
|
||||
|
||||
task = g_task_new (self, NULL, callback, user_data);
|
||||
|
||||
if (!self->priv->dtmf_sent)
|
||||
self->priv->dtmf_sent = g_string_new ("");
|
||||
self->priv->dtmf_num_accepted = MIN (self->priv->dtmf_accept_len, strlen (dtmf));
|
||||
g_string_append_len (self->priv->dtmf_sent, dtmf, self->priv->dtmf_num_accepted);
|
||||
|
||||
self->priv->idle_id = g_idle_add ((GSourceFunc) call_send_dtmf_ready, task);
|
||||
self->priv->dtmf_in_send = TRUE;
|
||||
}
|
||||
|
||||
/*****************************************************************************/
|
||||
/* Stop DTMF tone */
|
||||
|
||||
static gboolean
|
||||
call_stop_dtmf_finish (MMBaseCall *self,
|
||||
GAsyncResult *res,
|
||||
GError **error)
|
||||
{
|
||||
return g_task_propagate_boolean (G_TASK (res), error);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
call_stop_dtmf_ready (GTask *task)
|
||||
{
|
||||
MMFakeCall *self;
|
||||
|
||||
self = g_task_get_source_object (task);
|
||||
|
||||
self->priv->idle_id = 0;
|
||||
self->priv->dtmf_stop_called = TRUE;
|
||||
|
||||
if (self->priv->dtmf_stop_error_msg) {
|
||||
g_task_return_new_error (task,
|
||||
MM_CORE_ERROR,
|
||||
MM_CORE_ERROR_FAILED,
|
||||
"%s",
|
||||
self->priv->dtmf_stop_error_msg);
|
||||
} else
|
||||
g_task_return_boolean (task, TRUE);
|
||||
|
||||
g_object_unref (task);
|
||||
return G_SOURCE_REMOVE;
|
||||
}
|
||||
|
||||
static void
|
||||
call_stop_dtmf (MMBaseCall *_self,
|
||||
GAsyncReadyCallback callback,
|
||||
gpointer user_data)
|
||||
{
|
||||
MMFakeCall *self = MM_FAKE_CALL (_self);
|
||||
GTask *task;
|
||||
|
||||
g_assert_cmpint (self->priv->idle_id, ==, 0);
|
||||
g_assert_false (self->priv->dtmf_in_send);
|
||||
|
||||
task = g_task_new (self, NULL, callback, user_data);
|
||||
|
||||
self->priv->idle_id = g_idle_add ((GSourceFunc) call_stop_dtmf_ready, task);
|
||||
}
|
||||
|
||||
void
|
||||
mm_fake_call_enable_dtmf_stop (MMFakeCall *self,
|
||||
gboolean enable)
|
||||
{
|
||||
MMBaseCallClass *base_call_class = MM_BASE_CALL_GET_CLASS (self);
|
||||
|
||||
base_call_class->stop_dtmf = enable ? call_stop_dtmf : NULL;
|
||||
base_call_class->stop_dtmf_finish = enable ? call_stop_dtmf_finish : NULL;
|
||||
}
|
||||
|
||||
/*****************************************************************************/
|
||||
|
||||
MMFakeCall *
|
||||
mm_fake_call_new (GDBusConnection *connection,
|
||||
MMIfaceModemVoice *voice,
|
||||
MMCallDirection direction,
|
||||
const gchar *number,
|
||||
const guint dtmf_tone_duration)
|
||||
{
|
||||
MMFakeCall *call;
|
||||
|
||||
call = MM_FAKE_CALL (g_object_new (MM_TYPE_FAKE_CALL,
|
||||
MM_BASE_CALL_CONNECTION, connection,
|
||||
MM_BASE_CALL_IFACE_MODEM_VOICE, voice,
|
||||
MM_CALL_DIRECTION, direction,
|
||||
MM_CALL_NUMBER, number,
|
||||
MM_CALL_DTMF_TONE_DURATION, dtmf_tone_duration,
|
||||
MM_BASE_CALL_SKIP_INCOMING_TIMEOUT, TRUE,
|
||||
MM_BASE_CALL_SUPPORTS_DIALING_TO_RINGING, TRUE,
|
||||
MM_BASE_CALL_SUPPORTS_RINGING_TO_ACTIVE, TRUE,
|
||||
NULL));
|
||||
return call;
|
||||
}
|
||||
|
||||
static void
|
||||
mm_fake_call_init (MMFakeCall *self)
|
||||
{
|
||||
self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, MM_TYPE_FAKE_CALL, MMFakeCallPrivate);
|
||||
}
|
||||
|
||||
static void
|
||||
dispose (GObject *object)
|
||||
{
|
||||
MMFakeCall *self = MM_FAKE_CALL (object);
|
||||
|
||||
if (self->priv->idle_id)
|
||||
g_source_remove (self->priv->idle_id);
|
||||
self->priv->idle_id = 0;
|
||||
|
||||
if (self->priv->dtmf_sent) {
|
||||
g_string_free (self->priv->dtmf_sent, TRUE);
|
||||
self->priv->dtmf_sent = NULL;
|
||||
}
|
||||
|
||||
G_OBJECT_CLASS (mm_fake_call_parent_class)->dispose (object);
|
||||
}
|
||||
|
||||
static void
|
||||
finalize (GObject *object)
|
||||
{
|
||||
G_OBJECT_CLASS (mm_fake_call_parent_class)->finalize (object);
|
||||
}
|
||||
|
||||
static void
|
||||
mm_fake_call_class_init (MMFakeCallClass *klass)
|
||||
{
|
||||
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
||||
MMBaseCallClass *base_call_class = MM_BASE_CALL_CLASS (klass);
|
||||
|
||||
g_type_class_add_private (object_class, sizeof (MMFakeCallPrivate));
|
||||
|
||||
object_class->dispose = dispose;
|
||||
object_class->finalize = finalize;
|
||||
|
||||
base_call_class->start = call_start;
|
||||
base_call_class->start_finish = call_start_finish;
|
||||
base_call_class->accept = call_accept;
|
||||
base_call_class->accept_finish = call_accept_finish;
|
||||
base_call_class->deflect = call_deflect;
|
||||
base_call_class->deflect_finish = call_deflect_finish;
|
||||
base_call_class->hangup = call_hangup;
|
||||
base_call_class->hangup_finish = call_hangup_finish;
|
||||
base_call_class->send_dtmf = call_send_dtmf;
|
||||
base_call_class->send_dtmf_finish = call_send_dtmf_finish;
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
/* -*- 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) 2025 Dan Williams <dan@ioncontrol.co>
|
||||
*/
|
||||
|
||||
#ifndef MM_FAKE_CALL_H
|
||||
#define MM_FAKE_CALL_H
|
||||
|
||||
#include <glib.h>
|
||||
#include <glib-object.h>
|
||||
|
||||
#include "mm-base-call.h"
|
||||
#include "mm-iface-modem-voice.h"
|
||||
|
||||
#define MM_TYPE_FAKE_CALL (mm_fake_call_get_type ())
|
||||
#define MM_FAKE_CALL(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_FAKE_CALL, MMFakeCall))
|
||||
#define MM_FAKE_CALL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_FAKE_CALL, MMFakeCallClass))
|
||||
#define MM_IS_FAKE_CALL(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_FAKE_CALL))
|
||||
#define MM_IS_FAKE_CALL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_FAKE_CALL))
|
||||
#define MM_FAKE_CALL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_FAKE_CALL, MMFakeCallClass))
|
||||
|
||||
typedef struct _MMFakeCall MMFakeCall;
|
||||
typedef struct _MMFakeCallClass MMFakeCallClass;
|
||||
typedef struct _MMFakeCallPrivate MMFakeCallPrivate;
|
||||
|
||||
struct _MMFakeCallPrivate {
|
||||
const gchar *start_error_msg;
|
||||
const gchar *accept_error_msg;
|
||||
const gchar *deflect_error_msg;
|
||||
const gchar *hangup_error_msg;
|
||||
|
||||
/* DTMF */
|
||||
const gchar *dtmf_error_msg;
|
||||
const gchar *dtmf_stop_error_msg;
|
||||
/* How many DTMF characters we can accept at a time */
|
||||
guint dtmf_accept_len;
|
||||
/* How many DTMF characters were actually accepted */
|
||||
guint dtmf_num_accepted;
|
||||
GString *dtmf_sent;
|
||||
gboolean dtmf_in_send;
|
||||
gboolean dtmf_stop_called;
|
||||
|
||||
guint idle_id;
|
||||
};
|
||||
|
||||
struct _MMFakeCall {
|
||||
MMBaseCall parent;
|
||||
MMFakeCallPrivate *priv;
|
||||
};
|
||||
|
||||
struct _MMFakeCallClass {
|
||||
MMBaseCallClass parent;
|
||||
};
|
||||
|
||||
GType mm_fake_call_get_type (void);
|
||||
G_DEFINE_AUTOPTR_CLEANUP_FUNC (MMFakeCall, g_object_unref)
|
||||
|
||||
MMFakeCall *mm_fake_call_new (GDBusConnection *connection,
|
||||
MMIfaceModemVoice *voice,
|
||||
MMCallDirection direction,
|
||||
const gchar *number,
|
||||
const guint dtmf_tone_duration);
|
||||
|
||||
void mm_fake_call_enable_dtmf_stop (MMFakeCall *self,
|
||||
gboolean enable);
|
||||
|
||||
#endif /* MM_FAKE_CALL_H */
|
||||
@@ -0,0 +1,463 @@
|
||||
/* -*- 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) 2025 Dan Williams <dan@ioncontrol.co>
|
||||
*/
|
||||
|
||||
#include <glib.h>
|
||||
#include <glib-object.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <locale.h>
|
||||
|
||||
#define _LIBMM_INSIDE_MM
|
||||
#include <libmm-glib.h>
|
||||
|
||||
#include "fake-modem.h"
|
||||
#include "mm-iface-modem.h"
|
||||
#include "mm-iface-modem-voice.h"
|
||||
#include "mm-call-list.h"
|
||||
#include "fake-call.h"
|
||||
#include "mm-bind.h"
|
||||
|
||||
#define MM_FAKE_MODEM_PATH "fake-modem-path"
|
||||
|
||||
static void iface_modem_init (MMIfaceModemInterface *iface);
|
||||
static void iface_modem_voice_init (MMIfaceModemVoiceInterface *iface);
|
||||
|
||||
G_DEFINE_TYPE_EXTENDED (MMFakeModem, mm_fake_modem, MM_TYPE_BASE_MODEM, 0,
|
||||
G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM, iface_modem_init)
|
||||
G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_VOICE, iface_modem_voice_init))
|
||||
|
||||
enum {
|
||||
PROP_0,
|
||||
PROP_CONNECTION,
|
||||
PROP_PATH,
|
||||
PROP_MODEM_DBUS_SKELETON,
|
||||
PROP_MODEM_STATE,
|
||||
PROP_MODEM_SIM,
|
||||
PROP_MODEM_SIM_SLOTS,
|
||||
PROP_MODEM_SIM_HOT_SWAP_SUPPORTED,
|
||||
PROP_MODEM_PERIODIC_SIGNAL_CHECK_DISABLED,
|
||||
PROP_MODEM_PERIODIC_ACCESS_TECH_CHECK_DISABLED,
|
||||
PROP_MODEM_PERIODIC_CALL_LIST_CHECK_DISABLED,
|
||||
PROP_MODEM_CARRIER_CONFIG_MAPPING,
|
||||
PROP_MODEM_BEARER_LIST,
|
||||
PROP_MODEM_INDICATION_CALL_LIST_RELOAD_ENABLED,
|
||||
PROP_MODEM_VOICE_DBUS_SKELETON,
|
||||
PROP_MODEM_VOICE_CALL_LIST,
|
||||
PROP_LAST
|
||||
};
|
||||
|
||||
static GParamSpec *properties[PROP_LAST];
|
||||
|
||||
struct _MMFakeModemPrivate {
|
||||
GDBusConnection *connection;
|
||||
guint dbus_id;
|
||||
gchar *path;
|
||||
|
||||
GObject *modem_dbus_skeleton;
|
||||
MMModemState modem_state;
|
||||
GObject *modem_sim;
|
||||
GPtrArray *modem_sim_slots;
|
||||
gboolean sim_hot_swap_supported;
|
||||
gboolean periodic_signal_check_disabled;
|
||||
gboolean periodic_access_tech_check_disabled;
|
||||
gboolean periodic_call_list_check_disabled;
|
||||
gchar *carrier_config_mapping;
|
||||
MMBearerList *modem_bearer_list;
|
||||
gboolean indication_call_list_reload_enabled;
|
||||
GObject *modem_voice_dbus_skeleton;
|
||||
MMCallList *modem_voice_call_list;
|
||||
};
|
||||
|
||||
/*****************************************************************************/
|
||||
|
||||
const gchar *
|
||||
mm_fake_modem_get_path (MMFakeModem *self)
|
||||
{
|
||||
return self->priv->path;
|
||||
}
|
||||
|
||||
MMCallList *
|
||||
mm_fake_modem_get_call_list (MMFakeModem *self)
|
||||
{
|
||||
return self->priv->modem_voice_call_list;
|
||||
}
|
||||
|
||||
gboolean
|
||||
mm_fake_modem_export_interfaces (MMFakeModem *self, GError **error)
|
||||
{
|
||||
g_assert (self->priv->path);
|
||||
g_assert (self->priv->connection);
|
||||
|
||||
if (self->priv->modem_dbus_skeleton) {
|
||||
if (!g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (self->priv->modem_dbus_skeleton),
|
||||
self->priv->connection,
|
||||
self->priv->path,
|
||||
error))
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (self->priv->modem_voice_dbus_skeleton) {
|
||||
if (!g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (self->priv->modem_voice_dbus_skeleton),
|
||||
self->priv->connection,
|
||||
self->priv->path,
|
||||
error))
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/*****************************************************************************/
|
||||
|
||||
static gboolean
|
||||
modem_voice_check_support_finish (MMIfaceModemVoice *self,
|
||||
GAsyncResult *res,
|
||||
GError **error)
|
||||
{
|
||||
gboolean foobar;
|
||||
|
||||
foobar = g_task_propagate_boolean (G_TASK (res), error);
|
||||
return foobar;
|
||||
}
|
||||
|
||||
static void
|
||||
modem_voice_check_support (MMIfaceModemVoice *self,
|
||||
GAsyncReadyCallback callback,
|
||||
gpointer user_data)
|
||||
{
|
||||
GTask *task;
|
||||
|
||||
task = g_task_new (self, NULL, callback, user_data);
|
||||
g_task_return_boolean (task, TRUE);
|
||||
g_object_unref (task);
|
||||
}
|
||||
|
||||
/*****************************************************************************/
|
||||
|
||||
static MMBaseCall *
|
||||
modem_voice_create_call (MMIfaceModemVoice *_self,
|
||||
MMCallDirection direction,
|
||||
const gchar *number,
|
||||
const guint dtmf_tone_duration)
|
||||
{
|
||||
MMFakeModem *self = MM_FAKE_MODEM (_self);
|
||||
|
||||
return MM_BASE_CALL (mm_fake_call_new (self->priv->connection,
|
||||
_self,
|
||||
direction,
|
||||
number,
|
||||
dtmf_tone_duration));
|
||||
}
|
||||
|
||||
/*****************************************************************************/
|
||||
|
||||
MMFakeModem *
|
||||
mm_fake_modem_new (GDBusConnection *connection)
|
||||
{
|
||||
return MM_FAKE_MODEM (g_object_new (MM_TYPE_FAKE_MODEM,
|
||||
MM_BINDABLE_CONNECTION, connection,
|
||||
NULL));
|
||||
}
|
||||
|
||||
static void
|
||||
set_property (GObject *object,
|
||||
guint prop_id,
|
||||
const GValue *value,
|
||||
GParamSpec *pspec)
|
||||
{
|
||||
MMFakeModem *self = MM_FAKE_MODEM (object);
|
||||
|
||||
switch (prop_id) {
|
||||
case PROP_PATH:
|
||||
g_free (self->priv->path);
|
||||
self->priv->path = g_value_dup_string (value);
|
||||
break;
|
||||
case PROP_CONNECTION:
|
||||
g_clear_object (&self->priv->connection);
|
||||
self->priv->connection = g_value_dup_object (value);
|
||||
break;
|
||||
case PROP_MODEM_DBUS_SKELETON:
|
||||
g_clear_object (&self->priv->modem_dbus_skeleton);
|
||||
self->priv->modem_dbus_skeleton = g_value_dup_object (value);
|
||||
break;
|
||||
case PROP_MODEM_STATE:
|
||||
self->priv->modem_state = g_value_get_enum (value);
|
||||
break;
|
||||
case PROP_MODEM_SIM:
|
||||
g_clear_object (&self->priv->modem_sim);
|
||||
self->priv->modem_sim = g_value_dup_object (value);
|
||||
break;
|
||||
case PROP_MODEM_SIM_SLOTS:
|
||||
g_clear_pointer (&self->priv->modem_sim_slots, g_ptr_array_unref);
|
||||
self->priv->modem_sim_slots = g_value_dup_boxed (value);
|
||||
break;
|
||||
case PROP_MODEM_SIM_HOT_SWAP_SUPPORTED:
|
||||
self->priv->sim_hot_swap_supported = g_value_get_boolean (value);
|
||||
break;
|
||||
case PROP_MODEM_PERIODIC_SIGNAL_CHECK_DISABLED:
|
||||
self->priv->periodic_signal_check_disabled = g_value_get_boolean (value);
|
||||
break;
|
||||
case PROP_MODEM_PERIODIC_ACCESS_TECH_CHECK_DISABLED:
|
||||
self->priv->periodic_access_tech_check_disabled = g_value_get_boolean (value);
|
||||
break;
|
||||
case PROP_MODEM_PERIODIC_CALL_LIST_CHECK_DISABLED:
|
||||
self->priv->periodic_call_list_check_disabled = g_value_get_boolean (value);
|
||||
break;
|
||||
case PROP_MODEM_CARRIER_CONFIG_MAPPING:
|
||||
self->priv->carrier_config_mapping = g_value_dup_string (value);
|
||||
break;
|
||||
case PROP_MODEM_BEARER_LIST:
|
||||
g_clear_object (&self->priv->modem_bearer_list);
|
||||
self->priv->modem_bearer_list = g_value_dup_object (value);
|
||||
break;
|
||||
case PROP_MODEM_INDICATION_CALL_LIST_RELOAD_ENABLED:
|
||||
self->priv->indication_call_list_reload_enabled = g_value_get_boolean (value);
|
||||
break;
|
||||
case PROP_MODEM_VOICE_DBUS_SKELETON:
|
||||
g_clear_object (&self->priv->modem_voice_dbus_skeleton);
|
||||
self->priv->modem_voice_dbus_skeleton = g_value_dup_object (value);
|
||||
break;
|
||||
case PROP_MODEM_VOICE_CALL_LIST:
|
||||
g_clear_object (&self->priv->modem_voice_call_list);
|
||||
self->priv->modem_voice_call_list = g_value_dup_object (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)
|
||||
{
|
||||
MMFakeModem *self = MM_FAKE_MODEM (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_DBUS_SKELETON:
|
||||
g_value_set_object (value, self->priv->modem_dbus_skeleton);
|
||||
break;
|
||||
case PROP_MODEM_STATE:
|
||||
g_value_set_enum (value, self->priv->modem_state);
|
||||
break;
|
||||
case PROP_MODEM_SIM:
|
||||
g_value_set_object (value, self->priv->modem_sim);
|
||||
break;
|
||||
case PROP_MODEM_SIM_SLOTS:
|
||||
g_value_set_boxed (value, self->priv->modem_sim_slots);
|
||||
break;
|
||||
case PROP_MODEM_SIM_HOT_SWAP_SUPPORTED:
|
||||
g_value_set_boolean (value, self->priv->sim_hot_swap_supported);
|
||||
break;
|
||||
case PROP_MODEM_PERIODIC_SIGNAL_CHECK_DISABLED:
|
||||
g_value_set_boolean (value, self->priv->periodic_signal_check_disabled);
|
||||
break;
|
||||
case PROP_MODEM_PERIODIC_ACCESS_TECH_CHECK_DISABLED:
|
||||
g_value_set_boolean (value, self->priv->periodic_access_tech_check_disabled);
|
||||
break;
|
||||
case PROP_MODEM_PERIODIC_CALL_LIST_CHECK_DISABLED:
|
||||
g_value_set_boolean (value, self->priv->periodic_call_list_check_disabled);
|
||||
break;
|
||||
case PROP_MODEM_CARRIER_CONFIG_MAPPING:
|
||||
g_value_set_string (value, self->priv->carrier_config_mapping);
|
||||
break;
|
||||
case PROP_MODEM_BEARER_LIST:
|
||||
g_value_set_object (value, self->priv->modem_bearer_list);
|
||||
break;
|
||||
case PROP_MODEM_INDICATION_CALL_LIST_RELOAD_ENABLED:
|
||||
g_value_set_boolean (value, self->priv->indication_call_list_reload_enabled);
|
||||
break;
|
||||
case PROP_MODEM_VOICE_DBUS_SKELETON:
|
||||
g_value_set_object (value, self->priv->modem_voice_dbus_skeleton);
|
||||
break;
|
||||
case PROP_MODEM_VOICE_CALL_LIST:
|
||||
g_value_set_object (value, self->priv->modem_voice_call_list);
|
||||
break;
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
mm_fake_modem_init (MMFakeModem *self)
|
||||
{
|
||||
static guint id = 0;
|
||||
|
||||
/* Initialize private data */
|
||||
self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
|
||||
MM_TYPE_FAKE_MODEM,
|
||||
MMFakeModemPrivate);
|
||||
|
||||
/* Each modem is given a unique id to build its own DBus path */
|
||||
self->priv->dbus_id = id++;
|
||||
self->priv->path = g_strdup_printf (MM_DBUS_MODEM_PREFIX "/%d", self->priv->dbus_id);
|
||||
}
|
||||
|
||||
static void
|
||||
finalize (GObject *object)
|
||||
{
|
||||
MMFakeModem *self = MM_FAKE_MODEM (object);
|
||||
|
||||
g_free (self->priv->path);
|
||||
g_free (self->priv->carrier_config_mapping);
|
||||
|
||||
G_OBJECT_CLASS (mm_fake_modem_parent_class)->finalize (object);
|
||||
}
|
||||
|
||||
static void
|
||||
dispose (GObject *object)
|
||||
{
|
||||
MMFakeModem *self = MM_FAKE_MODEM (object);
|
||||
|
||||
if (self->priv->modem_dbus_skeleton) {
|
||||
mm_iface_modem_shutdown (MM_IFACE_MODEM (object));
|
||||
g_clear_object (&self->priv->modem_dbus_skeleton);
|
||||
}
|
||||
if (self->priv->modem_voice_dbus_skeleton) {
|
||||
mm_iface_modem_voice_shutdown (MM_IFACE_MODEM_VOICE (object));
|
||||
g_clear_object (&self->priv->modem_voice_dbus_skeleton);
|
||||
}
|
||||
g_clear_object (&self->priv->modem_sim);
|
||||
g_clear_pointer (&self->priv->modem_sim_slots, g_ptr_array_unref);
|
||||
g_clear_object (&self->priv->modem_bearer_list);
|
||||
g_clear_object (&self->priv->modem_voice_call_list);
|
||||
g_clear_object (&self->priv->connection);
|
||||
|
||||
G_OBJECT_CLASS (mm_fake_modem_parent_class)->dispose (object);
|
||||
}
|
||||
|
||||
static void
|
||||
iface_modem_init (MMIfaceModemInterface *iface)
|
||||
{
|
||||
}
|
||||
|
||||
static void
|
||||
iface_modem_voice_init (MMIfaceModemVoiceInterface *iface)
|
||||
{
|
||||
iface->check_support = modem_voice_check_support;
|
||||
iface->check_support_finish = modem_voice_check_support_finish;
|
||||
iface->create_call = modem_voice_create_call;
|
||||
|
||||
#if 0
|
||||
iface->load_call_list = modem_voice_load_call_list;
|
||||
iface->load_call_list_finish = modem_voice_load_call_list_finish;
|
||||
iface->hold_and_accept = modem_voice_hold_and_accept;
|
||||
iface->hold_and_accept_finish = modem_voice_hold_and_accept_finish;
|
||||
iface->hangup_and_accept = modem_voice_hangup_and_accept;
|
||||
iface->hangup_and_accept_finish = modem_voice_hangup_and_accept_finish;
|
||||
iface->hangup_all = modem_voice_hangup_all;
|
||||
iface->hangup_all_finish = modem_voice_hangup_all_finish;
|
||||
iface->join_multiparty = modem_voice_join_multiparty;
|
||||
iface->join_multiparty_finish = modem_voice_join_multiparty_finish;
|
||||
iface->leave_multiparty = modem_voice_leave_multiparty;
|
||||
iface->leave_multiparty_finish = modem_voice_leave_multiparty_finish;
|
||||
iface->transfer = modem_voice_transfer;
|
||||
iface->transfer_finish = modem_voice_transfer_finish;
|
||||
iface->call_waiting_setup = modem_voice_call_waiting_setup;
|
||||
iface->call_waiting_setup_finish = modem_voice_call_waiting_setup_finish;
|
||||
iface->call_waiting_query = modem_voice_call_waiting_query;
|
||||
iface->call_waiting_query_finish = modem_voice_call_waiting_query_finish;
|
||||
#endif
|
||||
}
|
||||
|
||||
static void
|
||||
mm_fake_modem_class_init (MMFakeModemClass *klass)
|
||||
{
|
||||
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
||||
|
||||
g_type_class_add_private (object_class, sizeof (MMFakeModemPrivate));
|
||||
|
||||
object_class->set_property = set_property;
|
||||
object_class->get_property = get_property;
|
||||
object_class->finalize = finalize;
|
||||
object_class->dispose = dispose;
|
||||
|
||||
properties[PROP_CONNECTION] =
|
||||
g_param_spec_object (MM_BINDABLE_CONNECTION,
|
||||
"Connection",
|
||||
"GDBus connection to the system bus.",
|
||||
G_TYPE_DBUS_CONNECTION,
|
||||
G_PARAM_READWRITE);
|
||||
g_object_class_install_property (object_class, PROP_CONNECTION, properties[PROP_CONNECTION]);
|
||||
|
||||
properties[PROP_PATH] =
|
||||
g_param_spec_string (MM_FAKE_MODEM_PATH,
|
||||
"Path",
|
||||
"DBus path of the call",
|
||||
NULL,
|
||||
G_PARAM_READWRITE);
|
||||
g_object_class_install_property (object_class, PROP_PATH, properties[PROP_PATH]);
|
||||
|
||||
g_object_class_override_property (object_class,
|
||||
PROP_MODEM_DBUS_SKELETON,
|
||||
MM_IFACE_MODEM_DBUS_SKELETON);
|
||||
|
||||
g_object_class_override_property (object_class,
|
||||
PROP_MODEM_STATE,
|
||||
MM_IFACE_MODEM_STATE);
|
||||
|
||||
g_object_class_override_property (object_class,
|
||||
PROP_MODEM_SIM,
|
||||
MM_IFACE_MODEM_SIM);
|
||||
|
||||
g_object_class_override_property (object_class,
|
||||
PROP_MODEM_SIM_SLOTS,
|
||||
MM_IFACE_MODEM_SIM_SLOTS);
|
||||
|
||||
g_object_class_override_property (object_class,
|
||||
PROP_MODEM_SIM_HOT_SWAP_SUPPORTED,
|
||||
MM_IFACE_MODEM_SIM_HOT_SWAP_SUPPORTED);
|
||||
|
||||
g_object_class_override_property (object_class,
|
||||
PROP_MODEM_PERIODIC_SIGNAL_CHECK_DISABLED,
|
||||
MM_IFACE_MODEM_PERIODIC_SIGNAL_CHECK_DISABLED);
|
||||
|
||||
g_object_class_override_property (object_class,
|
||||
PROP_MODEM_PERIODIC_ACCESS_TECH_CHECK_DISABLED,
|
||||
MM_IFACE_MODEM_PERIODIC_ACCESS_TECH_CHECK_DISABLED);
|
||||
|
||||
g_object_class_override_property (object_class,
|
||||
PROP_MODEM_PERIODIC_CALL_LIST_CHECK_DISABLED,
|
||||
MM_IFACE_MODEM_VOICE_PERIODIC_CALL_LIST_CHECK_DISABLED);
|
||||
|
||||
g_object_class_override_property (object_class,
|
||||
PROP_MODEM_CARRIER_CONFIG_MAPPING,
|
||||
MM_IFACE_MODEM_CARRIER_CONFIG_MAPPING);
|
||||
|
||||
g_object_class_override_property (object_class,
|
||||
PROP_MODEM_BEARER_LIST,
|
||||
MM_IFACE_MODEM_BEARER_LIST);
|
||||
|
||||
g_object_class_override_property (object_class,
|
||||
PROP_MODEM_INDICATION_CALL_LIST_RELOAD_ENABLED,
|
||||
MM_IFACE_MODEM_VOICE_INDICATION_CALL_LIST_RELOAD_ENABLED);
|
||||
|
||||
g_object_class_override_property (object_class,
|
||||
PROP_MODEM_VOICE_DBUS_SKELETON,
|
||||
MM_IFACE_MODEM_VOICE_DBUS_SKELETON);
|
||||
|
||||
g_object_class_override_property (object_class,
|
||||
PROP_MODEM_VOICE_CALL_LIST,
|
||||
MM_IFACE_MODEM_VOICE_CALL_LIST);
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
/* -*- 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) 2025 Dan Williams <dan@ioncontrol.co>
|
||||
*/
|
||||
|
||||
#ifndef MM_FAKE_MODEM_H
|
||||
#define MM_FAKE_MODEM_H
|
||||
|
||||
#include <glib.h>
|
||||
#include <glib-object.h>
|
||||
|
||||
#include "mm-base-modem.h"
|
||||
#include "mm-call-list.h"
|
||||
|
||||
#define MM_TYPE_FAKE_MODEM (mm_fake_modem_get_type ())
|
||||
#define MM_FAKE_MODEM(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_FAKE_MODEM, MMFakeModem))
|
||||
#define MM_FAKE_MODEM_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_FAKE_MODEM, MMFakeModemClass))
|
||||
#define MM_IS_FAKE_MODEM(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_FAKE_MODEM))
|
||||
#define MM_IS_FAKE_MODEM_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_FAKE_MODEM))
|
||||
#define MM_FAKE_MODEM_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_FAKE_MODEM, MMFakeModemClass))
|
||||
|
||||
typedef struct _MMFakeModem MMFakeModem;
|
||||
typedef struct _MMFakeModemClass MMFakeModemClass;
|
||||
typedef struct _MMFakeModemPrivate MMFakeModemPrivate;
|
||||
|
||||
struct _MMFakeModem {
|
||||
MMBaseModem parent;
|
||||
MMFakeModemPrivate *priv;
|
||||
};
|
||||
|
||||
struct _MMFakeModemClass {
|
||||
MMBaseModemClass parent;
|
||||
};
|
||||
|
||||
GType mm_fake_modem_get_type (void);
|
||||
G_DEFINE_AUTOPTR_CLEANUP_FUNC (MMFakeModem, g_object_unref)
|
||||
|
||||
MMFakeModem *mm_fake_modem_new (GDBusConnection *connection);
|
||||
|
||||
const gchar *mm_fake_modem_get_path (MMFakeModem *self);
|
||||
|
||||
gboolean mm_fake_modem_export_interfaces (MMFakeModem *self,
|
||||
GError **error);
|
||||
|
||||
MMCallList *mm_fake_modem_get_call_list (MMFakeModem *self);
|
||||
|
||||
#endif /* MM_FAKE_MODEM_H */
|
||||
+18
-1
@@ -31,6 +31,11 @@ if enable_mbim
|
||||
test_units += {'modem-helpers-mbim': libkerneldevice_dep}
|
||||
endif
|
||||
|
||||
c_args = [
|
||||
'-DTEST_SERVICES="@0@"'.format(build_root / 'data/tests'),
|
||||
'-DTESTUDEVRULESDIR="@0@"'.format(src_dir),
|
||||
]
|
||||
|
||||
foreach test_unit, test_deps: test_units
|
||||
test_name = 'test-' + test_unit
|
||||
|
||||
@@ -39,12 +44,24 @@ foreach test_unit, test_deps: test_units
|
||||
sources: test_name + '.c',
|
||||
include_directories: top_inc,
|
||||
dependencies: test_deps,
|
||||
c_args: '-DTESTUDEVRULESDIR="@0@"'.format(src_dir)
|
||||
c_args: c_args,
|
||||
)
|
||||
|
||||
test(test_name, exe)
|
||||
endforeach
|
||||
|
||||
# base call test
|
||||
exe = executable(
|
||||
'test-base-call',
|
||||
sources: [ 'test-base-call.c', 'fake-modem.c', 'fake-call.c' ],
|
||||
include_directories: top_inc,
|
||||
dependencies: libmmbase_dep,
|
||||
c_args: c_args,
|
||||
)
|
||||
|
||||
test('test-base-call', exe)
|
||||
|
||||
|
||||
if get_option('fuzzer')
|
||||
fuzzer_tests = ['test-sms-part-3gpp-fuzzer',
|
||||
'test-sms-part-3gpp-tr-fuzzer',
|
||||
|
||||
@@ -0,0 +1,519 @@
|
||||
/* -*- 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) 2025 Dan Williams <dan@ioncontrol.co>
|
||||
*/
|
||||
|
||||
#include <glib.h>
|
||||
#include <glib-object.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <locale.h>
|
||||
|
||||
#define _LIBMM_INSIDE_MM
|
||||
#include <libmm-glib.h>
|
||||
|
||||
#include "mm-base-call.h"
|
||||
#include "mm-context.h"
|
||||
#include "mm-call-list.h"
|
||||
#include "mm-iface-modem-voice.h"
|
||||
#include "fake-modem.h"
|
||||
#include "fake-call.h"
|
||||
#include "mm-log.h"
|
||||
#include "mm-test-utils.h"
|
||||
|
||||
/****************************************************************/
|
||||
/* Make the linker happy */
|
||||
|
||||
#if defined WITH_QMI
|
||||
|
||||
typedef struct MMBroadbandModemQmi MMBroadbandModemQmi;
|
||||
GType mm_broadband_modem_qmi_get_type (void);
|
||||
MMPortQmi *mm_broadband_modem_qmi_peek_port_qmi (MMBroadbandModemQmi *self);
|
||||
|
||||
GType
|
||||
mm_broadband_modem_qmi_get_type (void)
|
||||
{
|
||||
return G_TYPE_INVALID;
|
||||
}
|
||||
|
||||
MMPortQmi *
|
||||
mm_broadband_modem_qmi_peek_port_qmi (MMBroadbandModemQmi *self)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
#endif /* WITH_QMI */
|
||||
|
||||
#if defined WITH_MBIM
|
||||
|
||||
typedef struct MMBroadbandModemMbim MMBroadbandModemMbim;
|
||||
GType mm_broadband_modem_mbim_get_type (void);
|
||||
MMPortMbim *mm_broadband_modem_mbim_peek_port_mbim (MMBroadbandModemMbim *self);
|
||||
|
||||
GType
|
||||
mm_broadband_modem_mbim_get_type (void)
|
||||
{
|
||||
return G_TYPE_INVALID;
|
||||
}
|
||||
|
||||
MMPortMbim *
|
||||
mm_broadband_modem_mbim_peek_port_mbim (MMBroadbandModemMbim *self)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
#endif /* WITH_MBIM */
|
||||
|
||||
/****************************************************************/
|
||||
|
||||
typedef struct {
|
||||
const gchar *desc;
|
||||
|
||||
const gchar *start_error_msg;
|
||||
const gchar *accept_error_msg;
|
||||
const gchar *deflect_error_msg;
|
||||
const gchar *hangup_error_msg;
|
||||
|
||||
const gchar *number;
|
||||
|
||||
/* DTMF */
|
||||
const gchar *dtmf_error_msg;
|
||||
const gchar *dtmf_stop_error_msg;
|
||||
const guint dtmf_accept_len; /* how many chars modem can accept at a time */
|
||||
const guint dtmf_tone_duration;
|
||||
const gchar *dtmf;
|
||||
const guint dtmf_min_duration;
|
||||
} Testcase;
|
||||
|
||||
typedef struct {
|
||||
GTestDBus *dbus;
|
||||
GDBusConnection *connection;
|
||||
GMainLoop *loop;
|
||||
guint name_id;
|
||||
|
||||
MmGdbusModemVoice *voice_proxy;
|
||||
MMFakeModem *modem;
|
||||
|
||||
MmGdbusCall *call_proxy;
|
||||
MMFakeCall *call;
|
||||
|
||||
GError *error;
|
||||
|
||||
const Testcase *tc;
|
||||
} TestFixture;
|
||||
|
||||
/****************************************************************/
|
||||
|
||||
static MMFakeCall *
|
||||
get_call (TestFixture *tf)
|
||||
{
|
||||
MMCallList *list;
|
||||
MMFakeCall *call;
|
||||
const gchar *call_path;
|
||||
|
||||
list = mm_fake_modem_get_call_list (tf->modem);
|
||||
g_assert (list);
|
||||
|
||||
g_assert (tf->call_proxy);
|
||||
call_path = g_dbus_proxy_get_object_path (G_DBUS_PROXY (tf->call_proxy));
|
||||
call = (MMFakeCall *) mm_call_list_get_call (list, call_path);
|
||||
g_assert (call);
|
||||
return call;
|
||||
}
|
||||
|
||||
/****************************************************************/
|
||||
|
||||
static void
|
||||
dtmf_send_ready (MmGdbusCall *call,
|
||||
GAsyncResult *res,
|
||||
TestFixture *tf)
|
||||
{
|
||||
if (!mm_gdbus_call_call_send_dtmf_finish (call, res, &tf->error))
|
||||
g_assert_true (tf->error);
|
||||
g_main_loop_quit (tf->loop);
|
||||
}
|
||||
|
||||
static void
|
||||
dtmf_call_start_ready (MmGdbusCall *call,
|
||||
GAsyncResult *res,
|
||||
TestFixture *tf)
|
||||
{
|
||||
gboolean success;
|
||||
g_autoptr(GError) error = NULL;
|
||||
const MMCallInfo cinfo = {
|
||||
.index = 1,
|
||||
.number = (gchar *) tf->tc->number,
|
||||
.direction = MM_CALL_DIRECTION_OUTGOING,
|
||||
.state = MM_CALL_STATE_ACTIVE,
|
||||
};
|
||||
|
||||
success = mm_gdbus_call_call_start_finish (call, res, &error);
|
||||
g_assert_no_error (error);
|
||||
g_assert_true (success);
|
||||
|
||||
/* Set the call active */
|
||||
mm_iface_modem_voice_report_call (MM_IFACE_MODEM_VOICE (tf->modem), &cinfo);
|
||||
g_main_loop_quit (tf->loop);
|
||||
}
|
||||
|
||||
static void
|
||||
dtmf_proxy_ready (gpointer unused,
|
||||
GAsyncResult *res,
|
||||
TestFixture *tf)
|
||||
{
|
||||
g_autoptr(GError) error = NULL;
|
||||
g_autoptr(MMCallList) list = NULL;
|
||||
|
||||
tf->call_proxy = mm_gdbus_call_proxy_new_for_bus_finish (res, &error);
|
||||
g_assert_no_error (error);
|
||||
g_assert (tf->call_proxy);
|
||||
g_main_loop_quit (tf->loop);
|
||||
}
|
||||
|
||||
static void
|
||||
dtmf_create_call_ready (MMIfaceModemVoice *self,
|
||||
GAsyncResult *res,
|
||||
TestFixture *tf)
|
||||
{
|
||||
g_autoptr(GError) error = NULL;
|
||||
g_autofree gchar *call_path = NULL;
|
||||
gboolean success;
|
||||
|
||||
success = mm_gdbus_modem_voice_call_create_call_finish (MM_GDBUS_MODEM_VOICE (self),
|
||||
&call_path,
|
||||
res,
|
||||
&error);
|
||||
g_assert_true (success);
|
||||
g_assert_no_error (error);
|
||||
|
||||
/* Create our call proxy */
|
||||
mm_gdbus_call_proxy_new_for_bus (G_BUS_TYPE_SESSION,
|
||||
G_DBUS_PROXY_FLAGS_NONE,
|
||||
"org.freedesktop.ModemManager1",
|
||||
call_path,
|
||||
NULL,
|
||||
(GAsyncReadyCallback) dtmf_proxy_ready,
|
||||
tf);
|
||||
}
|
||||
|
||||
static void
|
||||
test_dtmf (TestFixture *tf, const Testcase *tc, gboolean test_stop)
|
||||
{
|
||||
g_autoptr(GError) error = NULL;
|
||||
g_autoptr(GVariant) dictionary = NULL;
|
||||
g_autoptr(MMCallProperties) call_props = NULL;
|
||||
MMFakeCall *call;
|
||||
guint dtmf_len_no_pause = 0;
|
||||
const gchar *p;
|
||||
gint64 call_start_time;
|
||||
|
||||
tf->tc = tc;
|
||||
|
||||
call_props = mm_call_properties_new ();
|
||||
mm_call_properties_set_number (call_props, tf->tc->number);
|
||||
mm_call_properties_set_dtmf_tone_duration (call_props, tf->tc->dtmf_tone_duration);
|
||||
dictionary = mm_call_properties_get_dictionary (call_props);
|
||||
|
||||
/* Create the voice call we'll use for DTMF */
|
||||
mm_gdbus_modem_voice_call_create_call (tf->voice_proxy,
|
||||
dictionary,
|
||||
NULL,
|
||||
(GAsyncReadyCallback)dtmf_create_call_ready,
|
||||
tf);
|
||||
g_main_loop_run (tf->loop);
|
||||
|
||||
/* start the voice call */
|
||||
mm_gdbus_call_call_start (tf->call_proxy,
|
||||
NULL,
|
||||
(GAsyncReadyCallback) dtmf_call_start_ready,
|
||||
tf);
|
||||
g_main_loop_run (tf->loop);
|
||||
|
||||
/* Get the call and copy expectations into it */
|
||||
call = get_call (tf);
|
||||
call->priv->dtmf_accept_len = tf->tc->dtmf_accept_len;
|
||||
call->priv->start_error_msg = tf->tc->start_error_msg;
|
||||
call->priv->accept_error_msg = tf->tc->accept_error_msg;
|
||||
call->priv->deflect_error_msg = tf->tc->deflect_error_msg;
|
||||
call->priv->hangup_error_msg = tf->tc->hangup_error_msg;
|
||||
call->priv->dtmf_error_msg = tf->tc->dtmf_error_msg;
|
||||
call->priv->dtmf_stop_error_msg = tf->tc->dtmf_stop_error_msg;
|
||||
|
||||
mm_fake_call_enable_dtmf_stop (call, test_stop);
|
||||
|
||||
g_message ("####### about to run %s", tf->tc->desc);
|
||||
/* Run the test */
|
||||
call_start_time = g_get_real_time ();
|
||||
mm_gdbus_call_call_send_dtmf (tf->call_proxy,
|
||||
tf->tc->dtmf,
|
||||
NULL,
|
||||
(GAsyncReadyCallback) dtmf_send_ready,
|
||||
tf);
|
||||
g_main_loop_run (tf->loop);
|
||||
|
||||
/* Validate results */
|
||||
if (tf->tc->start_error_msg)
|
||||
mm_assert_error_str (tf->error, tf->tc->start_error_msg);
|
||||
else if (tf->tc->accept_error_msg)
|
||||
mm_assert_error_str (tf->error, tf->tc->accept_error_msg);
|
||||
else if (tf->tc->deflect_error_msg)
|
||||
mm_assert_error_str (tf->error, tf->tc->deflect_error_msg);
|
||||
else if (tf->tc->hangup_error_msg)
|
||||
mm_assert_error_str (tf->error, tf->tc->hangup_error_msg);
|
||||
else if (tf->tc->dtmf_error_msg)
|
||||
mm_assert_error_str (tf->error, tf->tc->dtmf_error_msg);
|
||||
else if (test_stop && tf->tc->dtmf_stop_error_msg)
|
||||
mm_assert_error_str (tf->error, tf->tc->dtmf_stop_error_msg);
|
||||
else {
|
||||
p = tf->tc->dtmf;
|
||||
while (*p++) {
|
||||
if (*p != MM_CALL_DTMF_PAUSE_CHAR)
|
||||
dtmf_len_no_pause++;
|
||||
}
|
||||
g_assert_cmpint (strlen (call->priv->dtmf_sent->str), ==, dtmf_len_no_pause);
|
||||
|
||||
if (tf->tc->dtmf_min_duration) {
|
||||
g_assert_cmpint (call_start_time + (tf->tc->dtmf_min_duration * G_USEC_PER_SEC), <=, g_get_real_time ());
|
||||
}
|
||||
}
|
||||
if (test_stop && !tf->tc->dtmf_error_msg)
|
||||
g_assert_true (call->priv->dtmf_stop_called);
|
||||
}
|
||||
|
||||
static void
|
||||
test_dtmf_nostop (TestFixture *tf, gconstpointer user_data)
|
||||
{
|
||||
test_dtmf (tf, (const Testcase *) user_data, FALSE);
|
||||
}
|
||||
|
||||
static void
|
||||
test_dtmf_need_stop (TestFixture *tf, gconstpointer user_data)
|
||||
{
|
||||
test_dtmf (tf, (const Testcase *) user_data, TRUE);
|
||||
}
|
||||
|
||||
/************************************************************/
|
||||
|
||||
static void
|
||||
on_bus_acquired (GDBusConnection *connection,
|
||||
const gchar *name,
|
||||
TestFixture *tf)
|
||||
{
|
||||
tf->connection = connection;
|
||||
}
|
||||
|
||||
static void
|
||||
name_acquired_cb (GDBusConnection *connection,
|
||||
const gchar *name,
|
||||
TestFixture *tf)
|
||||
{
|
||||
g_main_loop_quit (tf->loop);
|
||||
}
|
||||
|
||||
static void
|
||||
voice_init_ready (MMIfaceModemVoice *_self,
|
||||
GAsyncResult *result,
|
||||
TestFixture *tf)
|
||||
{
|
||||
g_autoptr(GError) error = NULL;
|
||||
gboolean success;
|
||||
|
||||
success = mm_iface_modem_voice_initialize_finish (_self, result, &error);
|
||||
g_assert_no_error (error);
|
||||
g_assert (success);
|
||||
g_main_loop_quit (tf->loop);
|
||||
}
|
||||
|
||||
static void
|
||||
voice_proxy_ready (gpointer unused,
|
||||
GAsyncResult *res,
|
||||
TestFixture *tf)
|
||||
{
|
||||
g_autoptr(GError) error = NULL;
|
||||
|
||||
tf->voice_proxy = mm_gdbus_modem_voice_proxy_new_for_bus_finish (res, &error);
|
||||
g_assert_no_error (error);
|
||||
g_assert (tf->voice_proxy);
|
||||
g_main_loop_quit (tf->loop);
|
||||
}
|
||||
|
||||
static void
|
||||
test_fixture_setup (TestFixture *tf, gconstpointer unused)
|
||||
{
|
||||
g_autoptr(GError) error = NULL;
|
||||
gboolean success;
|
||||
|
||||
success = mm_log_setup (mm_context_get_log_level (),
|
||||
mm_context_get_log_file (),
|
||||
mm_context_get_log_journal (),
|
||||
mm_context_get_log_timestamps (),
|
||||
mm_context_get_log_relative_timestamps (),
|
||||
mm_context_get_log_personal_info (),
|
||||
&error);
|
||||
g_assert_no_error (error);
|
||||
g_assert (success);
|
||||
|
||||
tf->loop = g_main_loop_new (NULL, FALSE);
|
||||
|
||||
/* Create the global dbus-daemon for this test suite */
|
||||
tf->dbus = g_test_dbus_new (G_TEST_DBUS_NONE);
|
||||
g_assert (tf->dbus);
|
||||
|
||||
/* Add the private directory with our in-tree service files,
|
||||
* TEST_SERVICES is defined by the build system to point
|
||||
* to the right directory. */
|
||||
g_test_dbus_add_service_dir (tf->dbus, TEST_SERVICES);
|
||||
|
||||
/* Start the private DBus daemon */
|
||||
g_test_dbus_up (tf->dbus);
|
||||
|
||||
/* Acquire name, don't allow replacement */
|
||||
tf->name_id = g_bus_own_name (G_BUS_TYPE_SESSION,
|
||||
MM_DBUS_SERVICE,
|
||||
G_BUS_NAME_OWNER_FLAGS_NONE,
|
||||
(GBusAcquiredCallback) on_bus_acquired,
|
||||
(GBusNameAcquiredCallback) name_acquired_cb,
|
||||
NULL,
|
||||
tf,
|
||||
NULL);
|
||||
/* Wait for name acquired */
|
||||
g_main_loop_run (tf->loop);
|
||||
|
||||
/* Create and export the server-side modem voice interface */
|
||||
g_assert (tf->connection);
|
||||
tf->modem = mm_fake_modem_new (tf->connection);
|
||||
mm_iface_modem_voice_initialize (MM_IFACE_MODEM_VOICE (tf->modem),
|
||||
NULL,
|
||||
(GAsyncReadyCallback) voice_init_ready,
|
||||
tf);
|
||||
g_main_loop_run (tf->loop);
|
||||
|
||||
if (!mm_fake_modem_export_interfaces (tf->modem, &error))
|
||||
g_assert_no_error (error);
|
||||
|
||||
/* Create client-side modem proxy */
|
||||
mm_gdbus_modem_voice_proxy_new_for_bus (G_BUS_TYPE_SESSION,
|
||||
G_DBUS_PROXY_FLAGS_NONE,
|
||||
"org.freedesktop.ModemManager1",
|
||||
mm_fake_modem_get_path (tf->modem),
|
||||
NULL,
|
||||
(GAsyncReadyCallback) voice_proxy_ready,
|
||||
tf);
|
||||
g_main_loop_run (tf->loop);
|
||||
}
|
||||
|
||||
static void
|
||||
test_fixture_cleanup (TestFixture *tf, gconstpointer unused)
|
||||
{
|
||||
g_bus_unown_name (tf->name_id);
|
||||
mm_iface_modem_voice_shutdown (MM_IFACE_MODEM_VOICE (tf->modem));
|
||||
|
||||
g_clear_error (&tf->error);
|
||||
|
||||
g_clear_object (&tf->call_proxy);
|
||||
g_clear_object (&tf->call);
|
||||
g_clear_object (&tf->voice_proxy);
|
||||
/* Run dispose to break a ref cycle in case there's still a FakeCall hanging around */
|
||||
g_object_run_dispose (G_OBJECT (tf->modem));
|
||||
g_clear_object (&tf->modem);
|
||||
g_dbus_connection_close_sync (tf->connection, NULL, NULL);
|
||||
g_test_dbus_stop (tf->dbus);
|
||||
g_test_dbus_down (tf->dbus);
|
||||
g_clear_object (&tf->dbus);
|
||||
g_main_loop_unref (tf->loop);
|
||||
mm_log_shutdown ();
|
||||
}
|
||||
|
||||
/****************************************************************/
|
||||
|
||||
static const Testcase tests[] = {
|
||||
{
|
||||
.desc = "/MM/Call/DTMF/send-one-accept-len",
|
||||
.number = "911",
|
||||
.dtmf_accept_len = 1,
|
||||
.dtmf_tone_duration = 300,
|
||||
.dtmf = "987654321",
|
||||
},
|
||||
{
|
||||
.desc = "/MM/Call/DTMF/send-single-tone",
|
||||
.number = "911",
|
||||
.dtmf_accept_len = 3,
|
||||
.dtmf_tone_duration = 300,
|
||||
.dtmf = "9",
|
||||
},
|
||||
{
|
||||
.desc = "/MM/Call/DTMF/send-multi-tone",
|
||||
.number = "911",
|
||||
.dtmf_accept_len = 3,
|
||||
.dtmf_tone_duration = 300,
|
||||
.dtmf = "123",
|
||||
},
|
||||
{
|
||||
.desc = "/MM/Call/DTMF/send-pause",
|
||||
.number = "911",
|
||||
.dtmf_accept_len = 10,
|
||||
.dtmf_tone_duration = 300,
|
||||
.dtmf = "123,,4",
|
||||
.dtmf_min_duration = 4,
|
||||
},
|
||||
/* Error testing */
|
||||
{
|
||||
.desc = "/MM/Call/DTMF/send-error",
|
||||
.number = "911",
|
||||
.dtmf_accept_len = 1,
|
||||
.dtmf = "123",
|
||||
.dtmf_error_msg = "send failure",
|
||||
},
|
||||
{
|
||||
.desc = "/MM/Call/DTMF/stop-error",
|
||||
.number = "911",
|
||||
.dtmf_accept_len = 1,
|
||||
.dtmf = "123",
|
||||
.dtmf_stop_error_msg = "stop failure",
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
|
||||
#define TCASE(n, d, f) \
|
||||
g_test_add (n, \
|
||||
TestFixture, \
|
||||
d, \
|
||||
test_fixture_setup, \
|
||||
f, \
|
||||
test_fixture_cleanup); \
|
||||
|
||||
#define TCASE_DTMF_STOP(n, d, f) \
|
||||
{ \
|
||||
g_autofree gchar *desc = g_strdup_printf ("%s-stop", n); \
|
||||
TCASE(desc, d, f); \
|
||||
}
|
||||
|
||||
int main (int argc, char **argv)
|
||||
{
|
||||
const gchar *test_args[] = { argv[0], "--test-session" };
|
||||
guint i;
|
||||
|
||||
setlocale (LC_ALL, "");
|
||||
|
||||
g_test_init (&argc, &argv, NULL);
|
||||
mm_context_init (G_N_ELEMENTS (test_args), (gchar **) test_args);
|
||||
|
||||
for (i = 0; i < G_N_ELEMENTS (tests); i++) {
|
||||
TCASE(tests[i].desc, &tests[i], test_dtmf_nostop);
|
||||
/* Test everything again for paired start/stop (eg QMI) */
|
||||
TCASE_DTMF_STOP(tests[i].desc, &tests[i], test_dtmf_need_stop);
|
||||
}
|
||||
|
||||
return g_test_run ();
|
||||
}
|
||||
Reference in New Issue
Block a user