diff --git a/plugins/sip/calls-sip-enums.c.in b/plugins/sip/calls-sip-enums.c.in new file mode 100644 index 0000000..a0f7b25 --- /dev/null +++ b/plugins/sip/calls-sip-enums.c.in @@ -0,0 +1,40 @@ +/*** BEGIN file-header ***/ + +#include "config.h" + +#include "calls-sip-util.h" + +#include "calls-sip-enums.h" + +/*** END file-header ***/ + +/*** BEGIN file-production ***/ +/* enumerations from "@filename@" */ +/*** END file-production ***/ + +/*** BEGIN value-header ***/ +GType +@enum_name@_get_type (void) +{ + static GType etype = 0; + if (G_UNLIKELY(etype == 0)) { + static const G@Type@Value values[] = { +/*** END value-header ***/ + +/*** BEGIN value-production ***/ + { @VALUENAME@, "@VALUENAME@", "@valuenick@" }, +/*** END value-production ***/ + +/*** BEGIN value-tail ***/ + { 0, NULL, NULL } + }; + etype = g_@type@_register_static (g_intern_static_string ("@EnumName@"), values); + } + return etype; +} + +/*** END value-tail ***/ + +/*** BEGIN file-tail ***/ + +/*** END file-tail ***/ diff --git a/plugins/sip/calls-sip-enums.h.in b/plugins/sip/calls-sip-enums.h.in new file mode 100644 index 0000000..2070d01 --- /dev/null +++ b/plugins/sip/calls-sip-enums.h.in @@ -0,0 +1,21 @@ +/*** BEGIN file-header ***/ +#pragma once + +#include + +G_BEGIN_DECLS +/*** END file-header ***/ + +/*** BEGIN file-production ***/ + +/* enumerations from "@basename@" */ +/*** END file-production ***/ + +/*** BEGIN value-header ***/ +GType @enum_name@_get_type (void); +#define @ENUMPREFIX@_TYPE_@ENUMSHORT@ (@enum_name@_get_type ()) +/*** END value-header ***/ + +/*** BEGIN file-tail ***/ +G_END_DECLS +/*** END file-tail ***/ diff --git a/plugins/sip/calls-sip-origin.c b/plugins/sip/calls-sip-origin.c index bfc44fd..e8c13c0 100644 --- a/plugins/sip/calls-sip-origin.c +++ b/plugins/sip/calls-sip-origin.c @@ -27,9 +27,11 @@ #include "calls-message-source.h" #include "calls-origin.h" #include "calls-sip-call.h" +#include "calls-sip-util.h" #include #include +#include struct _CallsSipOrigin @@ -37,12 +39,28 @@ struct _CallsSipOrigin GObject parent_instance; GString *name; + CallsSipContext *ctx; + nua_t *nua; + CallsSipHandles *oper; + /* Maybe it makes sense to have one call handle (nua_handle_t) in + * CallsSipCall (do we need a backpointer to CallsSipOrigin?) + * and define the HMAGIC as a CallsSipOrigin + */ + + /* Direct connection mode is useful for debugging purposes */ + gboolean use_direct_connection; + + /* Needed to handle shutdown correctly. See sip_callback and dispose method */ + gboolean is_nua_shutdown; + + SipAccountState state; + /* Account information */ gchar *user; gchar *password; gchar *host; + gint port; gchar *protocol; - gboolean use_direct_connection; GList *calls; }; @@ -62,13 +80,22 @@ enum { PROP_ACC_USER, PROP_ACC_PASSWORD, PROP_ACC_HOST, + PROP_ACC_PORT, PROP_ACC_PROTOCOL, PROP_ACC_DIRECT, + PROP_SIP_CONTEXT, PROP_CALLS, PROP_LAST_PROP, }; static GParamSpec *props[PROP_LAST_PROP]; + +static gboolean +init_sip_account (CallsSipOrigin *self) +{ + return FALSE; +} + static gboolean protocol_is_valid (const gchar *protocol) { @@ -95,12 +122,15 @@ remove_call (CallsSipOrigin *self, static void remove_calls (CallsSipOrigin *self, const gchar *reason) { - gpointer call; + CallsCall *call; GList *next; while (self->calls != NULL) { call = self->calls->data; next = self->calls->next; + + calls_call_hang_up (call); + g_list_free_1 (self->calls); self->calls = next; @@ -199,6 +229,10 @@ calls_sip_origin_set_property (GObject *object, self->host = g_value_dup_string (value); break; + case PROP_ACC_PORT: + self->port = g_value_get_int (value); + break; + case PROP_ACC_PROTOCOL: if (!protocol_is_valid (g_value_get_string (value))) { g_warning ("Tried setting invalid protocol: '%s'\n" @@ -215,6 +249,10 @@ calls_sip_origin_set_property (GObject *object, self->use_direct_connection = g_value_get_boolean (value); break; + case PROP_SIP_CONTEXT: + self->ctx = g_value_get_pointer (value); + break; + default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; @@ -247,6 +285,10 @@ calls_sip_origin_get_property (GObject *object, g_value_set_string (value, self->host); break; + case PROP_ACC_PORT: + g_value_set_int (value, self->port); + break; + case PROP_ACC_PROTOCOL: g_value_set_string (value, self->protocol); break; @@ -258,13 +300,47 @@ calls_sip_origin_get_property (GObject *object, } +static void +calls_sip_origin_constructed (GObject *object) +{ + CallsSipOrigin *self = CALLS_SIP_ORIGIN (object); + + init_sip_account (self); + + G_OBJECT_CLASS (calls_sip_origin_parent_class)->constructed (object); +} + + static void calls_sip_origin_dispose (GObject *object) { CallsSipOrigin *self = CALLS_SIP_ORIGIN (object); + if (self->state == SIP_ACCOUNT_NULL) + return; + remove_calls (self, NULL); + if (self->oper) { + g_clear_pointer (&self->oper->call_handle, nua_handle_unref); + g_clear_pointer (&self->oper->incoming_call_handle, nua_handle_unref); + g_clear_pointer (&self->oper->register_handle, nua_handle_unref); + } + + if (self->nua) { + g_debug ("Requesting nua_shutdown ()"); + nua_shutdown (self->nua); + // need to wait for nua_r_shutdown event before calling nua_destroy () + while (!self->is_nua_shutdown) + su_root_step (self->ctx->root, 100); + + g_debug ("nua_shutdown () complete. Destroying nua handle"); + nua_destroy (self->nua); + self->nua = NULL; + } + + self->state = SIP_ACCOUNT_NULL; + G_OBJECT_CLASS (calls_sip_origin_parent_class)->dispose (object); } @@ -289,6 +365,7 @@ calls_sip_origin_class_init (CallsSipOriginClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); + object_class->constructed = calls_sip_origin_constructed; object_class->dispose = calls_sip_origin_dispose; object_class->finalize = calls_sip_origin_finalize; object_class->get_property = calls_sip_origin_get_property; @@ -299,7 +376,7 @@ calls_sip_origin_class_init (CallsSipOriginClass *klass) "User", "The username for authentication", "", - G_PARAM_READWRITE); + G_PARAM_READWRITE | G_PARAM_CONSTRUCT); g_object_class_install_property (object_class, PROP_ACC_USER, props[PROP_ACC_USER]); props[PROP_ACC_PASSWORD] = @@ -307,7 +384,7 @@ calls_sip_origin_class_init (CallsSipOriginClass *klass) "Password", "The password for authentication", "", - G_PARAM_WRITABLE); + G_PARAM_WRITABLE | G_PARAM_CONSTRUCT); g_object_class_install_property (object_class, PROP_ACC_PASSWORD, props[PROP_ACC_PASSWORD]); props[PROP_ACC_HOST] = @@ -315,17 +392,24 @@ calls_sip_origin_class_init (CallsSipOriginClass *klass) "Host", "The fqdn of the SIP server", "", - G_PARAM_READWRITE); + G_PARAM_READWRITE | G_PARAM_CONSTRUCT); g_object_class_install_property (object_class, PROP_ACC_HOST, props[PROP_ACC_HOST]); + props[PROP_ACC_PORT] = + g_param_spec_int ("port", + "Port", + "Port of the SIP server", + 1025, 65535, 5060, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT); + g_object_class_install_property (object_class, PROP_ACC_PORT, props[PROP_ACC_PORT]); + props[PROP_ACC_PROTOCOL] = g_param_spec_string ("protocol", "Protocol", "The protocol used to connect to the SIP server", "UDP", - G_PARAM_READWRITE); + G_PARAM_READWRITE | G_PARAM_CONSTRUCT); g_object_class_install_property (object_class, PROP_ACC_PROTOCOL, props[PROP_ACC_PROTOCOL]); - props[PROP_ACC_DIRECT] = g_param_spec_boolean ("direct-connection", "Direct connection", @@ -334,6 +418,13 @@ calls_sip_origin_class_init (CallsSipOriginClass *klass) G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY); g_object_class_install_property (object_class, PROP_ACC_DIRECT, props[PROP_ACC_DIRECT]); + props[PROP_SIP_CONTEXT] = + g_param_spec_pointer ("sip-context", + "SIP context", + "The SIP context (sofia) used for our sip handles", + G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY); + g_object_class_install_property (object_class, PROP_SIP_CONTEXT, props[PROP_SIP_CONTEXT]); + #define IMPLEMENTS(ID, NAME) \ g_object_class_override_property (object_class, ID, NAME); \ props[ID] = g_object_class_find_property(object_class, NAME); @@ -362,6 +453,10 @@ static void calls_sip_origin_init (CallsSipOrigin *self) { self->name = g_string_new (NULL); + + /* Direct connection mode is useful for debugging purposes */ + self->use_direct_connection = TRUE; + } @@ -377,19 +472,22 @@ calls_sip_origin_create_inbound (CallsSipOrigin *self, CallsSipOrigin * -calls_sip_origin_new (const gchar *name, - const gchar *user, - const gchar *password, - const gchar *host, - const gchar *protocol, - gboolean direct_connection) - +calls_sip_origin_new (const gchar *name, + CallsSipContext *sip_context, + const gchar *user, + const gchar *password, + const gchar *host, + gint port, + const gchar *protocol, + gboolean direct_connection) { CallsSipOrigin *origin = g_object_new (CALLS_TYPE_SIP_ORIGIN, + "sip-context", sip_context, "user", user, "password", password, "host", host, + "port", port, "protocol", protocol, "direct-connection", direct_connection, NULL); diff --git a/plugins/sip/calls-sip-origin.h b/plugins/sip/calls-sip-origin.h index 0a630d1..f84faa8 100644 --- a/plugins/sip/calls-sip-origin.h +++ b/plugins/sip/calls-sip-origin.h @@ -24,6 +24,8 @@ #pragma once +#include "calls-sip-util.h" + #include G_BEGIN_DECLS @@ -32,12 +34,14 @@ G_BEGIN_DECLS G_DECLARE_FINAL_TYPE (CallsSipOrigin, calls_sip_origin, CALLS, SIP_ORIGIN, GObject); -CallsSipOrigin *calls_sip_origin_new (const gchar *name, - const gchar *user, - const gchar *password, - const gchar *host, - const gchar *protocol, - gboolean direct_connection); +CallsSipOrigin *calls_sip_origin_new (const gchar *name, + CallsSipContext *sip_context, + const gchar *user, + const gchar *password, + const gchar *host, + gint port, + const gchar *protocol, + gboolean direct_connection); void calls_sip_origin_create_inbound (CallsSipOrigin *self, const gchar *number); diff --git a/plugins/sip/calls-sip-provider.c b/plugins/sip/calls-sip-provider.c index c1fd950..8f97602 100644 --- a/plugins/sip/calls-sip-provider.c +++ b/plugins/sip/calls-sip-provider.c @@ -22,13 +22,22 @@ * */ +#define G_LOG_DOMAIN "CallsSipProvider" + +#define SU_ROOT_MAGIC_T CallsSipProvider + #include "calls-sip-provider.h" #include "calls-message-source.h" #include "calls-provider.h" #include "calls-sip-origin.h" +#include "calls-sip-util.h" +#include "calls-sip-enums.h" +#include "config.h" #include +#include +#include #define SIP_ACCOUNT_FILE "sip-account.cfg" @@ -39,17 +48,26 @@ struct _CallsSipProvider GListStore *origins; /* SIP */ + CallsSipContext *ctx; + SipEngineState sip_state; gchar *filename; }; +enum { + PROP_0, + PROP_SIP_STATE, + PROP_LAST_PROP, +}; +static GParamSpec *props[PROP_LAST_PROP]; + static void calls_sip_provider_message_source_interface_init (CallsMessageSourceInterface *iface); G_DEFINE_DYNAMIC_TYPE_EXTENDED (CallsSipProvider, calls_sip_provider, CALLS_TYPE_PROVIDER, 0, G_IMPLEMENT_INTERFACE_DYNAMIC (CALLS_TYPE_MESSAGE_SOURCE, - calls_sip_provider_message_source_interface_init)) + calls_sip_provider_message_source_interface_init)); static gboolean check_required_keys (GKeyFile *key_file, @@ -70,6 +88,7 @@ check_required_keys (GKeyFile *key_file, return TRUE; } + static void calls_sip_provider_load_accounts (CallsSipProvider *self) { @@ -91,6 +110,7 @@ calls_sip_provider_load_accounts (CallsSipProvider *self) g_autofree gchar *password = NULL; g_autofree gchar *host = NULL; g_autofree gchar *protocol = NULL; + gint port = 0; gboolean direct_connection = g_key_file_get_boolean (key_file, groups[i], "Direct", NULL); @@ -107,31 +127,55 @@ calls_sip_provider_load_accounts (CallsSipProvider *self) password = g_key_file_get_string (key_file, groups[i], "Password", NULL); host = g_key_file_get_string (key_file, groups[i], "Host", NULL); protocol = g_key_file_get_string (key_file, groups[i], "Protocol", NULL); + port = g_key_file_get_integer (key_file, groups[i], "Port", NULL); skip: if (protocol == NULL) protocol = g_strdup ("UDP"); + /* If Protocol is TLS fall back to port 5061, 5060 otherwise */ + if (port == 0) { + if (g_strcmp0 (protocol, "TLS") == 0) + port = 5061; + else + port = 5060; + } + g_debug ("Adding origin for SIP account %s", groups[i]); - calls_sip_provider_add_origin (self, groups[i], user, password, host, protocol, direct_connection); + calls_sip_provider_add_origin (self, groups[i], user, password, host, port, protocol, direct_connection); } g_strfreev (groups); } + static const char * calls_sip_provider_get_name (CallsProvider *provider) { return "SIP provider"; } + static const char * calls_sip_provider_get_status (CallsProvider *provider) { - return "Normal"; + CallsSipProvider *self = CALLS_SIP_PROVIDER (provider); + + switch (self->sip_state) { + case SIP_ENGINE_ERROR: + return "Error"; + + case SIP_ENGINE_READY: + return "Normal"; + + default: + break; + } + return "Unknown"; } + static GListModel * calls_sip_provider_get_origins (CallsProvider *provider) { @@ -140,12 +184,118 @@ calls_sip_provider_get_origins (CallsProvider *provider) return G_LIST_MODEL (self->origins); } + +static void +calls_sip_provider_deinit_sip (CallsSipProvider *self) +{ + GSource *gsource; + + if (self->sip_state == SIP_ENGINE_NULL) + return; + + /* clean up sofia */ + if (!self->ctx) + goto bail; + + + if (self->ctx->root) { + gsource = su_glib_root_gsource (self->ctx->root); + g_source_destroy (gsource); + su_root_destroy (self->ctx->root); + + if (su_home_unref (self->ctx->home) != 1) + g_error ("Error in su_home_unref ()"); + } + g_clear_pointer (&self->ctx, g_free); + + bail: + self->sip_state = SIP_ENGINE_NULL; + g_object_notify_by_pspec (G_OBJECT (self), props[PROP_SIP_STATE]); +} + + +static gboolean +calls_sip_provider_init_sofia (CallsSipProvider *self, + GError **error) +{ + GSource *gsource; + + g_return_val_if_fail (CALLS_IS_SIP_PROVIDER (self), FALSE); + + self->ctx = g_new0 (CallsSipContext, 1); + if (self->ctx == NULL) { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Could not allocate memory for the SIP context"); + goto err; + } + + if (su_init () != su_success) { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "su_init () failed"); + goto err; + } + + if (su_home_init (self->ctx->home) != su_success) { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "su_home_init () failed"); + goto err; + } + + self->ctx->root = su_glib_root_create (self); + if (self->ctx->root == NULL) { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "su_glib_root_create () failed"); + goto err; + } + gsource = su_glib_root_gsource (self->ctx->root); + if (gsource == NULL) { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "su_glib_root_gsource () failed"); + goto err; + } + + g_source_attach (gsource, NULL); + self->sip_state = SIP_ENGINE_READY; + g_object_notify_by_pspec (G_OBJECT (self), props[PROP_SIP_STATE]); + return TRUE; + + err: + self->sip_state = SIP_ENGINE_ERROR; + g_object_notify_by_pspec (G_OBJECT (self), props[PROP_SIP_STATE]); + return FALSE; +} + + +static void +calls_sip_provider_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + CallsSipProvider *self = CALLS_SIP_PROVIDER (object); + + switch (property_id) { + case PROP_SIP_STATE: + g_value_set_enum (value, self->sip_state); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + + static void calls_sip_provider_constructed (GObject *object) { CallsSipProvider *self = CALLS_SIP_PROVIDER (object); + g_autoptr (GError) error = NULL; - calls_sip_provider_load_accounts (self); + if (calls_sip_provider_init_sofia (self, &error)) + calls_sip_provider_load_accounts (self); + else + g_warning ("Could not initalize sofia stack: %s", error->message); G_OBJECT_CLASS (calls_sip_provider_parent_class)->constructed (object); } @@ -162,6 +312,8 @@ calls_sip_provider_dispose (GObject *object) g_free (self->filename); self->filename = NULL; + calls_sip_provider_deinit_sip (self); + G_OBJECT_CLASS (calls_sip_provider_parent_class)->dispose (object); } @@ -174,10 +326,21 @@ calls_sip_provider_class_init (CallsSipProviderClass *klass) object_class->constructed = calls_sip_provider_constructed; object_class->dispose = calls_sip_provider_dispose; + object_class->get_property = calls_sip_provider_get_property; provider_class->get_name = calls_sip_provider_get_name; provider_class->get_status = calls_sip_provider_get_status; provider_class->get_origins = calls_sip_provider_get_origins; + + props[PROP_SIP_STATE] = + g_param_spec_enum ("sip-state", + "SIP state", + "The state of the SIP engine", + SIP_TYPE_ENGINE_STATE, + SIP_ENGINE_NULL, + G_PARAM_READABLE); + + g_object_class_install_properties (object_class, PROP_LAST_PROP, props); } @@ -191,6 +354,10 @@ static void calls_sip_provider_init (CallsSipProvider *self) { self->origins = g_list_store_new (CALLS_TYPE_SIP_ORIGIN); + self->filename = g_build_filename (g_get_user_data_dir (), + APP_DATA_NAME, + SIP_ACCOUNT_FILE, + NULL); } @@ -200,17 +367,27 @@ calls_sip_provider_add_origin (CallsSipProvider *self, const gchar *user, const gchar *password, const gchar *host, + gint port, const gchar *protocol, gboolean direct_connection) { - g_autoptr (CallsSipOrigin) origin = - calls_sip_origin_new (name, - user, - password, - host, - protocol, - direct_connection); + g_autoptr (CallsSipOrigin) origin = NULL; + g_return_if_fail (CALLS_IS_SIP_PROVIDER (self)); + + origin = calls_sip_origin_new (name, + self->ctx, + user, + password, + host, + port, + protocol, + direct_connection); + + if (!origin) { + g_warning ("Could not create CallsSipOrigin"); + return; + } g_list_store_append (self->origins, origin); } diff --git a/plugins/sip/calls-sip-provider.h b/plugins/sip/calls-sip-provider.h index bee2a22..d460366 100644 --- a/plugins/sip/calls-sip-provider.h +++ b/plugins/sip/calls-sip-provider.h @@ -40,6 +40,7 @@ void calls_sip_provider_add_origin (CallsSipProvider *s const gchar *user, const gchar *password, const gchar *host, + gint port, const gchar *protocol, gboolean direct_connection); diff --git a/plugins/sip/calls-sip-util.h b/plugins/sip/calls-sip-util.h new file mode 100644 index 0000000..a2f96ce --- /dev/null +++ b/plugins/sip/calls-sip-util.h @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2021 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 . + * + * Author: Evangelos Ribeiro Tzaras + * + * SPDX-License-Identifier: GPL-3.0-or-later + * + */ + + +#pragma once + +#include +#include + +typedef struct +{ + su_home_t home[1]; + su_root_t *root; +} CallsSipContext; + +typedef struct +{ + nua_handle_t *register_handle; + nua_handle_t *call_handle; + nua_handle_t *incoming_call_handle; + CallsSipContext *context; +} CallsSipHandles; + + +/** + * SipEngineState: + * @SIP_ENGINE_NULL: Not initialized + * @SIP_ENGINE_INITIALIZING: Need to complete initialization + * @SIP_ENGINE_ERROR: Unrecoverable/Unhandled sofia-sip error + * @SIP_ENGINE_READY: Ready for operation + */ +typedef enum + { + SIP_ENGINE_NULL = 0, + SIP_ENGINE_INITIALIZING, + SIP_ENGINE_ERROR, + SIP_ENGINE_READY, + } SipEngineState; + +/** + * SipAccountState: + * @SIP_ACCOUNT_NULL: Not initialized + * @SIP_ACCOUNT_OFFLINE: Account considered offline (not ready for placing or receiving calls) + * @SIP_ACCOUNT_REGISTERING: REGISTER sent + * @SIP_ACCOUNT_AUTHENTICATING: Authenticating using web-auth/proxy-auth + * @SIP_ACCOUNT_ERROR: Unrecoverable error + * @SIP_ACCOUNT_ERROR_RETRY: Recoverable error (f.e. using other credentials) + * @SIP_ACCOUNT_ONLINE: Account considered online (can place and receive calls) + */ +typedef enum + { + SIP_ACCOUNT_NULL = 0, + SIP_ACCOUNT_OFFLINE, + SIP_ACCOUNT_REGISTERING, + SIP_ACCOUNT_AUTHENTICATING, + SIP_ACCOUNT_ERROR, + SIP_ACCOUNT_ERROR_RETRY, + SIP_ACCOUNT_ONLINE, + } SipAccountState; + diff --git a/plugins/sip/meson.build b/plugins/sip/meson.build index bbd1428..4196f93 100644 --- a/plugins/sip/meson.build +++ b/plugins/sip/meson.build @@ -48,6 +48,18 @@ sip_sources = files( ] ) +sip_enum_headers = [ + 'calls-sip-util.h', +] + +sip_enums = gnome.mkenums('calls-sip-enums', + h_template: 'calls-sip-enums.h.in', + c_template: 'calls-sip-enums.c.in', + sources: sip_enum_headers, +) + +sip_sources += sip_enums + shared_module( 'sip', sip_sources,