diff --git a/plugins/sip/calls-sdp-crypto-context.c b/plugins/sip/calls-sdp-crypto-context.c new file mode 100644 index 0000000..fda89e8 --- /dev/null +++ b/plugins/sip/calls-sdp-crypto-context.c @@ -0,0 +1,512 @@ +/* + * 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 . + * + * Author: Evangelos Ribeiro Tzaras + * + * SPDX-License-Identifier: GPL-3.0-or-later + * + */ + +#include "calls-sdp-crypto-context.h" +#include "calls-sip-enums.h" + +#include + +enum { + PROP_0, + PROP_STATE, + PROP_LAST_PROP +}; +static GParamSpec *props[PROP_LAST_PROP]; + +struct _CallsSdpCryptoContext { + GObject parent_instance; + + GList *local_crypto_attributes; + GList *remote_crypto_attributes; + + CallsCryptoContextState state; + int negotiated_tag; +}; + +#if GLIB_CHECK_VERSION (2, 70, 0) +G_DEFINE_FINAL_TYPE (CallsSdpCryptoContext, calls_sdp_crypto_context, G_TYPE_OBJECT) +#else +G_DEFINE_TYPE (CallsSdpCryptoContext, calls_sdp_crypto_context, G_TYPE_OBJECT) +#endif + + +static GStrv +get_all_crypto_attributes_strv (sdp_media_t *media) +{ +#if GLIB_CHECK_VERSION (2, 68, 0) + g_autoptr (GStrvBuilder) builder = NULL; + + g_assert (media); + + builder = g_strv_builder_new (); + + for (sdp_attribute_t *attr = media->m_attributes; attr; attr = attr->a_next) { + g_autofree char *crypto_str = NULL; + + if (g_strcmp0 (attr->a_name, "crypto") != 0) + continue; + + crypto_str = g_strconcat ("a=crypto:", attr->a_value, NULL); + g_strv_builder_add (builder, crypto_str); + } + + return g_strv_builder_end (builder); +#else + /* implement a poor mans GStrv */ + g_autofree char *attribute_string = NULL; + + g_assert (media); + + for (sdp_attribute_t *attr = media->m_attributes; attr; attr = attr->a_next) { + g_autofree char *crypto_str = NULL; + + if (g_strcmp0 (attr->a_name, "crypto") != 0) + continue; + + crypto_str = g_strconcat ("a=crypto:", attr->a_value, NULL); + if (!attribute_string) { + attribute_string = g_strdup (crypto_str); + } else { + g_autofree char *tmp = attribute_string; + attribute_string = g_strconcat (attribute_string, "\n", crypto_str, NULL); + } + } + return g_strsplit (attribute_string, "\n", -1); +#endif +} + + +static gboolean +crypto_attribute_is_supported (CallsSdpCryptoContext *self, + calls_srtp_crypto_attribute *attr) +{ + g_assert (attr); + + if (attr->crypto_suite == CALLS_SRTP_SUITE_UNKNOWN) + return FALSE; + + /* TODO setup a policy mechanism, for now this is hardcoded */ + if (attr->unencrypted_srtp || + attr->unauthenticated_srtp || + attr->unencrypted_srtcp) + return FALSE; + + return TRUE; +} + +static calls_srtp_crypto_attribute * +get_crypto_attribute_by_tag (GList *attributes, + guint tag) +{ + + g_assert (attributes); + g_assert (tag > 0); + + for (GList *node = attributes; node; node = node->next) { + calls_srtp_crypto_attribute *attr = node->data; + + if (attr->tag == tag) + return attr; + } + + return NULL; +} + + +static void +set_state (CallsSdpCryptoContext *self, + CallsCryptoContextState state) +{ + g_assert (CALLS_IS_SDP_CRYPTO_CONTEXT (self)); + + if (self->state == state) + return; + + self->state = state; + g_object_notify_by_pspec (G_OBJECT (self), props[PROP_STATE]); +} + + +/* Returns if %TRUE if the state was updated, %FALSE otherwise */ +static gboolean +update_state (CallsSdpCryptoContext *self) +{ + GList *tags_local = NULL; + GList *tags_remote = NULL; + gint negotiated_tag = -1; + calls_srtp_crypto_attribute *local_crypto; + calls_srtp_crypto_attribute *remote_crypto; + + + g_assert (CALLS_IS_SDP_CRYPTO_CONTEXT (self)); + + /* Cannot update final states */ + if (self->state == CALLS_CRYPTO_CONTEXT_STATE_NEGOTIATION_FAILED || + self->state == CALLS_CRYPTO_CONTEXT_STATE_NEGOTIATION_SUCCESS) + return FALSE; + + if (self->state == CALLS_CRYPTO_CONTEXT_STATE_INIT) { + if (self->local_crypto_attributes) { + set_state (self, CALLS_CRYPTO_CONTEXT_STATE_OFFER_LOCAL); + return TRUE; + } + if (self->remote_crypto_attributes) { + set_state (self, CALLS_CRYPTO_CONTEXT_STATE_OFFER_REMOTE); + return TRUE; + } + + return FALSE; + } + + for (GList *node = self->local_crypto_attributes; node; node = node->next) { + calls_srtp_crypto_attribute *attr = node->data; + tags_local = g_list_append (tags_local, GUINT_TO_POINTER (attr->tag)); + } + + for (GList *node = self->remote_crypto_attributes; node; node = node->next) { + calls_srtp_crypto_attribute *attr = node->data; + tags_remote = g_list_append (tags_remote, GUINT_TO_POINTER (attr->tag)); + } + + if (self->state == CALLS_CRYPTO_CONTEXT_STATE_OFFER_LOCAL) { + for (GList *node = tags_local; node; node = node->next) { + if (g_list_find (tags_remote, node->data)) { + negotiated_tag = GPOINTER_TO_UINT (node->data); + break; + } + } + } else if (self->state == CALLS_CRYPTO_CONTEXT_STATE_OFFER_REMOTE) { + for (GList *node = tags_remote; node; node = node->next) { + if (g_list_find (tags_local, node->data)) { + negotiated_tag = GPOINTER_TO_UINT (node->data); + break; + } + } + } else { + g_assert_not_reached (); + } + + g_list_free (tags_local); + g_list_free (tags_remote); + + if (negotiated_tag == -1) { + self->state = CALLS_CRYPTO_CONTEXT_STATE_NEGOTIATION_FAILED; + return TRUE; + } + + local_crypto = get_crypto_attribute_by_tag (self->local_crypto_attributes, + negotiated_tag); + remote_crypto = get_crypto_attribute_by_tag (self->remote_crypto_attributes, + negotiated_tag); + + if (local_crypto->crypto_suite != remote_crypto->crypto_suite) { + set_state (self, CALLS_CRYPTO_CONTEXT_STATE_NEGOTIATION_FAILED); + return TRUE; + } + + /* TODO check mandatory parameters and policy constrains */ + + self->negotiated_tag = negotiated_tag; + set_state (self, CALLS_CRYPTO_CONTEXT_STATE_NEGOTIATION_SUCCESS); + + return TRUE; +} + + +static void +calls_sdp_crypto_context_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + CallsSdpCryptoContext *self = CALLS_SDP_CRYPTO_CONTEXT (object); + + switch (property_id) { + case PROP_STATE: + g_value_set_enum (value, self->state); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + + +static void +calls_sdp_crypto_context_dispose (GObject *object) +{ + CallsSdpCryptoContext *self = CALLS_SDP_CRYPTO_CONTEXT (object); + + g_clear_list (&self->local_crypto_attributes, (GDestroyNotify) calls_srtp_crypto_attribute_free); + g_clear_list (&self->remote_crypto_attributes, (GDestroyNotify) calls_srtp_crypto_attribute_free); + + G_OBJECT_CLASS (calls_sdp_crypto_context_parent_class)->dispose (object); +} + + +static void +calls_sdp_crypto_context_class_init (CallsSdpCryptoContextClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->dispose = calls_sdp_crypto_context_dispose; + object_class->get_property = calls_sdp_crypto_context_get_property; + + props[PROP_STATE] = + g_param_spec_enum ("state", + "State", + "State of the crypto context", + CALLS_TYPE_CRYPTO_CONTEXT_STATE, + CALLS_CRYPTO_CONTEXT_STATE_INIT, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties (object_class, PROP_LAST_PROP, props); +} + + +static void +calls_sdp_crypto_context_init (CallsSdpCryptoContext *self) +{ +} + + +CallsSdpCryptoContext * +calls_sdp_crypto_context_new (void) +{ + return g_object_new (CALLS_TYPE_SDP_CRYPTO_CONTEXT, NULL); +} + + +gboolean +calls_sdp_crypto_context_set_local_media (CallsSdpCryptoContext *self, + sdp_media_t *media) +{ + g_auto (GStrv) crypto_strv = NULL; + guint n_crypto_attr; + + g_return_val_if_fail (CALLS_IS_SDP_CRYPTO_CONTEXT (self), FALSE); + g_return_val_if_fail (media, FALSE); + + if (self->local_crypto_attributes) { + g_warning ("Local crypto attributes already set"); + return FALSE; + } + + crypto_strv = get_all_crypto_attributes_strv (media); + n_crypto_attr = g_strv_length (crypto_strv); + + if (n_crypto_attr == 0) { + g_warning ("No crypto attributes found in given SDP media"); + return FALSE; + } + + for (guint i = 0; i < n_crypto_attr; i++) { + g_autoptr (GError) error = NULL; + calls_srtp_crypto_attribute *attr; + + attr = calls_srtp_parse_sdp_crypto_attribute (crypto_strv[i], &error); + if (!attr) { + g_warning ("Failed parsing crypto attribute '%s': %s", + crypto_strv[i], error->message); + continue; + } + self->local_crypto_attributes = + g_list_append (self->local_crypto_attributes, attr); + } + + if (!self->local_crypto_attributes) { + g_warning ("Could not parse a single crypto attribute, aborting"); + return FALSE; + } + + return update_state (self); +} + + +gboolean +calls_sdp_crypto_context_set_remote_media (CallsSdpCryptoContext *self, + sdp_media_t *media) +{ + g_auto (GStrv) crypto_strv = NULL; + + guint n_crypto_attr; + + g_return_val_if_fail (CALLS_IS_SDP_CRYPTO_CONTEXT (self), FALSE); + g_return_val_if_fail (media, FALSE); + + if (self->remote_crypto_attributes) { + g_warning ("Remote crypto attributes already set"); + return FALSE; + } + + crypto_strv = get_all_crypto_attributes_strv (media); + n_crypto_attr = g_strv_length (crypto_strv); + + if (n_crypto_attr == 0) { + g_warning ("No crypto attributes found in given SDP media"); + return FALSE; + } + + for (guint i = 0; i < n_crypto_attr; i++) { + g_autoptr (GError) error = NULL; + calls_srtp_crypto_attribute *attr; + + attr = calls_srtp_parse_sdp_crypto_attribute (crypto_strv[i], &error); + if (!attr) { + g_warning ("Failed parsing crypto attribute '%s': %s", + crypto_strv[i], error->message); + continue; + } + self->remote_crypto_attributes = + g_list_append (self->remote_crypto_attributes, attr); + } + + if (!self->remote_crypto_attributes) { + g_warning ("Could not parse a single crypto attribute, aborting"); + return FALSE; + } + + return update_state (self); +} + + +calls_srtp_crypto_attribute * +calls_sdp_crypto_context_get_local_crypto (CallsSdpCryptoContext *self) +{ + g_return_val_if_fail (CALLS_IS_SDP_CRYPTO_CONTEXT (self), NULL); + + if (self->state != CALLS_CRYPTO_CONTEXT_STATE_NEGOTIATION_SUCCESS) + return NULL; + + return get_crypto_attribute_by_tag (self->local_crypto_attributes, + self->negotiated_tag); +} + + +calls_srtp_crypto_attribute * +calls_sdp_crypto_context_get_remote_crypto (CallsSdpCryptoContext *self) +{ + g_return_val_if_fail (CALLS_IS_SDP_CRYPTO_CONTEXT (self), NULL); + + if (self->state != CALLS_CRYPTO_CONTEXT_STATE_NEGOTIATION_SUCCESS) + return NULL; + + return get_crypto_attribute_by_tag (self->remote_crypto_attributes, + self->negotiated_tag); +} + + +gboolean +calls_sdp_crypto_context_generate_offer (CallsSdpCryptoContext *self) +{ + calls_srtp_crypto_attribute *attr; + + g_return_val_if_fail (CALLS_IS_SDP_CRYPTO_CONTEXT (self), FALSE); + + if (self->state != CALLS_CRYPTO_CONTEXT_STATE_INIT) { + g_warning ("Cannot generate offer. Need INIT state, but found %d", + self->state); + return FALSE; + } + + g_assert (!self->local_crypto_attributes); + + attr = calls_srtp_crypto_attribute_new (1); + attr->tag = 1; + attr->crypto_suite = CALLS_SRTP_SUITE_AES_128_SHA1_80; + calls_srtp_crypto_attribute_init_keys (attr); + + self->local_crypto_attributes = g_list_append (NULL, attr); + + attr = calls_srtp_crypto_attribute_new (1); + attr->tag = 2; + attr->crypto_suite = CALLS_SRTP_SUITE_AES_128_SHA1_32; + calls_srtp_crypto_attribute_init_keys (attr); + + self->local_crypto_attributes = g_list_append (self->local_crypto_attributes, attr); + + return update_state (self); +} + + +gboolean +calls_sdp_crypto_context_generate_answer (CallsSdpCryptoContext *self) +{ + calls_srtp_crypto_attribute *attr = NULL; + + g_return_val_if_fail (CALLS_IS_SDP_CRYPTO_CONTEXT (self), FALSE); + + if (self->state != CALLS_CRYPTO_CONTEXT_STATE_OFFER_REMOTE) { + g_warning ("Cannot generate answer. Need OFFER_REMOTE state, but found %d", + self->state); + return FALSE; + } + + for (GList *node = self->remote_crypto_attributes; node; node = node->next) { + calls_srtp_crypto_attribute *attr_offer = node->data; + + if (crypto_attribute_is_supported (self, attr_offer)) { + attr = calls_srtp_crypto_attribute_new (1); + attr->crypto_suite = attr_offer->crypto_suite; + attr->tag = attr_offer->tag; + calls_srtp_crypto_attribute_init_keys (attr); + } + } + + if (!attr) + return FALSE; + + self->local_crypto_attributes = g_list_append (NULL, attr); + + return update_state (self); +} + + +gboolean +calls_sdp_crypto_context_get_is_negotiated (CallsSdpCryptoContext *self) +{ + g_return_val_if_fail (CALLS_IS_SDP_CRYPTO_CONTEXT (self), FALSE); + + return self->state == CALLS_CRYPTO_CONTEXT_STATE_NEGOTIATION_SUCCESS; +} + + +CallsCryptoContextState +calls_sdp_crypto_context_get_state (CallsSdpCryptoContext *self) +{ + g_return_val_if_fail (CALLS_IS_SDP_CRYPTO_CONTEXT (self), CALLS_CRYPTO_CONTEXT_STATE_INIT); + + return self->state; +} + + +GList * +calls_sdp_crypto_context_get_crypto_candidates (CallsSdpCryptoContext *self, + gboolean remote) +{ + g_return_val_if_fail (CALLS_IS_SDP_CRYPTO_CONTEXT (self), NULL); + + return g_list_copy (remote ? self->remote_crypto_attributes : self->local_crypto_attributes); +} diff --git a/plugins/sip/calls-sdp-crypto-context.h b/plugins/sip/calls-sdp-crypto-context.h new file mode 100644 index 0000000..efb6fed --- /dev/null +++ b/plugins/sip/calls-sdp-crypto-context.h @@ -0,0 +1,66 @@ +/* + * 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 . + * + * Author: Evangelos Ribeiro Tzaras + * + * SPDX-License-Identifier: GPL-3.0-or-later + * + */ + +#pragma once + +#include "calls-srtp-utils.h" + +#include + +#include + +G_BEGIN_DECLS + +/** XXX media line with cryptographic key parameters or session parameters that we + * do not support MUST be rejected (https://datatracker.ietf.org/doc/html/rfc4568#section-7.1.2) + */ + +typedef enum { + CALLS_CRYPTO_CONTEXT_STATE_INIT = 0, + CALLS_CRYPTO_CONTEXT_STATE_OFFER_LOCAL, + CALLS_CRYPTO_CONTEXT_STATE_OFFER_REMOTE, + CALLS_CRYPTO_CONTEXT_STATE_NEGOTIATION_FAILED, + CALLS_CRYPTO_CONTEXT_STATE_NEGOTIATION_SUCCESS +} CallsCryptoContextState; + + +#define CALLS_TYPE_SDP_CRYPTO_CONTEXT (calls_sdp_crypto_context_get_type ()) + +G_DECLARE_FINAL_TYPE (CallsSdpCryptoContext, calls_sdp_crypto_context, CALLS, SDP_CRYPTO_CONTEXT, GObject); + +CallsSdpCryptoContext *calls_sdp_crypto_context_new (void); +gboolean calls_sdp_crypto_context_set_local_media (CallsSdpCryptoContext *self, + sdp_media_t *media); +gboolean calls_sdp_crypto_context_set_remote_media (CallsSdpCryptoContext *self, + sdp_media_t *media); +calls_srtp_crypto_attribute *calls_sdp_crypto_context_get_local_crypto (CallsSdpCryptoContext *self); +calls_srtp_crypto_attribute *calls_sdp_crypto_context_get_remote_crypto (CallsSdpCryptoContext *self); +gboolean calls_sdp_crypto_context_generate_offer (CallsSdpCryptoContext *self); +gboolean calls_sdp_crypto_context_generate_answer (CallsSdpCryptoContext *self); +gboolean calls_sdp_crypto_context_get_is_negotiated (CallsSdpCryptoContext *self); +CallsCryptoContextState calls_sdp_crypto_context_get_state (CallsSdpCryptoContext *self); +GList *calls_sdp_crypto_context_get_crypto_candidates (CallsSdpCryptoContext *self, + gboolean remote); + +G_END_DECLS diff --git a/plugins/sip/meson.build b/plugins/sip/meson.build index 9b3e1dc..65959c9 100644 --- a/plugins/sip/meson.build +++ b/plugins/sip/meson.build @@ -44,6 +44,7 @@ sip_deps = [ sip_sources = files( [ + 'calls-sdp-crypto-context.c', 'calls-sdp-crypto-context.h', 'calls-sip-call.c', 'calls-sip-call.h', 'calls-sip-origin.c', 'calls-sip-origin.h', 'calls-sip-provider.c', 'calls-sip-provider.h', @@ -65,6 +66,7 @@ pipeline_enums = gnome.mkenums_simple('calls-media-pipeline-enums', sip_sources += pipeline_enums sip_enum_headers = [ + 'calls-sdp-crypto-context.h', 'calls-sip-util.h', ] diff --git a/tests/meson.build b/tests/meson.build index 1f68d2c..fa45543 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -138,6 +138,21 @@ t = executable('srtp', test_sources, ) test('srtp', t, env: test_env) +test_sources = [ 'test-sdp-crypto.c' ] +test_sources += sip_sources +t = executable('sdp-crypto', test_sources, + c_args : test_cflags, + link_args: test_link_args, + pie: true, + link_with : [calls_vala, libcalls], + dependencies: [calls_deps, sip_deps], + include_directories : [ + calls_includes, + sip_include, + ] + ) +test('sdp-crypto', t, env: test_env) + test_sources = [ 'test-util.c' ] t = executable('util', test_sources, c_args : test_cflags, diff --git a/tests/test-sdp-crypto.c b/tests/test-sdp-crypto.c new file mode 100644 index 0000000..df733bf --- /dev/null +++ b/tests/test-sdp-crypto.c @@ -0,0 +1,177 @@ +/* + * Copyright (C) 2022 Purism SPC + * + * SPDX-License-Identifier: GPL-3.0+ + * + * Author: Evangelos Ribeiro Tzaras + */ + +#include "calls-sdp-crypto-context.h" + +#include + + +static void +test_crypto_manual (void) +{ + g_autoptr (CallsSdpCryptoContext) ctx = NULL; + g_autofree guchar *offer_key = NULL; + g_autofree char *offer_key_b64 = NULL; + g_autofree char *offer_crypto_attr = NULL; + g_autofree char *offer_full = NULL; + g_autofree guchar *answer_key = NULL; + g_autofree char *answer_key_b64 = NULL; + g_autofree char *answer_crypto_attr = NULL; + g_autofree char *answer_full = NULL; + calls_srtp_crypto_attribute *offer_attr; + calls_srtp_crypto_attribute *answer_attr; + + su_home_t home; + sdp_parser_t *sdp_parser_offer; + sdp_session_t *sdp_offer; + sdp_parser_t *sdp_parser_answer; + sdp_session_t *sdp_answer; + + su_home_init (&home); + + ctx = calls_sdp_crypto_context_new (); + + offer_key = calls_srtp_generate_key_salt (30); + offer_key_b64 = g_base64_encode (offer_key, 30); + g_debug ("Offer key: %s", offer_key_b64); + + offer_crypto_attr = g_strdup_printf ("a=crypto:1 AES_CM_128_HMAC_SHA1_32 inline:%s", + offer_key_b64); + offer_full = g_strdup_printf ("m=audio 21000 RTP/SAVP 0\r\n" + "%s", + offer_crypto_attr); + + sdp_parser_offer = sdp_parse (&home, offer_full, -1, sdp_f_config); + + sdp_offer = sdp_session (sdp_parser_offer); + + + g_assert_true (calls_sdp_crypto_context_set_remote_media (ctx, sdp_offer->sdp_media)); + + answer_key = calls_srtp_generate_key_salt (30); + answer_key_b64 = g_base64_encode (answer_key, 30); + g_debug ("Answer key: %s", answer_key_b64); + + answer_crypto_attr = g_strdup_printf ("a=crypto:1 AES_CM_128_HMAC_SHA1_32 inline:%s", + answer_key_b64); + answer_full = g_strdup_printf ("m=audio 42000 RTP/SAVP 0\r\n" + "%s", + answer_crypto_attr); + + sdp_parser_answer = sdp_parse (&home, answer_full, -1, sdp_f_config); + sdp_answer = sdp_session (sdp_parser_answer); + + g_assert_true (calls_sdp_crypto_context_set_local_media (ctx, sdp_answer->sdp_media)); + + g_assert_true (calls_sdp_crypto_context_get_is_negotiated (ctx)); + + offer_attr = calls_sdp_crypto_context_get_remote_crypto (ctx); + g_assert_true (offer_attr); + g_assert_cmpuint (offer_attr->n_key_params, ==, 1); + g_assert_cmpstr (offer_attr->key_params[0].b64_keysalt, ==, offer_key_b64); + + answer_attr = calls_sdp_crypto_context_get_local_crypto (ctx); + g_assert_true (answer_attr); + g_assert_cmpuint (answer_attr->n_key_params, ==, 1); + g_assert_cmpstr (answer_attr->key_params[0].b64_keysalt, ==, answer_key_b64); + + g_assert_cmpint (offer_attr->tag, ==, answer_attr->tag); + g_assert_cmpint (offer_attr->crypto_suite, ==, answer_attr->crypto_suite); + + sdp_parser_free (sdp_parser_offer); + sdp_parser_free (sdp_parser_answer); + + su_home_deinit (&home); +} + + +static void +test_crypto_offer_answer (void) +{ + g_autoptr (CallsSdpCryptoContext) ctx_offer = calls_sdp_crypto_context_new (); + g_autoptr (CallsSdpCryptoContext) ctx_answer = calls_sdp_crypto_context_new (); + g_autoptr (GList) attr_offer_list = NULL; + calls_srtp_crypto_attribute *attr_offer; + calls_srtp_crypto_attribute *attr_answer; + g_autofree char *attr_offer_str = NULL; + g_autofree char *attr_answer_str = NULL; + g_autofree char *media_line_offer = NULL; + g_autofree char *media_line_answer = NULL; + su_home_t home; + sdp_parser_t *sdp_parser_offer; + sdp_parser_t *sdp_parser_answer; + sdp_session_t *sdp_session_offer; + sdp_session_t *sdp_session_answer; + + su_home_init (&home); + + g_assert_cmpint (calls_sdp_crypto_context_get_state (ctx_offer), ==, + CALLS_CRYPTO_CONTEXT_STATE_INIT); + g_assert_cmpint (calls_sdp_crypto_context_get_state (ctx_answer), ==, + CALLS_CRYPTO_CONTEXT_STATE_INIT); + + calls_sdp_crypto_context_generate_offer (ctx_offer); + g_assert_cmpint (calls_sdp_crypto_context_get_state (ctx_offer), ==, + CALLS_CRYPTO_CONTEXT_STATE_OFFER_LOCAL); + + attr_offer_list = + calls_sdp_crypto_context_get_crypto_candidates (ctx_offer, FALSE); + attr_offer = attr_offer_list->data; + attr_offer_str = calls_srtp_print_sdp_crypto_attribute (attr_offer, NULL); + + g_assert_true (attr_offer_str); + + media_line_offer = g_strdup_printf ("m=audio 42024 RTP/SAVP 0\r\n%s", + attr_offer_str); + sdp_parser_offer = sdp_parse (&home, media_line_offer, -1, sdp_f_config); + sdp_session_offer = sdp_session (sdp_parser_offer); + + calls_sdp_crypto_context_set_remote_media (ctx_answer, + sdp_session_offer->sdp_media); + + g_assert_cmpint (calls_sdp_crypto_context_get_state (ctx_answer), ==, + CALLS_CRYPTO_CONTEXT_STATE_OFFER_REMOTE); + + calls_sdp_crypto_context_generate_answer (ctx_answer); + + g_assert_cmpint (calls_sdp_crypto_context_get_state (ctx_answer), ==, + CALLS_CRYPTO_CONTEXT_STATE_NEGOTIATION_SUCCESS); + g_assert_true (calls_sdp_crypto_context_get_is_negotiated (ctx_answer)); + + attr_answer = calls_sdp_crypto_context_get_remote_crypto (ctx_answer); + attr_answer_str = calls_srtp_print_sdp_crypto_attribute (attr_answer, NULL); + + g_assert_true (attr_answer_str); + + media_line_answer = g_strdup_printf ("m=audio 42124 RTP/SAVP 0\r\n%s", + attr_answer_str); + sdp_parser_answer = sdp_parse (&home, media_line_answer, -1, sdp_f_config); + sdp_session_answer = sdp_session (sdp_parser_answer); + + calls_sdp_crypto_context_set_remote_media (ctx_offer, + sdp_session_answer->sdp_media); + + g_assert_cmpint (calls_sdp_crypto_context_get_state (ctx_offer), ==, + CALLS_CRYPTO_CONTEXT_STATE_NEGOTIATION_SUCCESS); + g_assert_true (calls_sdp_crypto_context_get_is_negotiated (ctx_offer)); + + su_home_deinit (&home); +} + + +int +main (int argc, + char *argv[]) +{ + gtk_test_init (&argc, &argv, NULL); + + g_test_add_func ("/Calls/SDP_crypto/manual", test_crypto_manual); + g_test_add_func ("/Calls/SDP_crypto/offer_answer", test_crypto_offer_answer); + + return g_test_run (); +}