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 ();
+}